Test- driven development is an extraordinary method to keep the quality of your code high, while protecting yourself from regression and proving to yourself as well as other people that your code does what it should. Go has robust in-built testing library. If you are thinking to build or software with Go, it will be better to know the Go testing techniques so as to develop a perfect software. So here we’ll discuss strategies to level up Go testing that will save your time and effort to maintain the code.
Suite testing is a process of developing a test against common interface that can be used against various implementations of that interface. Here you will see how you can pass in multiple different Thinger implementations and have them run against the same tests.
type Thinger interface { DoThing(input string) (Result, error) } // Suite tests all the functionality that Thingers should implement func Suite(t *testing.T, impl Thinger) { res, _ := impl.DoThing("thing") if res != expected { t.Fail("unexpected result") } }
// TestOne tests the first implementation of Thinger func TestOne(t *testing.T) { one := one.NewOne() Suite(t, one) }
// TestOne tests another implementation of Thinger func TestTwo(t *testing.T) { two := two.NewTwo() Suite(t, two) }
Tests that are written against the interface are usable by all implementations of interface to determine if the behavior requirements are met. This strategy will save time to solve the P versus NP problem. While swapping two underlying systems, you don’t need to write extra tests and it won’t break your app. Implicitly it requires that you create an interface defining the surface area of that you’re testing. By the use of dependency injection, you set up the suite from your package passing in the implementation for package.
Another great example of this in the standard library is golang.org/x/net/nettest package. It provides the means to verify a net.Conn satisfies its interface.
Go provides easy to use concurrency primitives that can sometimes causes their overuse. Mainly concerned is about channels and sync package. Some of the time, it is enticing to export a channel from your package for consumers to use. Also, it is common mistake to embed sync.Mutex without making it private. Likewise with anything, this isn’t in every case bad however it does difficulties when testing your program. When you export channels, you expose the consumer of the package to extra complexity they shouldn’t care about. When the channel is exported from a package, you open up challenges in testing for one consuming that channel. So as to test properly, the consumer needs to know about- when data is finished being sent on the channel, whether there are any errors receiving the data or not, After completion, how does the package clean up channel etc.
Consider an example of reading a queue. Here’s an example library that reads from the queue and exposes a channel for the consumer to read from. User of library wants to implement a test for their consumer: User might decide that DI(dependency injection) is a good idea for this and write messages with channel.
However, what about errors?
How to generate events to actually write into this mock that replicate the behavior of actual library you’re using? If library wrote synchronous API, then you could add this concurrency in our client code and it becomes easy to test.
Always remember that, it is easy to add concurrency in consuming package and difficult to remove once exported from a library. Don’t forget to mention in package documentation whether or not a struct/package is safe for concurrent access by various goroutines. Some of the times, it is still necessary to export channel through accessors rather than directly and force them to be ready-only or write only channels in declaration.
Interfaces are important for testing as they are the most powerful tool in test arsenal, hence it is important to use them appropriately. Packages export an interface for consumers to use that turns leads to- consumers implementing their own mock of package implementation or the package exporting own mock. It will be better to consider interfaces before exporting. Mostly Programmers are tempted to export interfaces to mock out their behavior. Rather than, document that interfaces your structs satisfy just like you don’t create a hard dependency between consumer package and your own. Error package is a good example of this. When you have interface in program that you don’t want to export can use an internal/ package subtree to keep it scoped to the package. With this, we remove the concern that other consumers might depend on it and so can be flexible in the evolution of interfaces as new needs present themselves. Generally we create interfaces around external dependencies and use dependency injection to run tests locally. With this, consumer can implement small interfaces of their own, just wrapping the consumed surface of library for their own testing.
Know more at- https://solaceinfotech.com/blog/top-5-advanced-go-testing-techniques/