Our site uses cookies. If you are using the site, you give you consent to use cookies, according to current browser settings. Got it

On Dependency Injection, Loose Coupling and Unit Tests in C++

How Dependency Injection solves a problem with strong coupling and dependency mess?

In an effort to improve the quality of our software, we are trying to move our testing effort from functional testing to unit testing. However, there are architectural problems that hold this transition. One such problem is a prevailing strong coupling and dependency mess.

In order to be able to create unit tests, a module of software must be designed to be testable in isolation, i.e. it must be loosely coupled to other objects through abstractions, which allow stubbing or mocking the dependencies. One particularly useful technique to achieve loose coupling is Dependency Injection design pattern (DI).

When we create a class that is supposed to use some other object, we usually do it this way:

/* public header */
class UntestableClass
  MyObject *mObject;
  UntestableClass() ;
/* somewhere in cpp file */
UntestableClass::UntestableClass(){ mObject = new MyObject; }

or even simpler (but with even more dependency headache):

class UntestableClass
  MyObject mObject;

Our class depends on a particular implementation of the object. You cannot instantiate any object of your class without instantiating a particular object of MyObject class the code depends on!

With such an implementation, you may even be forced to #include a header you are completely not interested in. A change in MyObject implementation may require relinking and frequently recompiling our whole module! A design with such a ripple effect is called strongly or tightly coupled.

In Dependency Injection design pattern, we delegate task of creating the object to the caller outside our module and usually require a pointer to such class in the constructor (in more complicated systems, e.g. many objects with dependencies to each other, we may need to add a special method to initiate the object with its dependencies), e.g.:

class TestableClass
  MyObject *mObject;
  TestableClass(MyObject *object): mObject(object) {}

With such an approach, our class does not need to know anything about creating or destroying the object. Hence, it does not need to know its implementation and internal members. Such coupling is called Loose Coupling (***). Such a loosely coupled object can be easily stubbed or mocked by unit tests. To simplify, what we use here is a particular class - without complete DIP implementation (i.e. abstract base class plus a method stub plus actual implementation.)

You may notice that a code using this design pattern is more complicated than necessary. Sometimes the whole pattern may seem to be an overkill. That’s true. In fact, this design pattern also has other minor disadvantages e.g. object lifetime management that has to be moved to another, higher layer - which isn’t necessarily attentive to such objects’ management. From this perspective, the objects might be irrelevant because in fact, they are used only by TestableClass. It might violate the rule of encapsulation. Well, a favour for a favour.

But Dependency Injection provides a great quality and maintainability improvement. It offers better separation of a core business logic code from configuration and initialization burden. Dependency Injection “allows the client to remove all knowledge of a concrete implementation that it needs to use. This helps to isolate the client from the impact of design changes and defects. It promotes reusability, testability and maintainability” (**).

Dependency Injection design pattern provides many advantages by itself. However, most of all, Dependency Injection is the first necessary step of applying full Dependency Inversion Principle (DIP) and making things unit-testable! Together with Dependency Inversion Principle, use this pattern for modules or classes you want unit tests to apply. (Actually, in my opinion, all classes that are interfaces to other modules - or that interact with other projects - should use it.)

On a final note, a related curse of our software is an overuse of global variables and singleton pattern (also known as anti-pattern) creating dependency entanglement. Avoid using them as much as possible. Use Dependency Injection to pass pointers to objects grouping and holding such data. Global variables and singletons are usually needed only by the system level objects that start and build up the major objects of the application (i.e. load dlls, plugins, create global object, etc.) Such a code is usually not unit-testable anyway and is the subject of system integration tests.

(*) https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29
(**) https://en.wikipedia.org/wiki/Dependency_injection
(***) https://en.wikipedia.org/wiki/Loose_coupling 

More from Bulldogjob

Zobacz jakie firmy chcą Ci płacić więcej.

Ile teraz zarabiasz?

Podpowiedzieliśmy już razy.

Mamy ponad 1200 ofert pracy. Zobacz o ile więcej możesz zarabiać. » [zamknij]