If there is a possibility of several things going wrong, the one that will cause the most damage will be the one to go wrong. Corollary: If there is a worse time for something to go wrong, it will happen then.
– one of Murphyās general laws.
Every organisation that tries to make a breakthrough, be it landing a human on the moon or designing and producing a new handheld smart device encounters issues, bugs, errors throughout the project lifecycle.. Bugs are always costly and need to be taken into consideration while budgeting or estimating project timelines. The further into the project lifecycle we go, the more expensive the bugs become.
Below graph pictures how costly, fixing bugs is in each phase of development.
As you can see,Ā bugs in the phase of maintenance which in other words means production, are the most expensive.
Here are some examples of costly (not only in financial terms) production issues.
- Explosion of Ariane 5 rocket (1996) – internal overflow – 10 year budget of 7 bln $
- Therac-25 (1986) – X ray mode with no filter – patient deathsĀ
- Samsung Note S7 – battery explosions and recalls – 17 bln $
- Toyota brake blocking issue – 3 bln $
- Gitlab – whipped out database
In order to mitigate the danger of unnoticed high severity bugs, unit tests should be written and used.
Characteristics of well written unit tests are::
- Fast – unit tests should take under few seconds
- Independent – test shouldnāt depend on each other
- Repeatable – each run gives the same result
- Self-validating – each test uses explicit assertions
- Timely – in accordance to TDD
This graph shows the most common proportions between various test types in a project.
We can clearly see that unit tests are the most commonly executed.
Unit tests are typically arranged in three phases.
- Arrange – create items for test
- Act – we call the method under test and get actual value.
- Assert – we check expected and actual value.
If the expected value and actual value are equal, the test is passed.
Below you can see a good example of a unit test.
Now let’s discuss the most common mistakes in unit test structure.
Most common mistakes are:
- No clear Given/When/Then parts
- Only When+Then parts
- Section duplication
Also naming the tests is important.
Here are some examples:
- MethodName_ExpectedBehavior_StateUnderTest: – e.g. withdrawMoney_ThrowsException_IfAccountIsInvalid
- Should_ExpectedBehavior_When_StateUnderTest – e.g. Should_FailToWithdrawMoney_ForInvalidAccount
- When_StateUnderTest_Expect_ExpectedBehavior – e.g. When_InvalidAccount_Expect_WithdrawMoneyToFail
- Given_Preconditions_When_StateUnderTest_Then_ExpectedBehavior -e.g. Given_UserIsAuthenticated_When_InvalidAccountNumberIsUsedToWithdrawMoney_Then_TransactionsWillFail
Test doubles – Stubs and Mocks.
Stubs are a type of fake that fake behaviour and can return a predefined expected value. A mock is a type of fake that we can monitor to make sure that a certain method was called an expected amount of times or with an expected set of parameters.
There are numerous forms of test doubles, consisting of mocks and stubs, each serving specific purposes. Mocks are used to affirm interactions and behaviours, while stubs provide predefined responses to method calls without imposing strict conduct verification.Ā
According to Martin Fowler’s article:
- Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
- Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production
- Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.
- Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
- Mocks objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
Let us take a closer look at mocking good practices.
- āDonāt mock what you donāt ownā – e.g. external libraries (after upgrade you willĀ have to rewrite all your mocks)
- negative case verification
- loose specification – you avoid āconcretingāĀ logicĀ
- mocks should not return mocks – āEvery time a mock returns a mock a fairy diesā (from Mockito docs)
- Demether law- as proposed by Ian Holland at Northeastern University in 1987, and the following three recommendations serve as a succinct summary:
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
- Each unit should only talk to its friends; don’t talk to strangers.
- Only talk to your immediate friends.
In TDD – Test–Driven Development technique for building software the development is guided by writing tests.
TDD can be described by the following diagram:
3 Phases of TDD are:
1. Create precise tests: Developers exact unit tests to verify the functionality of specific features. They ensure that the test compiles so that it can execute. The test is bound to fail but this is a meaningful failure as developers create compact tests based on their assumptions of how the feature will behave.
2. Correcting the Code: Once a test fails, developers make the minimal changes required to update the code to run successfully when re-executed.
3. Refactor the Code: Once the test runs successfully, check for redundancy or any possible code optimizations to enhance overall performance. Ensure that refactoring does not affect the external behaviour of the program.
Mutation testing
In order to introduce changes to the code we can use mutation testing. Then we can run unit tests against the changed code. It is expected that the unit tests will fail. If they don’t fail, it might indicate that the tests do not sufficiently cover the code.
Hereās an example of mutation testing:
-
production code modification in order to check tests:
- changing from < to <=
- changing from i++ to i–Ā
- returning a null pointer instead of an object
- if not at least one test is āredā then the mutation is not covered
-
example tools
- Python – mutmut
- C++ – mutate++
We hope that these tips would help you create better and more relaible code!