‘Ice Cream Cones are bad’
At the moment a lot of code in your company has acceptance tests only. Perhaps automated acceptance tests, perhaps lots of manual tests. This is because it is hard to write code that is easy to test, and most of the code has not been written that way
‘Pyramids are good’
This policy is opinionated and demands that the code has a lot of Unit Tests. Unit tests are regarded as valuable because they force the code to be written in a style that is easy to be unit tested, which results in (generally) higher quality code. Generally these tests run quicker, are
Generally speaking the lower in the pyramid that a test is placed, the easier it is to write, check, debug and fix when it fails.
How do we invert the pyramid
The following are just examples. Each test that you write: it is important to work out how you push it down the pyramid. If it seems to hard to write it as anything other than an acceptance test: one that requires working other machines… then consult your team lead or other colleagues for advice, or seek training.
Acceptable Acceptance Tests
The only tests we should be running in the acceptance environment are Exploratory tests, Smoke tests and perhaps Performance Tests if the project decides this is the best place to do them. We should not be running and functional checks that are checking the behavior of the api or website
Change Acceptance Tests to System Tests
Any test that we currently run in the acceptance environment we should work out if we can run without other systems
- Performance tests. If we have mocks of the other systems, and even better if we have those mocks having the same timing characteristics, then we can run the peformance tests on our laptops. This allows much faster investigation of issues when there are a problem. Of course they can still run in the pipeline, but don’t require the other systems to be up: decoupling us from those systems
- Functional tests. We should be using mocks of the other systems (using Consumer Driven Contract Tests to make this ‘safe’).
Acceptable System tests
We should not be checking behavior at this level.
- Routing Tests can check that the routing directs the urls you expect to work to the correct endpoint. These have mocks implemnting all the behavior: all we are doing is checking that the routing works.
Changing System tests to Interaction Tests
Most of our ‘complicated tests’ involving more than one piece of software should be interaction tests and no higher.
- The behavior of end points. Can be tested by calling the end points with a mock Http Request and evaluating the Http Response. Most probably the end points just delegate to some sort of service, and these tests should only assert that the end points call the service, parse the HttpRequest correctly, give a suitable HttpResponse and handle errors from the service correctly
- The behavior of ‘businss logic’. This should be pushed down the pyramid until it is just the concatentation of some functions, and those functions are well tested with unit tests. If you are testing your businss logic by calling an endpoint and making assertion on the result, this is wrong and not in line with this policy. Change it!
Acceptable Interaction Tests
This should not duplicate the code in the method they call. They should mostly use mocks and simply assert that the other methods were called. The main purpose of these tests is to check the ‘wiring’. i.e. that the correct values are taken from one place and psased to another. If you use strong typing and things like ‘tiny types’ many of these tests become almost redundant as the type system does the same work as the test
Changing interaction tests to Unit Tests
If your interaction test is testing behavior then it should be re-written. Unit tests are the main place that we test behavior. Ideally your methods/functions/procedures are either simple pieces of code that only interact with primitives or vaue objects, or they are only about wiring such things together.
Unit tests
Most of our tests should be unit tests. A unit test is a test on a piece of code that only interacts with primitives and value objects. The easiest code to unit test is a pure function: this is a piece of code that uses values from the inputs and produces an output without any side effects.
Links
- http://blog.thecodewhisperer.com/permalink/integrated-tests-are-a-scam
- https://blogs.agilefaqs.com/2011/02/01/inverting-the-testing-pyramid/
Counter arguments to inverting the pyramid
In my view the counter arguments are flawed because they miss the essence of unit tests and the rebuttal of them is that ‘unit tests are not about testing’. They are a design technique. They apply pressure to the code and that forces the code to adhere to good coding practices. This is captured in the link above