wizzdev image

Why should we write unit tests?

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:
  1. Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
  2. Each unit should only talk to its friends; don’t talk to strangers.
  3. Only talk to your immediate friends.

 

 

In TDD – TestDriven 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!

Recent entries

Woman lifting a reusable shipping box

How to monetize an IoT Device? – Limeloop

Electric scooter business model   Today we will take a closer look at one of the most popular monetization models. The electric scooter business model. Weā€™re big fans of this model

Woman lifting a reusable shipping box

How to monetize an IoT Device? – Limeloop

Electric scooter business model   Today we will take a closer look at one of the most popular monetization models. The electric scooter business model. Weā€™re big fans of this model

We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.