.NET Testing Guidelines¶
Test naming rules¶
The name of your test should consist of three parts:
- The name of the method being tested.
- The scenario under which it's being tested.
- The expected behavior when the scenario is invoked.
Bad naming example
Better naming example
Test arrangement¶
Arrange, Act, Assert is a common pattern when unit testing. As the name implies, it consists of three main actions:
- Arrange your objects, creating and setting them up as necessary. This is the first step of a unit test application. Here we will arrange the test, in other words we will do the necessary setup of the test.
- Act on an object. This is the middle step of the unit test application. In this step we will execute the test. In other words we will do the actual unit testing and grab the result obtained from the software under test. Basically we will call the targeted method(s) using the object(s) that we created in the previous step.
- Assert that something is as expected. This is the last step of a unit test. In this step we will check and verify the returned result with expected results.
Why?
- Clearly separates what is being tested from the arrange and assert steps.
- Less chance to intermix assertions with "Act" code.
Bad arrangement example:
C# | |
---|---|
Better arrangement example:
C# | |
---|---|
Write minimally passing tests¶
The input to be used in a unit test should be the simplest possible in order to verify the behavior that you are currently testing.
Why?
- Tests become more resilient to future changes in the codebase.
- Closer to testing behavior over implementation.
Avoid magic strings¶
Naming variables in unit tests is as important, if not more important, than naming variables in production code. Unit tests should not contain magic strings.
Why?
- Prevents the need for the reader of the test to inspect the production code in order to figure out what makes the value special.
- Explicitly shows what you're trying to prove rather than trying to accomplish.
Avoid logic in tests¶
When writing your unit tests avoid manual string concatenation and logical conditions such as if, while, for, switch, etc.
Why?
- Less chance to introduce a bug inside of your tests.
- Focus on the end result, rather than implementation details.
Prefer helper methods to setup and teardown¶
If you require a similar object or state for your tests, prefer a helper method
than leveraging Setup
and Teardown
attributes if they exist.
Why?
- Less confusion when reading the tests since all of the code is visible from within each test.
- Less chance of setting up too much or too little for the given test.
- Less chance of sharing state between tests, which creates unwanted dependencies between them.
Bad test setup example
Important
Avoid as much as possible Setup
and Teardown
methods to preserve the
readability of the test code. The test can be clearer when you don't need
to go back and forth looking for the pieces of code that build up the test
logic as a whole to completely understand it.
Better test setup example
C# | |
---|---|
Avoid multiple asserts¶
When writing your tests, try to only include one Assert per test. Common approaches to using only one assert include:
- Create a separate test for each assert.
- Use parameterized tests.
Why?
- If one Assert fails, the subsequent Asserts will not be evaluated.
- Ensures you are not asserting multiple cases in your tests.
- Gives you the entire picture as to why your tests are failing.
Note
A common exception to this rule is when asserting against an object. In this case, it is generally acceptable to have multiple asserts against each property to ensure the object is in the state that you expect it to be in.
Bad multiple assert usage example
C# | |
---|---|
Better multiple assert usage example
C# | |
---|---|
Validate private methods by unit testing public methods¶
In most cases, it should not be necessary to test a private method. Private methods are an implementation detail. You can think of it this way: private methods never exist in isolation. At some point, there is going to be a public facing method that calls the private method as part of its implementation. What you should care about is the end result of the public method that calls into the private one.
Stub static references¶
One of the principles of a unit test is that it must have full control of the system under test. This can be problematic when production code includes calls to static references (for example, DateTime.Now). To solve these problems, you'll need to introduce a seam into your production code. One approach is to wrap the code that you need to control in an interface and have the production code depend on that interface.