All new code must have a complete set of unit tests. The majority of the following standards stem from the work of Roy Osherove and his book The Art of Unit Testing
When is a test not a unit test?
A test is not a unit test if:
- It talks to the database
- It communicates across the network
- It touches the file system
- It can’t run at the same time as other unit tests
- You need to do special things to your environment (such as editing config files) to run it
Naming convention
Project names
Tests should be physically separated by test type (either unit or integration) to simplify managing where and when tests are executed in the build process and which of the quality gate(s) they govern.
For example:
Company.Services
Company.Services.Tests.Integration
Company.Services.Tests.Unit
File
Each class of behaviour should have tests in a file that aligns with the namespace and class name. The test file class name should end in Tests
to ensure there is no collision with the unit under test.
For example:
Company.Services\Controllers\Controller.cs
Company.Services.Tests.Integration\Controllers\ControllerTests.cs
Company.Services.Tests.Unit\Controllers\ControllerTests.cs
Method
http://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html
The basic naming of a test comprises of three main parts:
[UnitOfWork_StateUnderTest_ExpectedBehavior]
A unit of work is a use case in the system that starts with a public method and ends up with one of three types of results: a return value/exception, a state change to the system which changes its behaviour, or a call to a third party (when we use mocks). so a unit of work can be a small as a method, or as large as a class, or even multiple classes. as long is it all runs in memory, and is fully under our control.
Examples:
– public void Sum_NegativeNumberAs1stParam_ExceptionThrown()
– public void Sum_NegativeNumberAs2ndParam_ExceptionThrown()
– public void Sum_WithSimpleValues_Calculated()
Note:
– When checking if something is not valid use IsNotValid
Example:
– FurtherDetailsComponent_AllElementsAreEmpty_IsNotValid
Stubs vs Mock
Classes built to support testing are often referred to as Mocks. We differentiate between test objects that can affect the outcome of the tests and those that simply support the test.
A stub is used to support the test, producing a predefined for the item under test – for example reading a file from the hard drive such as StubIdentity
.
A mock is class that mimics (sometimes complex) expected behaviour – for example an entity framework DbContext
such as MockDbContext
Stubs and Mocks should be hand coded in all cases and there should be nothing complex enough to require a 3rd party mocking framework.
Test method structure
Each method should group these functional sections, separated by blank lines:
1. Arrange all necessary preconditions and inputs
2. Act on the object or method under test
3. Assert that the expected results have occurred
One Assert per test
Only one assert per test. This is not to say only one Assert
statement – we should only validate one thing, e.g. the state of an entity or the result of a calculation, per test. To put it another way, a test should have one primary reason for failing.
ObjectMother
Objects required as part of the Act portion of the test (usually entities in a certain state required to test the outcome of the unit being tested) should be supplied by factory methods often referred to as ObjectMother
ObjectMother starts with the factory pattern, by delivering prefabricated test-ready objects via a simple method call. It moves beyond the realm of the factory by facilitating the customisation of created objects, providing methods to update the objects during the tests, and if necessary, deleting the object from the database at the completion of the test.
Some reasons to use ObjectMother:
– Reduce code duplication in tests, increasing test maintainability
– Make test objects super-easily accessible, encouraging developers to write more tests
– Every test runs with fresh data.
– Tests always clean up after themselves
Standard ObjectMother
factories for data can be found in Company.CommonTestResources
Method Attributes
We have created some attributes to help document our testing.
PublicForRefactor
For testing it is sometimes necessary to change method modifiers from private or internal to public. In this case, add the PublicForRefactor attribute to document this
AddedToMakeTestable
Add this attribute to any new methods you add to existing classes that are only used for testing purposes. For example, it might make more sense to add a new public method to call a private or internal method, rather then make the method you want to test public. In this case, add this attribute to the new public method.
Untestable System
Some methods / classes access composer data which is currently untestable. Add this method as documentation when you come across these.
When we implement a solution for testing System, this will help find all the relevant code.