TDD and the SOLID principles (Part 6 – DIP)

November 1, 2011

This is the 6th post in the series about TDD and its relationships with the SOLID principles. Here’s the table of content for the series:

Pat 1 – introduction

Part 2 – SRP

Part 3 – OCP

Part 4 – LSP

Part 5 – ISP

Part 6 – DIP (this post)

Part 7 – Conclusion

DIP – Dependency Inversion Principle

Depend on abstractions, not on concretions.

This principle states that:

A. HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS.

B. ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS SHOULD DEPEND UPON ABSTRACTIONS.

This principle is perhaps the most important principle when doing TDD, and the one that greatly improves the design of code written that way. Obviously you can adhere to this principle without doing TDD, but TDD really makes it straight-forward. Without TDD (or at least high unit-test coverage) this principle is often neglected because it’s a bit awkward and counter-intuitive to write your code this way.

What this principle means is that in most cases, your classes (and interfaces) shouldn’t depend upon other classes, but only (or mainly) on interfaces. The great benefit of this is that your entire application becomes modular and loosely-coupled, and every class can be easily reused in different contexts.

The reason that this principle is crucial to TDD is that if you don’t use abstractions, the tests become more of an end-to-end tests rather than unit tests. For example, if you’re writing a Business Logic method that also saves some state to the database, you must use a real database in order to tests it. This makes the tests much more fragile (less maintainable) and also slow.

On the other hand, when doing TDD, it is easier to pass mocks instead of the real dependencies to the tested object. In order to be able to pass a mock at the test and a real object at run-time, the object must access it though an interface.

Let’s look at an example:

Suppose we need a class that imports customer’s data from an XML file and saves the data to the database. Let’s assume also that we already have a DTO* for the Customer, an XML parser that can read the Customer XML and return the Customer object, and even the CustomerDAO that can take a Customer object and save it to the database. Given all that, our work is pretty easy, and we can implement it like this:

* The term DTO stands for Data Transfer Object, and is used to describe a class that is used to describe data (that can be easily transferred) without any logic. DTO classes typically consist only of bunch of properties without methods. In TDD it’s usually considered OK not to test these classes because the only “logic” that can be tested about them is that each property returns what it was set (which if you’re using C# automatic properties you get for free)

   1:  

   2: class CustomerImporter

   3: {

   4:     private readonly string _connectionString;

   5:     public CustomerImporter(string connectionString)

   6:     {

   7:         _connectionString = connectionString;

   8:     }

   9:     

  10:     public void Import(string xmlFileName)

  11:     {

  12:         var customerXmlParser = new CustomerXmlParser();

  13:         var customer = customerXmlParser.ParseFromFile(xmlFileName);

  14:         using(var customerDAL = new CustomerDAL(_connectionString))

  15:         {

  16:             customerDB.Save(customer);

  17:         }

  18:     }

  19: }

Looks ok, isn’t it? Well, it’s ok as long as we don’t need to change anything. However, how reusable is this class? Let’s say that later we’re asked to provide the same functionality in a light-weight version of the same application, which uses files instead of a full-blown database. In this case we’ll need to change the method and replace all references to CustomerDAL to references to CustomerFile.

How things would have looked if we were doing TDD?

Obviously it’s not a good idea to use a real database and even read from a disk file from a unit-test. So we need to mock both the CustomerDAL and the CustomerXmlParser. Our test would probably look something like this:

   1: [TestMethod]

   2: public void CustomerImporterShouldImportACustomerFromXmlIntoTheDatabase()

   3: {

   4:     var customerDummy = new Customer { Name = “Arnon”, Email=arnona@e4d.co.il };

   5:     var xmlFile = CreateXmlFileMockFromCustomer(customerDummy);

   6:     var customerDALMock = new CustomerDALMock();

   7:     

   8:     var importer = new CustomerImporter(customerDALMock);

   9:     importer.Import(xmlFile);

  10:     

  11:     var savedCustomers = customerDALMock.GetSavedCustomers();

  12:     Assert.AreEqual(1, savedCustomers.Count());

  13:     Assert.AreEqual(customerDummy, savedCustomers.Single());

  14: }

  15:  

  16: private ICustomerFile CreateXmlFileMockFromCustomer(Customer customer)

  17: {

  18:     return new CustomerFileMock(customer);

  19: }

  20:  

  21: private class CustomerFileMock : ICustomerFile

  22: {

  23:     private Customer _customer;

  24:  

  25:     public CustomerFileMock(Customer customer)

  26:     {

  27:         _customer = customer;

  28:     }

  29: }

  30:  

  31: private class CustomerDALMock : ICustomerDAL

  32: {

  33:     public IEnumerable<Customer> GetSavedCustomers

  34:     {

  35:         get { throw new NotImplementedException(); }

  36:     }

  37: }

Note: if you use some sort of mocking framework (e.g. MS Moles, Moq, RihnoMocks, etc.) This code would be much shorter.

Now that we have all the test code in place, let’s add the production code to make it compile:

   1: public interface ICustomerFile { }

   2:  

   3: public interface ICustomerDAL { }

   4:  

   5: class CustomerImporter

   6: {

   7:     public CustomerImporter(ICustomerDAL customerDAL)

   8:     {

   9:     }

  10:  

  11:     public void Import(ICustomerFile customerFile)

  12:     {

  13:     }

  14: }

Now the code should compile but the test fails. So let’s fill in the code to make it pass:

   1: class CustomerImporter

   2: {

   3:     private readonly ICustomerDAL _customerDAL;

   4:  

   5:     public CustomerImporter(ICustomerDAL customerDAL)

   6:     {

   7:         _customerDAL = customerDAL;

   8:     }

   9:  

  10:     public void Import(ICustomerFile customerFile)

  11:     {

  12:         var customer = customerFile.Read();

  13:         _customerDAL.Save(customer);

  14:     }

  15: }

  16:  

  17: public interface ICustomerFile 

  18: {

  19:     Customer Read();

  20: }

  21:  

  22: public interface ICustomerDAL 

  23: {

  24:     void Save(Customer customer);

  25: }

Now we need to implement the new members in the mocks:

   1: private class CustomerFileMock : ICustomerFile

   2: {

   3:     private Customer _customer;

   4:  

   5:     public CustomerFileMock(Customer customer)

   6:     {

   7:         _customer = customer;

   8:     }

   9:  

  10:     public Customer Read()

  11:     {

  12:         return _customer;

  13:     }

  14: }

  15:  

  16: private class CustomerDALMock : ICustomerDAL

  17: {

  18:     private List<Customer> _customers = new List<Customer>();

  19:  

  20:     public IEnumerable<Customer> GetSavedCustomers

  21:     {

  22:         get { return _customers; }

  23:     }

  24:  

  25:     Public Save(Customer customer)

  26:     {

  27:         _customers.Add(customer);

  28:     }

  29: }

Test Passes!

Note how simpler CustomerImporter is now. But more importantly, it doesn’t depend on any concrete class, only on interfaces! (except of Customer which is a DTO).

Ok, but it only works with mocks and not with the real classes! So let’s go and develop these classes. Assuming that the CustomerDAL and CustomerXMLParser are given to us without tests, and without the source code, we need to wrap them with another class that implements our interfaces. If you’re using a mocking framework that allows to mock non-virtual members (like Moles does) then you should add tests for these wrappers and validate that they call the relevant methods in the legacy classes. If not, then you don’t have much option to test them. Sometimes it worth writing a simple “smoke” test for these wrappers to validate that they’re doing “something”, or just leave them untested. Anyway, these classes should be very small and straight-forward, that a simple code review and a manual test of the application every now and then is good enough.

   1: public class CustomerXMLFile : ICustomerFile

   2: {

   3:     private _filename;

   4:  

   5:     public CustomerXMLFile(string filename)

   6:     {

   7:         _filename = filename;

   8:     }

   9:  

  10:     public Customer Read()

  11:     {

  12:         returns new CustomerXmlParser(_filename).Parse();

  13:     }

  14: }

  15:  

  16: public class CustomerDALWrapper : ICustomerDAL

  17: {

  18:     private string _connectionString;

  19:     

  20:     public CustomerDALWrapper(string connectionString)

  21:     {

  22:         _connectionString = connectionString;

  23:     }

  24:     

  25:     public void Save(Customer customer)

  26:     {

  27:         using(var dal = new CustomerDAL(_connectionString))

  28:         {

  29:             dal.Save(customer);

  30:         }

  31:     }

  32: }

In order to use our new CustomerImporter in our application we need to do it like this:

   1: var customerDAL = new CustomerDALWrapper(_connectionString);

   2: var importer = new CustomerImporter(customerDAL);

   3: var customerXmlFile = new CustomerXmlFile(filename);

   4: importer.Import(customerXmlFile);

Clearly we can extract these 4 lines into a method that receives only the filename, or if we need to reuse it in other places then we can create a new class just for that (writing a test first, of course).Anyway, now we can use CustomerImporter in our light-weight version of the application without changing it at all! Just create a new class that implements ICustomerDAL and implements Save(Customer) by writing it to a file. Moreover, if we’ll be asked to be able to import files in different formats and not only XML, or even get the data from a different source (not a file), then we’ll be able to do so easily too: just create a new class that implements ICustomerFile and we’re set.

Factories and IOC containers

I wrote the above non-TDD version before I wrote the TDD version, so I didn’t anticipated how the code written using TDD will look like. In fact, I thought the final code will look pretty much the same, except that instead of using new to create objects, I would use factories that will be passed as interfaces to the constructor. As you can see, TDD really drives the design in ways you don’t expect…

Anyway, it is often the case that you have to create new instances of an object at run-time. The problem is that in order to adhere to the DIP principle, we can’t specify the class, and there’s no way to instantiate an interface… The solution is to use factory classes. Factory classes do use the new operator and so they don’t adhere to the DIP principle by themselves. But as long as this is their only responsibility, and only the factory classes depend on concrete classes, that’s ok (and even unavoidable). However, even though the factory classes rely on concrete classes, they shouldn’t expose any of these on its public interface.

Moreover, when you’re writing the test first, you’d probably want to abstract the generated object and not the factory itself. But in order to do so, you must also abstract the factory using an interface. Otherwise the real factory will create a real object that you can’t mock.

In order to avoid having to create a factory for each object and still use interfaces to abstract concrete classes, there are several tools out there that fall into a category called “IoC containers”. IoC stands for Inversion of Control, and IOC containers provide means to instantiate objects automatically without having to write factory for all classes. Here you can find a list of popuplar IoC containers. In addition, MEF can also be used as an IoC container, even though its main focus is to manage extensions and plug-ins.

IoC containers are invaluable tools when working in TDD, but are beyond the scope of this series.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*