Design Patterns In Test Driven Development (TDD)
In order to fully understand TDD it helps to have an understanding of a couple of design patterns. First, let’s answer the most important questions of what design patterns are and why they’re important. A great definition of design patterns is they are “general and reusable solution[s] to… commonly occurring problem[s] within a given context” (Wikipedia).
What that means is that design patterns are the first steps to solving problems that have been solved in the past. Somewhere along the line someone came up with a really good way to solve a software design problem. It got generalized and popularized and became a design pattern.
Design patterns are important for several reasons. First, they save developers some work when they’re first getting started on solving a problem. Second, they make our code more understandable for the author and other developers who review the code at some point in the future. Finally, they help us solve problems we didn’t know we were going to encounter.
In a nutshell: design patterns facilitate and simplify TDD.
Inversion of Control (IoC) is a design principle intended to help separate objects from each other, a process called decoupling. In object-oriented programming (OOP) it is considered poor design to have objects that are tightly coupled to other objects, and IoC provides a process for breaking them apart.
Let's start with coupling and decoupling and go from there. In OOP we have classes, which are used to create – known as instantiating – objects. Adhering to the single responsibility principle of software design, each class has only one duty. Coupling occurs when we have multiple classes that each have a single responsibility that rely on each other to be in specific states in order to function.
We like to use analogies to make sense of software design and there’s a great analogy for discussing IoC: cars. Think of a Car as an object that depends on other objects to function. To keep it simple we’ll say that Car depends on Engine and nothing else. Car is created by a CarFactory, which in software can be considered the entry point to the program. So we have CarFactory acting as the entry point to our program and it will create a Car object.
But in order to create the Car object, the CarFactory will have to supply the appropriate Engine object. It would be possible to have a separate CarFactory for each type of Engine and Car combination, but that would get unwieldy fast. IoC says that we want to allow CarFactory to determine and supply the instance of Engine to use when Car needs it. This streamlines our process and also allows us to do some neat – and important – things when it comes time to test our Car.
Dependency (Constructor) Injection
Constructor Injection (DI) is – in our experience – the most common way to implement IoC. In OOP each class has a constructor that is used to create an instance of the object. DI provides all of the dependencies once in the constructor. In the case of our Car class from earlier, we’d have a constructor that accepts an Engine class. When an instance of Car is created, it must be provided with an instance of an Engine to use.
"Software is the language of automation" – Jensen Huang, CEO of Nvidia Corporation
Dependency (Setter) Injection
Setter Injection (SI) is really similar to DI, but it is different in one significant way: the method used to set the dependencies. In SI we use a single, parameterless constructor to create an instance of the object. Any dependencies the object has are set to some default values. Once the object is created, setter methods are provided to allow the creator to change the default values. In our Car example we’d create every instance of a Car with a default Engine; let’s say a V8. Then, if CarFactory wanted to use a different Engine – like a V6 – it could use a method on the created Car object to change the type of Engine to V6.
Service Locator is a design pattern that says that an object will know where to ask for its dependencies when it needs them, but the object won’t know where they come from. If we applied the Service Locator pattern to our Car example we might have a new class – we’ll call it ServiceLocator for simplicity – that knows where to get an Engine. Car would depend on ServiceLocator and the value would always be the same and Car would always know how to get the appropriate Engine from ServiceLocator. The Service Locator pattern can be used to implement IoC, but generally one of the dependency injection methods is preferred. It’s worth mentioning because sometimes it is impossible to implement one of the dependency injection patterns, but we won’t discuss it any further in this guide.
The Factory Method is a creational pattern that “lets a class defer instantiation it uses to subclasses”. This means that a class that uses – or depends on – another class doesn’t need to know the details of the class on which it depends. Our Car doesn’t need to know how Engine works; it only needs to know what methods exist to interact with.
Car is supplied an instance of Engine with a guarantee that Engine will have some way to Start. Car only knows that there’s a way to call Start on the Engine, but it doesn’t know how the Engine actually starts up. Not only does this further allow us to decouple Car from Engine, but it will also make it easier to test Car’s functionality later.
Now, Start Doing Unit Testing
These principles are the groundwork of Test Driven Development (TDD). Knowing them, becoming familiar with them, and practicing them will get you on your way to becoming a proficient unit test writer.
Andrew Webster is a software extraordinaire who loves to write code that is clean, testable, and stable. He is a Certified Scrum Master, was a Co-Founder of a tech startup, and worked for one of the largest software consulting companies. You can find him on LinkedIn here.