This is a creation in Article, where the information may have evolved or changed.
The fourth chapter: Test and Mock with Goconvey
How do we test for microservices? What are the special challenges? This section, we will look at the following points:
- Unit Test
- Unit tests for writing behavior patterns with Goconey
- Introduction to Mocking Tips
Because this chapter does not change the core service code, there is no base test
Introduction to micro-service testing
First, you must remember to test the pyramids:
Unit testing must be the basis for your integration, E2E, acceptance testing is less easy to develop and maintain
MicroServices have a number of different test challenges, and the same guidelines are used to build the software architecture. The unit tests for microservices are not the same as traditional, and we'll talk about it here.
In short, I emphasize several points:
- Do normal unit testing-your business logic, validators, and so on are not different because they run on microservices.
- Integrated parts, such as communicating with other services, sending information, using a database, must be designed with a dependency injection method to use the mock
- Features of many microservices: configuration files, communication of other services, elastic testing, etc. it takes a lot of time to do a little testing. These tests can be done with integrated testing, you take the Docker container as a whole and test it.
Code
Full code
git checkout P4
Introduced
Go's unit test is also designed by the author of Go to follow the pattern of language habits. The test file is identified by a name. If we want to test something in handler.go, we create the file Handlers_test.go, under the same folder.
We start with a pessimistic test, assert 404, when we ask for an address that does not exist
package serviceimport ( . "github.com/smartystreets/goconvey/convey" "testing" "net/http/httptest")func TestGetAccountWrongPath(t *testing.T) { Convey("Given a HTTP request for /invalid/123", t, func() { req := httptest.NewRequest("GET", "/invalid/123", nil) resp := httptest.NewRecorder() Convey("When the request is handled by the Router", func() { NewRouter().ServeHTTP(resp, req) Convey("Then the response should be a 404", func() { So(resp.Code, ShouldEqual, 404) }) }) })}
This test shows the pattern of "given-when-then" (if-when-inferred). We also use the Httptest package, which we use to declare that the requested object is also used as the object of the reply as an assertion condition.
Go and run him under Accountservice:
> go test ./...? github.com/callistaenterprise/goblog/accountservice [no test files]? github.com/callistaenterprise/goblog/accountservice/dbclient [no test files]? github.com/callistaenterprise/goblog/accountservice/model [no test files]ok github.com/callistaenterprise/goblog/accountservice/service 0.012s
./... The test files under the current folder and all subfolders are run. We can also go to the Service folder under Go Test, which will run the tests under this folder.
Mocking
The above test does not require a mock, because we do not use the dbclient inside the getaccount. For good requests, we need to return the results, we need a mock client to connect Boltdb. There are many mocking methods. My favorite is stretchr/. Testify/mock this bag.
Under the/dbclient folder, create the Mockclient.go to implement the Iboltclient interface:
Package Dbclientimport ("Github.com/stretchr/testify/mock" "Github.com/callistaenterprise/goblog /accountservice/model ")//Mockboltclient is a mock implementation of a datastore client for testing purposes.//Instead of The bolt. DB pointer, we ' re just putting a generic mock object from//strechr/testifytype mockboltclient struct {mock. mock}//from here, we'll declare three functions that makes our mockboltclient fulfill the interface iboltclient that we D Eclared in part 3.func (M *mockboltclient) Queryaccount (AccountId string) (model. Account, error) {args: = m.mock.called (accountId) return args. Get (0). (model. account), args. Error (1)}func (M *mockboltclient) Openboltdb () {//Does Nothing}func (M *mockboltclient) Seed () {//Does n othing}
Mockboltclient can now be used as a mock we can write. To the top, we implicitly define all the functions to implement the Iboltclient interface.
If you don't like the mock method, you can look at mockery and he can generate any mock of the Go interface
The Queryaccount function is a bit strange. But that's what testify does, so that we have a full-fledged mock of internal control.
Writing a mock
We create the next test function in Handlers_test.go:
Func testgetaccount (t *testing. T) {//Create a mock instance that implements the Iboltclient interface Mockrepo: = &dbclient. mockboltclient{}//Declare the mock behaviours. For "123" as input, return a proper the account struct and nil as error. For "456" as input, return the empty account object and a real error. Mockrepo.on ("Queryaccount", "123"). Return (model. Account{id: "123", Name: "person_123"}, Nil) Mockrepo.on ("Queryaccount", "456"). Return (model. account{}, FMT. Errorf ("Some error"))//Finally, assign Mockrepo to the Dbclient field (it's in _handlers.go_, e.g. in the Same package) Dbclient = Mockrepo convey ("Given a HTTP request for/accounts/123", T, func () {req: = Httptest. Newrequest ("GET", "/accounts/123", nil) resp: = Httptest. Newrecorder () convey ("When the request was handled by the Router", func () {Newrouter (). Servehttp (RESP, req) convey ("then the ResPonse should is a, func () {SO (resp. Code, Shouldequal, $) Account: = model. account{} JSON. Unmarshal (resp. Body.bytes (), &account) so (account. Id, Shouldequal, "123") so (account. Name, Shouldequal, "person_123")})})}
This test request path/accounts/123, our mock implements this. In when, we assert the HTTP state, deserialize the account structure, and the result of the fragment is the same as our mock.
I like Goconvey because this "given-when-then" way is easy to read.
We also request a pessimistic address/accounts/456, which asserts that it will get http404:
Convey("Given a HTTP request for /accounts/456", t, func() { req := httptest.NewRequest("GET", "/accounts/456", nil) resp := httptest.NewRecorder() Convey("When the request is handled by the Router", func() { NewRouter().ServeHTTP(resp, req) Convey("Then the response should be a 404", func() { So(resp.Code, ShouldEqual, 404) }) })})
Run for a minute.
> go test ./...? github.com/callistaenterprise/goblog/accountservice [no test files]? github.com/callistaenterprise/goblog/accountservice/dbclient [no test files]? github.com/callistaenterprise/goblog/accountservice/model [no test files]ok github.com/callistaenterprise/goblog/accountservice/service 0.026s
All Green!goconvey There is a GUI that performs all tests automatically every time we save a file. I'm not going to tell you, here's a look at the Code Coverage report:
Goconvey This unit test written in behavioral testing is not something everyone likes. There are many other test frameworks. You can search for a lot.
If we look at the test pyramid above, we will want to write the integration test, or the final acceptance test. We will then start the real boltdb and then talk about the integration test. May use the Go Docker remote API and the written BOLTDB image
Summarize
In this section we write the first unit test with Goconvey. Colleagues use mock packs to help us simulate.
In the next section, we will launch the Docker swarm and deploy our service into swarm