|
It's been a while. I have been heads down on a new project (more about that some other time), and have not got around to posting. Without further ado here is the follow up post to TDD and Hart To Test Areas, Part 1 . Depend upon Abstractions The Gang of Four’s first principle is to program against abstractions not implementations. If we use abstractions then we can solve the hard-to-test problem by implementing the abstraction in terms of the hard-to-test dependency in production, but with a simple-to-test dependency in test. So we could use an IDatabase to abstract out our interaction with the Db, using concrete Ado.Net classes in production, but replace it with an in-memory collection for testing. Jeremy Miller summarizes this approach as ‘ isolate the ugly stuff ’. We need to show some rationality here. No one wants IString, everyone wants IDatabase, and we probably don’t need an ICustomer, but we might. The advantage of TDD is in flushing out where ICustomer is useful by exercising the SUT. So when designing for testability we need to think about which dependencies we want to allow to be concrete, and which won’t don’t. Different schools of programming make different value judgments here. Classicist approaches tend to avoid replacing all dependencies, focusing instead on ones that are needful to support extensibility and layering. Design principles should help us identify when to use interfaces and these tend provide the opportunities we need to use abstractions to isolate hard-to-test code. Layers A layered architecture also creates a need for abstractions. While the layers must communicate, like a layer cake higher lays may depend on lower-layers but not vice-versa. To effect this, higher layers in our architecture should depend on an abstraction exposed by the lower layer, not a concrete type. The two layers can communicate via the agreed contract, but the higher layer has no dependency on the lower layer. Robert Martin's Dependency Inversion Principle states that High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. This dovetails with hard-to-test areas as layer boundaries often co-inside with hard-to-test areas such as the UI or access to external systems. So using abstractions when we layer helps us to achieve testability. To ensure cohesion we often talk to a façade when we cross a layer boundary, which hides the complexity of the other layer, and this again simplifies testing, by removing the need to create the objects that implement the façade as part of our test setup. Open-Closed Principle In Agile Principles, Patterns, and Practices in C# Robert Martin describes the Open-Closed Principle as “ Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. ” To achieve this ‘impossible thing before breakfast’ we use polymorphism. By creating an abstraction...
|