Meszaros G., xUnit Test Patterns (2007)
This book is essential language-agnostic source to make your TDD delivering principal values: maintainable test code, joyful SUT refactorings, and zero product defects.
Contents
Summary
While TDD by Example is more conceptual, practically demonstrating only TDD cycle, this one presents a much wider and very practical body of knowledge a software engineer must not miss.
[INFO] xUnit is the family of unit testing frameworks implementing unified specific approaches and test code patternsThe family includes test frameworks for Java, C#,, JavaScript, PHP to name a few.
Despite seemingly heavy, The book is effectively organized and is a pleasure to absorb using my reviewSkim the diagrams and maps, take 1-2 techniques you like most and start applyingWhen hungry again return for moreWith time the book becomes a good foundational reference on testing.
The review combines "Part INarratives" and "Part IIIPatterns" into a single space as they discuss the same at different level on granularityPart I introduces concepts, Part III provides implementation patternsOn top the review moves the more broad concepts (goals, philosophy and principles) up.
Concepts
Goals of Test Automation
Tests should (p. 84):
- Help us to improve quality and understand the SUT.
- Reduce (and not introduce) risk.
- Be easy to run, easy to write and maintain.
- Require minimal maintenance as the system evolves around them.
Testing Philosophy
(p. 94)
- Write the tests first!
- Tests are examples (of SUT usage)!
- Usually write tests one at a time, but sometimes list all the tests one can think of as skeletons upfront.
- Outside-in development helps clarify which tests are needed for the next layer inward.
- Use primarily State Verification (p. 172, 525) but will resort to Behavior Verification (p. 531) when needed to get good code coverage.
- Perform fixture design on a test-by-test basis.
Principles of Test Automation
(p. 102)
- Write the Tests First
- Design for Testability
- Use the Front Door First
- Communicate Intent
- Don’t Modify the SUT
- Keep Tests Independent
- Isolate the SUT
- Minimize Test Overlap
- Minimize Untestable Code
- Keep Test Logic Out of Production Code
- Verify One Condition per Test
- Test Concerns Separately
- Ensure Commensurate Effort and Responsibility
Practice
Test Smells
The topics are introduced in Chapter 2 (p. 72) and thoroughly discussed in "Part IIThe Test Smells" (p. 246)Here is the brief map of test smells.
[HINT] Add the SVG Navigator extension to your Chrome to comfortably view the diagrams with zoom and pan in the separate tab.
Test Automation Strategy
(p. 112)
Since the book publishing there came a huge number of testing tools (p. 116) starting from unit testing frameworks to E2E and UI testing toolsSo the book here is a little outdated.
xUnit Basics
(p. 138)
The chapter shows the bare minimum of the xUnit framework basics: how frameworks and test (Setup, Exercise, Verify, Tear Down [^1]) are organized and run.
[^1] While this terminology includes what happens within the suite but outside the concrete test (Setup, Tear Down) and inside a concrete test (Exercise, Verify) there is the another convenient terminology - Arrange - Act - Assert (AAA) that describes what happens in a concrete test in slightly different terms.
Test Fixtures
A test fixture: is everything we need to have in place to exercise the SUT, including the tested objects (SUT), the dependencies, exemplary data any other resources needed to run the SUT in isolation from the parent system.
Result Verification
(p. 170)
- Making Tests Self-Checking
- State Verification
- Using Built-in Assertions
- Delta Assertions
- Delta Assertions
- Verifying Behavior
- Expected Behavior Specification
- Reducing Test Code Duplication
- Expected Objects
- Custom Assertions
- Outcome-Describing Verification Method
- Parameterized and Data-Driven Tests
- Avoiding Conditional Test Logic
- Eliminating "if" Statements
- Other Techniques
- Working Backward, Outside-In
- Using Test-Driven Development to Write Test Utility Methods
Test Doubles
Test Doubles isolate the SUT from its dependencies, enable control over indirect inputs (returned values, exceptions) and verification of indirect outputs (side effects, dependency calls).
(p188)
Test Organization
Testing with Databases
(p. 230)
In practice, we want to run at least a representative sample of our customer tests against the database to ensure that the SUT behaves the same way with a database as without one.
Use Database Sandbox when testing with database is unavoidable to isolate developers and testers from production (and each other).
Issues with Testing with Databases
- Persistent Fixtures
- Shared Fixtures
- General Fixtures
Testing without Databases
Layered software architectures (DDD, PoAEE, Ch1, Layering) provide decoupling between software components including DALIn tests the latter can be replaced with a Test Double.
Testing the Database
It includes testing the DAL functionality and database stored procedures if any.
Database Patterns
- Database Sandbox (p. 713)
- Stored Procedure Test (p. 717)
- Table Truncation Teardown (p. 724)
- Transaction Rollback Teardown (p. 731)
A Roadmap to Effective Test Automation
(p. 238)
Test Automation Difficulty
The following common kinds of tests are listed in approximate order of difficulty, from easiest to most difficult:
-
Simple entity objects (Domain Model PoEAA)
- Simple business classes with no dependencies
- Complex business classes with dependencies
-
Stateless service objects
- Individual components via component tests
- The entire business logic layer via Layer Tests (p. 400)
-
Stateful service objects
- Customer tests via a Service Facade CJ2EEP using Subcutaneous Tests (see Layer Test)
- Stateful components via component tests
-
"Hard-to-test" code
- User interface logic exposed via Humble Dialog (see Humble Object, p. 758)
- Database logic
- Multi-threaded software
-
Object-oriented legacy software (software built without any tests)
-
Non-object-oriented legacy software
Roadmap to Highly Maintainable Automated Tests
-
Exercise the happy path code
- Set up a simple pre-test state of the SUT
- Exercise the SUT by calling the method being tested
-
Verify direct outputs of the happy path
- Call Assertion Methods on the SUT’s responses
- Call Assertion Methods on the post-test state
-
Verify alternative paths
- Vary the SUT method arguments
- Vary the pre-test state of the SUT
- Control indirect inputs of the SUT via a Test Stub (p. 592)
-
Verify indirect output behavior
- Use Mock Objects (p. 607) or Test Spies (p. 601) to intercept and verify outgoing method calls
-
Optimize test execution and maintainability
- Make the tests run faster
- Make the tests easy to understand and maintain
- Design the SUT for testability
- Reduce the risk of missed bugs
Remaining Patterns
-
Design-for-Testability Patterns
- Dependency Injection (741)
- Dependency Lookup (749)
- Humble Object (758)
- Test Hook (772)
-
Value Patterns
- Literal Value (777)
- Derived Value (781)
- Generated Value (786)
- Dummy Object (791)