Tests expose problems early on
Tests that are written after the implementation have a certain odor about them. They are often too juvenile, and too accommodating, especially if the test writer was part of the implementation development. These types of tests will often perform some rudimentary checks, just around the surface, but won’t deep-dive into the functionality of the code. This is actually a human response. You have worked hard on the implementation and you are almost ‘afraid’ to change it, and these types of tests are telling you to not change too much. Because of this, any problems that the tests discover remain in the code base until a bug report comes in.
It is a good idea to write tests for each other because this tests the expected outcome with what the implementation provides. This is what tests should do: verify the actual implementation with the expected result, almost without looking at the backend code. The consequence, though, is that if tests are not considered a ‘nice to have’ or an ‘afterthought’ but an added value, new ideas on how the solution should be implemented will be proposed. And we should give it that freedom.
Tests show architectural opportunities
Tests change implementation: that should be a fact. Tests expose problems but also provide solutions. Duplication is one of the first things that tests notice. When a certain test assertion has to be re-used, for example, it could mean that the system you are trying to test has too many dependencies, because the only way to verify is to duplicate. Too many dependencies can also be spotted when the test arrangement is too big for what you are trying to test. Anything that feels ‘off’ when setting up your test, or anything that ‘you should not care about’ and is included in your test should be a red flag.
That is another reason why tests as an afterthought do not work. Thorough testing after the implementation means changing the implementation. This is not something you can ‘add-on’. Even the smallest test can lead to a big architectural change. If we embrace testing, we should embrace change.
Tests give direction during implementation
Change is something natural in a software project, and tests are no different. The implementation is not ‘done’ when there are no tests: it is just beginning. Because of this impact, it is natural that tests should be a companion during the very first lines of code implementation. A test-first mindset or test-driven development talks about this way of working, where the tests guides where the implementation should go. The idea is that the test will tell you what is missing by letting the test fail and that the implementation should only make the most minimal change to let the test succeed. It is actually fairly simple: instead of starting with the implementation, start with the test.
Even integration tests could be organically grown from a test-driven approach. When a new feature is added, write a failing integration test that should prove the feature. Then write unit tests to drive the smaller parts of the feature until the integration test succeeds. This way of working feels very natural, very safe, and has the psychological benefit of a green test when you are done – when you are really done.
Conclusion
The argument, “this is only used for tests” is natural when the project is test-driven, as you start with testing before you go over the implementation. I like to call test-driven development ‘proof-driven’ development, as the tests will continuously prove what part of the implementation works and what does not. The statement, “if it is not tested, it does not work” speaks of a new mindset, a way of seeing your project differently. You think and talk in tests. When a new feature request comes in, the first question you ask yourself should be, “How should we test this?”.
Thanks for reading,
Stijn
Subscribe to our RSS feed