Unit Testing
Unit test: Test(s) written for testing a unit of code in isolation
Verify that a known, fixed input produces a known, fixed output
- Testing a unit of work
- An unit can be a method
- Red-green testing
- System under test (SUT)
- Code coverage
- One unit test runs independently of any other unit test
- Tests can (and do) run in any order
- Tests can (and do) run in parallel in multiple threads
- External dependencies are managed with Test Doubles (such as Mocks/Stubs/Fakes)
- A single unit test should run in a second or less
- Unit test usually are partitioned into: Arrange, Act, Assert
Unit tests should not be the only means to test an application, as:
- They are further away from how users interact with software
- More likely to break with refactoring
Characteristics of a good unit test
Fast: It is not uncommon for mature projects to have thousands of unit tests. Unit tests should take very little time to run (in milliseconds)
Isolated: Unit tests are stand-alone, can be run in isolation, and have no dependencies on any outside factors such as a file system or database
- Mock dependencies
- Test internals
Repeatable: Running a unit test should be consistent with its results, that is, it always returns the same result if you do not 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 should not 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
Unit testing tips:
- Do not unit test everything
- Use data that is close to production data
- Cover edge cases
- Write tests that are independent of each other
Unit Test Structure
Setup: Put the Unit Under Test (UUT) or the overall test system in the state needed to run the test
Execution: Trigger/drive the UUT to perform the target behaviour and capture all output, such as return values and output parameters. This step is usually very simple
Validation: Ensure the results of the test are correct. These results may include explicit outputs captured during execution or state changes in the UUT
Clean-up: Restore UUT or the overall test system to the pre-test state. This restoration permits another test to execute immediately after this one. In some cases in order to preserve the information for possible test failure analysis the clean-up should be starting the test just before the test's setup run
Arranging your tests: 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
Act on an object
Assert that something is as expected
Naming conventions
- Roy Osherove's naming strategy for unit tests:
[UnitOfWork_StateUnderTest_ExpectedBehaviour]- E.g.
Divide_PositiveNumbers_ReturnsPositiveQuotient
- E.g.
Code Coverage
A high code coverage percentage is often associated with a higher quality of code
Tools:
Coverlet: Code coverage framework
ReportGenerator: Report generator
Test Doubles
A Test Double is an object that can stand-in for a real object in a test. Used instead of External Dependencies
- DB, Web, API, Library, Network etc...
- If tests fail then it must due the unit of code that is being tested not due to its dependencies
- Easy to simulate various scenarios
Types of Test Doubles:
Dummy: A dummy is the simplest form of a test double. It facilitates linker time substitution by providing a default return value where required
- It is used as a placeholder when an argument needs to be filled in
- Objects that the SUT (System Under Test) depends but they are never used
- Not relevant to the test scope
Stub: Generates predefined outputs. It provides fake data to the SUT
- 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
- Programmed Stub to return a Success, Failure or Exception
- A stub adds simplistic logic to a dummy, providing different outputs
Spy: It records information about how the class is being used
Mock: Mocks replace external interface
- A mock object is a fake object in the system that decides whether or not a unit test has passed or failed. A mock starts out as a Fake until it's asserted against
- They have the same signature of the function
- We can check if the function is being called or not
- How many times is the function being called?
- What Parameters are passed when it is called?
- It defines an expectation of how it will be used. It will cause a failure if the expectation isn't met
- Right call, Right Number of times with Right set of Parameter and in the Right order
Fake: Almost working implementation. It is an actual implementation of the contract but is unsuitable for production
- Connect to a local HTTP server
- Instead of actually going to the internet it connects to a local (limited) implementation
- Check the behaviour with respect to the actual data it receives from the server
- A Fake can be a stub or a mock object
Tools
- Test Runners:
- Find tests
- Run tests
- Determine whether tests pass or fail
- Code becomes DRY
- Test results
- Predictable
- CI Integration
- Auto Run
- AVA
