Author: haroonkhan

  • Complex conditions and testability

    We have seen in previous chapters that very complex conditions (such as an if statement composed of multiple boolean operations) require considerable effort from testers. For example, we may devise too many tests after applying boundary testing or condition + branch coverage criteria. Reducing the complexity of such conditions by, for example, breaking them into multiple smaller…

  • The coupling of the class under test

    In a world of cohesive classes, we combine different classes to build large behaviors. But doing so may lead to a highly coupled design. Excessive coupling may harm evolution, as changes in one class may propagate to other classes in ways that are not clear. Therefore, we should strive for classes that are coupled as…

  • The cohesion of the class under test

    Cohesion is about a module, a class, a method, or any element in your architecture having only a single responsibility. Classes with multiple responsibilities are naturally more complex and harder to comprehend than classes with fewer responsibilities. So, strive for classes and methods that do one thing. Defining what a single responsibility means is tricky…

  • Designing for testability in the real world

    Writing tests offers a significant advantage during development: if you pay attention to them (or listen to them, as many developers say), they may give you hints about the design of the code you are testing. Achieving good class design is a challenge in complex object-oriented systems. The more help we get, the better. The buzz about…

  • Dependency via class constructor or value via method parameter?

    A very common design decision is whether to pass a dependency to the class via constructor (so the class uses the dependency to get a required value) or pass that value directly to the method. As always, there is no right or wrong way. However, there is a trade-off you must understand to make the…

  • Example 2: Observing the behavior of void methods

    When a method returns an object, it is natural to think that assertions will check whether the returned object is as expected. However, this does not happen naturally in void methods. If your method does not return anything, what will you assert? It is even more complicated if what you need to assert stays within the method.…

  • Example 1: Introducing methods to facilitate assertions

    Take another look at the processAll() method and its test, in listings 7.2 and 7.5. Most of what its test asserts is the interaction with the ports. Such assertions are easily done, and we did not need much more than basic Mockito. Now, let’s look closer at one specific assertion: verify(someCart).markAsReadyForDelivery(someDate);. The someCart instance of ShoppingCart is not a mock but a spy.…

  • Making your classes and methods observable

    Observability, at the class level, is about how easy it is to assert that the behavior of the functionality went as expected. My main advice is to ensure that your classes provide developers with simple and easy ways to assert their state. Does a class produce a list of objects you need to assert one…

  • Dependency injection and controllability

    At the architectural level, we saw that an important concern is to ensure that application (or domain) code is fully separated from the infrastructure code. At the class level, the most important recommendation I can give you is to ensure that classes are fully controllable (that is, you can easily control what the class under test does)…

  • Separating infrastructure code from domain code

    I could spend pages discussing architectural patterns that enable testability. Instead, I will focus on what I consider the most important advice: separate infrastructure code from domain code. The domain is where the core of the system lies: that is, where all the business rules, logic, entities, services, and similar elements reside. Entities like Invoice and services such as ChristmasDiscount are examples…