This is a creation in Article, where the information may have evolved or changed. As long as you have written the Go program, you must already know that the go language has a fully functional test library built in. In this article we will list several strategies that will help you improve your ability to write tests. These strategies are an experience that we have summed up in our previous programming experience to save you time and energy. # # # using the test suite test suite is the most important strategy in this article. Suites It is a test for a common interface with multiple implementations, and in the following example, you will see how I passed the different implementations of the ' Thinger ' interface into the same test function and let them pass the test. "' Gotype Thinger Interface {dothing (input string) (Result, error)}//Suite tests all the functionality that Thingers Sho Uld implementfunc Suite (t *testing. T, Impl Thinger) {res, _: = Impl. Dothing ("thing") if res! = expected {T.fail ("unexpected result")}}//Testone tests the first implementation of THINGERFU NC testone (t *testing. T) {One: = one. Newone () Suite (T, one)}//Testone tests Another implementation of Thingerfunc Testtwo (t *testing. T) {both: =. Newtwo () Suite (T, i)} ' lucky readers may have been exposed to code libraries that use this test strategy. This type of testing is often seen in systems based on plug-in development. Testing for an interface can be used to verify that all of his implementations are satisfied with the behavior required by the interface. The test suite allows us to save a lot of time by not having to repeatedly write tests for a specific version when faced with multiple implementations of an interface. And when you switch the underlying implementation code of the interface, you can guarantee the stability of the program without having to write additional tests. [Here's a complete example] (Https://github.com/segmentio/testdemo). Although this example is the same design. You can think of it as a remote database (Mysql), aMemory Database (SQLite). Another good example is the ' golang.org/x/net/nettest ' package. When we implemented the custom ' net. Conn ' interface, you can use this package (' golang.org/x/net/nettest ') to directly verify that our implementation satisfies the requirements of the interface without having to redesign the test code ourselves. # # # to avoid interface contamination (interface pollution) We can't put aside the interface to talk about Go testing. Interfaces are important in the context of testing because the interface is a powerful tool for testing, so it's important to use it correctly. A package often exports the interface to the user, the user can use the predefined interface implementations in the package, or they can define the implementation for the interface themselves. > The bigger the interface, the weaker the abstraction.> > Rob Pike, Go Proverbs When exporting an interface we need to be very cautious about whether it should be exported. Developers often choose to export this interface in order to enable the user to customize the interface. However, we do not need to do this, you only need to implement the interface behavior in your structure, you can use the structure where the interface is needed. This way, your code package and the user's code package will not have a mandatory dependency relationship. A good example is [errors package] (https://godoc.org/github.com/pkg/errors) (the ' Error ' interface is not exported, but we can define our own implementation in any package). If you do not want to export an interface, you can use [internal/package subtree] (https://golang.org/doc/go1.4#internalpackages) to ensure that the interface is visible only within the package. This way we don't have to worry about whether the user will rely on this interface, or when new requirements arise, it is very flexible to modify the interface. We often use an external dependency to create an interface and use dependency injection to take this external dependency as an implementation of this interface, so that we can exclude external dependencies and only test our own code. This allows the user to encapsulate only a small portion of the code base that they use. For more details, [https://rakyll.org/interface-pollution/] (https://rakyll.org/interface-pollution/) # # # do not export concurrent primitives go Provides a very easy-to-use concurrency primitive, which has also led to its excessive use. Our main concern is the ' channel ' and ' Sync ' package. Sometimes we export a ' channel ' to the user. Another common mistake is the use of ' sync '. The Mutex ' is not set to private when it is a struct field. It's not always bad, but it needs to be considered more comprehensive when it comes to writing tests. When we export the ' channel ' we have brought some unnecessary trouble in the test for the user of this package. Each time you export a ' channel ' is to improve the user's difficulty in testing. In order to write the correct test, the user must consider these: * When the data is sent to completion. * If there is an error when accepting data. * What to do if you need to clean up the used channel in the package. * How to encapsulate the API into an interface so that we don't have to call it directly. Take a look at the following example of reading data in a queue. This library exports a ' channel ' for the user to read his data. "' Gotype Reader struct {...} Func (R *reader) Readchan () <-chan Msg {...} "Now there's a user who uses your library who wants to write a test program. "' Gofunc Testconsumer (t testing. T) {Cons: = &consumer{r:libqueue. Newreader (),} for msg: = Range Cons.r.readchan () {//Test thing.}} The user might think that using dependency injection is a good way. and implemented its own queue in the following way: "' Gofunc Testconsumer (t testing. T, q queueiface) {cons: = &consumer{r:q,} for msg: = Range Cons.r.readchan () {//Test thing.}} There is a potential problem. "' Gofunc Testconsumer (t testing. T, q queueiface) {cons: = &consumer{r:q,} for {select {case msg: = <-cons.r.readchan ()://Test thing. Case E RR: = <-CONS.R. Errchan ()://What caused this again? }}} "Now we do not know how to insert data into this ' channel ' to simulate the actual operation of this code base when it is used, if the library provides a synchronous API interface, then we can call it concurrently, so the test is very simple. "' Gofunc Testconsumer (t testing. T, q queueiface) {cons: = &consumer{r:q,} msg, err: = Cons.r.readmsg ()//Handle err, test thing} ' ' When you have questions, be sure to remember It is easy to use goroutine in a user's package, but once your package is exported it is difficult to remove it. So don't forget to note in the package document that this packet is goroutine and concurrency-safe. Sometimes, we inevitably need to export a ' channel '. To reduce this problem, you can instead export a ' channel ' directly by exporting a read-only ' channel ' (<-chan) ' or ' channel ' (chan<-). # # # using the ' net/http/httptest ' httptest package allows you to test your ' http ' without having to bind the port or start a server. Handle ' function. This speeds up testing and allows these tests to run in parallel at a very small cost. Here is an example of implementing the same test case in 2 different ways. They are not similar but can save you a lot of code and system resources while testing. "' Gofunc testserve (t *testing. T) {//The want to practice typing s: = &http. server{handler:http. Handlerfunc (servehttp),}//Pick port automatically for parallel tests and to avoid conflicts l, err: = Net. Listen ("TCP", ": 0") if err! = Nil {t.fatal (ERR)} defer l.close () go S.serve (L) res, err: = hTtp. Get ("http://" + L.ADDR (). String () + "/?sloths=arecool") if err! = Nil {log. Fatal (Err)} greeting, err: = Ioutil. ReadAll (Res. Body) Res. Body.close () if err! = Nil {log. Fatal (Err)} FMT. Println (String (greeting))}func testservememory (t *testing. T) {//less verbose and more flexible-req: = Httptest. Newrequest ("GET", "Http://example.com/?sloths=arecool", nil) W: = Httptest. Newrecorder () servehttp (W, req) greeting, err: = Ioutil. ReadAll (w.body) if err! = Nil {log. Fatal (Err)} FMT. Println (String (greeting))} "may be the biggest benefit to you in this way is that you can test the function you want to test individually. There are no other factors that affect routing, middleware, and so on. To learn more, see [post by Mark Berger] (http://markjberger.com/testing-web-apps-in-golang/). # # # Use a separate ' _test ' test package In most cases, the tests are written in the same package as the packages and named ' Pkg_test.go '. A separate test package is the separation of the test code and the formal code into different packages. Typically a separate test package is named after the package name + ' _test ' (for example: ' foo ' package is ' foo_test '). In the test package you can import the packages that you need to test with the packages that other tests depend on. This approach makes testing more flexible. We recommend this workaround when a circular reference is encountered in the package. He can prevent you from testing the variable parts of your code. And it allows developers to stand on the user's side of the package and use the package they have developed. If you develop a package that is difficult to use, then he must be hard to test. This test method avoids the test that is prone to change by restricting variable private variables. If your code does not pass this test, then in theThere must be problems in the process. This test method also helps to avoid circular references. Most packages rely on the packages you use in your tests, so it's easy to have a loop-dependent situation. This separate test package is not subject to cyclic dependencies beyond the original package and the hierarchy of the dependent packages. An example is a parser that implements a URL in the ' Net/url ' package, which is used by the ' net/http ' package. However, when the ' Net/url ' package is tested, it is necessary to import the ' net/http ' package, so the ' net/url_test ' package is generated. Now when you use a separate test package, some of the structures or functions in the package cannot be accessed in a separate test package due to the visibility of the package. Most people encounter this problem in time-based testing. For this problem, we can export them in the ' xx_test.go ' file in the package so that we can use them normally. # # # Remember these things above are not silver bullets, but in the process of actual use we need to carefully analyze the problem and find the most suitable solution. Want to learn more about test methods? You can look at these articles: * [Writing Table driven Tests in Go] (https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) by Dave cheney* [the Go programming Language chapter on Testing] (http://www.gopl.io/) or these videos: * [Hashimoto's Advanced Testin G with Go talk from Gophercon] (https://www.youtube.com/watch?v=yszygk1cpEc) * [Andrew Gerrand ' s testing techniques ta LK from] (https://talks.golang.org/2014/testing.slide#1)
via:https://segment.com/blog/5-advanced-testing-techniques-in-go/
Author: Alan Braithwaite Translator: Saberuster proofreading: polaris1119
This article by GCTT original compilation, go language Chinese network honor launches
This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove
1061 Reads