- Unit tests, on the other hand, take milliseconds
- Don't depend on external services
- Allow reproducible results
Arrange, Act, Assert is a common pattern when unit testing. As the name implies, it consists of three main actions:
- Arrange your objects, create and set them up as necessary. Example
- Act on an object.
- Assert that something is as expected. Readability is one of the most important aspects when writing a test. Separating each of these actions within the test clearly highlight the dependencies required to call your code, how your code is being called, and what you're trying to assert. While it might be possible to combine some steps and reduce the size of your test, the primary goal is to make the test as readable as possible.
- Provides confidence that your new code doesn't break existing functionality Example
- Unit testing helps identify bugs and issues in the code at an early stage, making it easier to fix them. Example
- When a test fails, it is much easier to locate the problem since the tests are focused on specific units of code.
- Tests provide functionality documentation Example
- Allows to easily run certain scenarios and check what happens if we pass certain input
- Well named test explain what can be done with the code and ensure it actually works
- When code is tightly coupled, it can be difficult to unit test. Without creating unit tests for the code that you're writing, coupling might be less apparent. Example
- The principle of developers eating their own dog food. Be the customer of your own code.
- Fast: It isn't uncommon for mature projects to have thousands of unit tests. Unit tests should take little time to run. Milliseconds.
- Isolated: Unit tests are standalone, can be run in isolation, and have no dependencies on any outside factors such as a file system or database.
- Repeatable: Running a unit test should be consistent with its results, that is, it always returns the same result if you don't change anything in between runs.
- Self-Checking: The test should be able to automatically detect if it passed or failed without any human interaction.
- Timely: A unit test shouldn't take a disproportionately long time to write compared to the code being tested. If you find testing the code taking a large amount of time compared to writing the code, consider a design that is more testable.
- Code coverage is often associated with a higher quality of code. However, the measurement itself can't determine the quality of code. Good practice is to maintain 85%-90% code coverage.
- The main thing to remember about mocks versus stubs is that mocks are just like stubs, but you assert against the mock object, whereas you don't assert against a stub.
- Dependencies make the tests slow and brittle
- Avoid these dependencies by:
- Using Dependency Injection
- Following the Explicit Dependencies Principle (i.e. all dependencies should be declared explicitly)
- Following The Dependency Inversion Principle (i.e. high level modules should not depend on low level modules, but both should depend on abstractions)
- The name of the method being tested Example
- The scenario under which it's being tested
- The expected behavior when the scenario is invoked
- Private methods are an implementation detail and never exist in isolation.
- At some point, there's 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.
- In some cases we have tightly coupled legacy code in public method which makes it hard to unit test it directly. In these cases we might want to unit test
internal
methods separately. Example
- For example, if we need to use
DateTime.Now
, instead of directly calling it in the code, create an instance of IDateTimeProvider.