Open Laoujin opened 1 hour ago
Also to be added:
This is a bit the discussion: should UnitTest be for one class and mock everything else? Or should you mock only things that are slow / tricky?
Other things that are missing:
And well, maybe just throw everything in ChatGPT and see if it comes up with anything it thinks is still missing?
Code Coverage vs Branch Coverage
Code Coverage / Statement Coverage: The percentage of source code that is executed by the tests. It is useful to identify which parts of the code are untested and might contain undetected bugs. It is easy to calculate and is one of the metrics of SonarQube.
Branch Coverage / Decision Coverage: To have complete “Branch Coverage” all possible routes through the code need to be tested. This reveals bugs that 100% Code Coverage alone cannot and ensures that all possible scenarios are covered by UnitTests.
It is therefor important to create all the UnitTests that are needed to get to complete Branch Coverage.
Example
Given the following method:
Example 2
To attain a good Branch Coverage it’s best to have a test that covers these cases:
When cond1 is already false, you could still cover all different cases for cond2/3 but, it doesn’t really matter since they are not being evaluated. Because these are white box tests, this is OK. If this is somewhat tricky/important code, subject to change, or in a part of the code in which bugs have been previously encountered, you may still want to write a test for all cases.
State Testing vs Behavior Testing
State Testing:
Given a certain input, check that the correct output is produced. Examples:
Behavior Testing:
Given a certain input, check that certain object interactions did or did not happen. Typically used when dealing with external systems. Examples:
DbContext.Save
method is calledEquivalence Partitioning and Boundary Value Analysis
Simple case: we expect an EnergyAmount to be between 0 and 100.
Equivalence Partitioning:
With the following 3 tests:
We have partitioned the data into 3 sections. It is not needed to create additional low/correct/high values since they all belong to the same 3 partitions we already covered.
Boundary Value Analysis:
There are only two hard problems in Computer Science: cache invalidation, naming things, and off-by-one errors.
This is where Boundary Value Analysis comes into play. Instead of just checking our semi-random values for the Equivalence Partitioning, we might add (or replace) them with boundary values to ensure that we haven’t made any accidental off-by-one errors. We could add the following test cases:
Edge Case Testing:
This could be considered a case of Boundary Value Analysis. In the EnergyAmount example, also create a test using NULL as input. You could also define the behavior of PositiveInfinity, NegativeInfinity and NaN EnergyAmounts – if this makes sense for your UseCase!