Testing
Desired properties
fast: the sooner we get feedback about what we just added or changed, the better.
deterministic: if a test becomes "flaky", how will we find out if it started failing for a good reason?
predictive: the tests should provide confidence that everything is working as expected, and if all are green then it should be fine to release and deploy the sytem.
sensitive to changes in behaviour: our tests should focus on the behaviour the end-user sees, and ensure it is correct and stable. If the behaviour changes, we have to adapt the tests to reflect the new behaviour.
insensitive to changes in structure: as long as we do not change the behaviour of the component, the tests should run without adapting them. If your tests depend on "how" the application is solving the business problem rather than if it "is" solving the business problem, you lose the possibility of refactoring (changing the structure of your software without affecting the behaviour).
cheap to write, read and maintain: tests should be helping us, not slow us down. If a test fails, we should immediately see what it is about, find the production code it is testing, and fix the problem. Changing them also needs to be easy and fast.
focused: each test should be about one thing, and it should be visible about which one. In the test, we should see only the details important to a certain desired behaviour, everything else should be hidden and abstracted away.
Basic structure of a test
Arrange:
initialises the component under test
sets up some desired state on which the test will be based
creates some test data
Act: triggers the behaviour that should be tested
Assert: verifies if the action triggered in the Act phase produced the expected outcomes
Annihilate: clean up resources after the test was executed
Make it a specification
It's possible to write tests in a way that even non-developers, like the stakeholders of our system, would be able to read and understand them.
One option it to follow the behaviour-driven development (BDD) methodology:
rename the test to describe the concrete part of the behaviour in a given situation, e.g.
should_calculate_subtotal_and_total_amount_if_two_items_with_different_prices_and_quantities_are_added
change the arrange/act/assert phases in the test to be given/when/then sections
Why You Should Write Automated Tests
Prevent regressions: if every (relevant) functionality of the application is verified by an automated test, you can be sure of any change you do. The moment you break something by accident, at least one test will turn red and inform you about the problem.
Verifying correctness: when working on some piece of code, the most common question is "is it working as expected?" - correctness can either be formally verified (extremely costly and therefore very rarely used) or it can be tested.
Risk-free refactoring: without tests, a potential refactoring is often skipped because it would take too much time.
Documentation: the tests show the expected behaviour of the system and its pieces. What is good about this documentation is that it cannot get outdated, as any misalignment between the tests and the production code will be fixed.
Resources
Articles
Anatomy of a Good Test - Torsten Mandry, Jacek Bilski
Why You Should Write Automated Tests - Torsten Mandry, Jacek Bilski
Books
Videos
TDD, where did it all go wrong - Ian Cooper
Websites
How They Test (GitHub) - A curated collection of publicly available resources on how software companies around the world test their software systems and build their quality culture
Last updated