Since I am not a developer I don't usually write unit tests, although I have done so occasionally when a developer asks me to help. Being a good tester, I knew to do things like mock out dependencies and keep my tests idempotent, but through this book I discovered lots of things I didn't know about unit testing.
The author has a background in mathematics, and it shows. He is very systematic in his process of explaining good unit test patterns, and each chapter builds upon the previous one. Here are some of the important things I learned from this book:
- There are two schools of thought about unit testing: the classical school and the London school. In the classical school, unit tests are not always limited to a single class. The tests are more concerned with units of behavior. Dependencies, such as other classes, don't need to be mocked if they aren't shared. In the London school, unit tests are limited to a single class, and calls to other classes are always mocked, even if they are part of the same code base and not shared with any other code.
- Unit tests should always follow this pattern:
- Arrange- where the variables, mocks, and system under test (SUT) are set up
- Act- where something is done to the SUT
- Assert- where we assert that the result is what we expect
- The Act section of the unit test should only have one line of code. If it has more than one line of code, that probably means that we are testing more than one thing at a time.
- A good unit test has the following characteristics:
- It's protected against regressions- it shouldn't break when you change something unrelated in the code
- It's resistant to refactoring- refactoring the code shouldn't break the test
- It provides fast feedback
- It's maintainable- it's easy for someone to look at the test, see what it's supposed to do, and make changes to it when needed
- Mocks and stubs are both types of test doubles: faked dependencies in tests which are used instead of calling the real dependencies in order to keep the tests fast, resilient, and focused only on the code being tested.
- Mocks emulate outgoing interactions, such as putting a message on a service bus
- Stubs emulate incoming interactions, such as receiving data from a database
- Test doubles should only be used with inter-system communications: calls to something outside the code, like a shared database or an email server. For intra-system communications, where a datastore or class is solely owned by the code, the call shouldn't be mocked or stubbed.
The most interesting thing I learned from the book was that it's really hard to write good unit tests when the code is bad. The author provides lots of examples of how to refactor code in order to make tests more robust. These practices also result in better code! Reading through the examples, I now understand how to better organize my code by separating it into two groups: code that makes a decision, such as function that adds two numbers, and code that acts upon a decision, such as writing a sum to a database.
The author doesn't just write about unit tests in this book; he also describes how to write integration tests, and provides examples of writing tests for interacting with databases.
I learned much more than I was expecting to from this book! Software test engineers will find many helpful ideas for all types of automation code in this book. Software developers will not only improve their unit test writing, but also their coding skills. I recommend it to anyone who would like to improve their test automation.