Reputation: 300
I always thought that good unit test, are test that are independent. By 'independent' i mean that when function 'A' uses 'B', and we test function 'A', we mock/stub out 'B' in case when 'B' does not work correctly it will not fail 'A'.
But when we check sources of golang packages that principle is not respected.
For example lets check url.go and url_test.go in url packages:
url.go:
func parseQuery(m Values, query string) (err error) {
for query != "" {
...
key, err1 := QueryUnescape(key)
...
url_test.go:
func TestParseQuery(t *testing.T) {
for i, test := range parseTests {
form, err := ParseQuery(test.query)
...
As we can see parseQuery
use QueryUnescape
. In test QueryUnescape
is not stubbed in any way. So if QueryUnescape
will work wrong, parseQuery
test will fail.
So authors of package not always meet requirement of 'independent' unit test. What was the reason to not bother about this principle in this case, is there some rule that allow programmer of accepting this form of unit tests?
After writing independent test in python, I'am little confused about balancing between writing perfect test(which much affects in golang about design of code) and results.
Upvotes: 7
Views: 4897
Reputation: 5985
Your question is really about defining the scope of a "unit" when "unit testing", which can be tricky. A "unit" does not necessarily mean a function, or a struct. It might mean a few functions/structs working together. But it most certainly means that you will not cross process boundaries (like using the real file system or going over a network, hitting a database, etc...). The boundaries are tested with integration tests or simulated using test doubles (mocks, stubs, spies, etc...).
I'm the author of GoConvey, a testing tool that build on top of the built-in go "testing" package. GoConvey has a comprehensive suite of unit tests. In the tests for the core of the project I don't write tests that invoke every single exported and non-exported function. I wrote the tests with the public-facing API in mind (the exported functions/structs). The implementation of that API is not the concern of my unit tests but the outcome is. You can see what I mean just by reading a few tests. I only call a few public-facing methods and assert that the result is correct. There are actually lots of structs and functions being invoked but the test coverage in that package is very high (currently 92.8%--not as high as I'd like but it's pretty good).
In the case of the convey
package within GoConvey the unit is the entire package and its components. In the beginning I remember the tests being more granular, which allowed me to get into the red-green-refactor cycle more quickly. As the code grew, those smaller-scoped tests were superseded by broader tests that seemed more appropriate. So the suite will evolve with the production code. You'll feel a lot of friction and inertia if every single method is tested directly as you won't be able to change anything without breaking a bunch of tests.
Here's another interesting summary about how to discern the scope of a unit test:
http://blog.8thlight.com/uncle-bob/2014/01/27/TheChickenOrTheRoad.html
Upvotes: 5