Testing guidelines¶
Test code
is as important as production code
. The product's test must be
carefully selected, designed, built and maintained. They live with the product
along it lifetime.
Basic concepts¶
The need for good tests¶
The main reason to invest in having the same acceptance criteria for the test code as for the production code, is the fact that without health tests we might not be able to keep our code maintainable, we wouldn't take the risks that good and deep refactors demand, you would do it with fear. Bad test code prevent us to make the right changes to the production code.
Adding test coverage to our code reduces our component's coupling. Coupled components are hard (or even impossible) to test, and coupling is often difficult to see when coding, you realize about it when writing unit test that involves your code.
Types of tests¶
The tests can be classified in Unit tests, Integration tests and Load tests.
-
Unit tests: exercises individual software components or methods under the control of the developer. They do not test infrastructure concerns.
-
Integration tests: it exercises two or more software components' ability to function together. These tests operate on a broader spectrum of the system under test, whereas unit tests focus on individual components. Often, integration tests do include infrastructure concerns.
-
Load tests: A load test aims to determine whether or not a system can handle a specified load.
How to write clean tests¶
As we said before, test code should be production code compliant, it is under the same quality restrictions than production code (readability, density, clarity, etc.) . Aside from this, the following aspects must be kept in mind as well, as we can see here below:
One assert per test¶
Having just an assert per test make the test code cleaner. This is a general
rule, there are some cases in which accomplish it would bring some downsides.
For example if we want to make an assertion over a string value, and we check
for its nullability state first, and then compare its value with an expected
one. Such scenarios are completely accepted and can't considered a violation of
this general rule, this lead us rather to the rule of one concept per test
.
One concept per test¶
The rationale here is that we want to validate a single concept in each test. This way we can clearly identify the boundaries of the test, why it makes sense for it not to be removed and simplify its maintainability.
F.I.R.S.T¶
Clean tests
also follow these five rules:
- Fast: tests must run fast. When the tests run slow you don't want to execute them all frequently.
- Independent: tests must not depend on each other. Otherwise the diagnostics based on test results would become hard to make.
- Repeatable: Test should be able to run anywhere, no matter what the environment is, may be QA, Staging, or even our laptop.
- Self-validating: tests must produce a boolean result indicating its result status. We don't want to depend on further analysis to determine if it succeeded or not.
- Timely: ideally the tests must be written before the code (TDD alike), or be contemporary with the coding tasks. Write test some time after you wrote the code you might found your code hard to test or event not testable.
Fake / Mocks / Stubs¶
The term mock is unfortunately often misused when talking about testing. The following points define the most common types of fakes when writing unit tests:
-
Fake: A fake is a generic term that can be used to describe either a stub or a mock object. Whether it's a stub or a mock depends on the context in which it's used. So in other words, a fake can be a stub or a mock.
-
Mock: A mock object is a fake object in the system that decides whether or not a unit test has passed or failed, by allowing us to assert whether or not a given method was called with some parameters, if it was called 3 or 4 times, etc. A mock starts out as a Fake until it's asserted against.
-
Stub: A stub is a controllable replacement for an existing dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly. This means we can define a stub and configure it in such a way that allows us to react and generate different testing scenarios, by defining things like 'when this method is called with these parameters, return this other thing'. By default, a fake starts out as a stub.
For implementation details, read the .NET Testing Guidelines and the Angular Testing Guidelines