Discussing some heuristics for system architecture and OO design principles that may help when applying TDD by improving modularity and testability of code.
Not all objects are equal - strategies for designing testable code
1. Not all objects are equal
Strategies for designing testable code
2. The two cycles of TDD
The small cycle
red -> green -> refactor
The large cycle
RED -> {r -> g -> r} ->...-> {r -> g -> r} -> GREEN ->
REFACTOR
3. Test types
Unit Tests
fast
contained “in memory”
single object or small cluster of objects
runs in single process and thread
Acceptance Tests
tests all the domain logic objects in a cluster.
faking / mocking the technical boundaries
4. Test Types - cont...
Integration Tests
Slow
Checks the integration with 3rd party libraries
May involve handling threading and asynchrony
E2E Tests
Black-box tests for the service as a whole
The slowest
Usually Involves handling UI, asynchrony, file system,
etc.
5. Our motivation
We want very high test coverage
We want a stable build with the fastest build
time we can achieve
We want to balance these two conflicting
concerns
8. Architectural Style - cont...
OO Shell over a Functional core
Leave I/O and other side effects on the adapter level
Construct a functional core out of the inputs
Execute purely functional computations using the core
Delegate computation results into other adapters
9. Design style - Object Categories
Not all objects are equal
We can split them into 4 categories :
Internals
Created inside your object using ‘new’ - lightweight
Usually immutable Value Objects or ‘pure’ functions
Represent a concept from the problem domain
contains the computation related to the data that they
encapsulate
10. Design Style - Object Categories
Collaborators
Passed into your object usually through the
constructor
Usually implement an interface which represents a
role in your domain
Usually represents an Architectural Boundary
(Database, SpringMVC, REST, Messaging, etc.)
Can be replaced with fakes for testing
11. Design Style - Object Categories
Modifications
Implementations of the Strategy or State design
pattern
Change the behaviour of an object at runtime
Can be replaced with mocks for testing
Make the object’s state mutable so thread safety is a
concern
12. Design Style - Object Categories
Notifications
Implementations of the Observer design pattern
Usually used to notify observers when your object’s
internal state has changed or other interesting event
has occurred.
Can be replaced with mocks for testing
Subscribers should be independent and orthogonal to
each other
13. Refactoring to simplify testing
Introduce domain immutable value objects
build them inside the adapters
eliminate “Train Wrecks” - move more and more
computational logic into the value objects
value objects are simple to test!
read Domain Driven Design by Eric Evans
14. Refactoring to simplify testing
separate side effect (I/O, OS integration) from
purely functional code
input adapter -> creates domain internals
tested through e2e’s + IT’s
domain internals -> calculate domain result
simple unit test
domain result -> output adapters translate result to
output domain
tested through e2e’s + IT’s
15. Refactoring to simplify testing
separate mutable state from purely functional
code
objects use a Tell Don’t Ask style API
objects can contain mutable state
the state is mutable. The value is Immutable
objects can notify observers when the state changes
Actor Model is a good example for Tell Don’t Ask APIs