Subscribe to PHP Freaks RSS

Testing actual behavior

syndicated from on June 26, 2018

The downsides of starting with the domain model

All the architectural focus on having a clean and infrastructure-free domain model is great. It's awesome to be able to develop your domain model in complete isolation; just a bunch of unit tests helping you design the most beautiful objects. And all the "impure" stuff comes later (like the database, UI interactions, etc.).

However, there's a big downside to starting with the domain model: it leads to inside-out development. The first negative effect of this is that when you start with designing your aggregates (entities and value objects), you will definitely need to revise them when you end up actually using them from the UI. Some aspects may turn out to be not so well-designed at all, and will make no sense from the user's perspective. Some functionality may have been designed well, but only theoretically, since it will never actually be used by any real client, except for the unit test you wrote for it.

Some common reasons for these design mistakes to happen are:

  • Imagination ("I think it works like that.")
  • The need for symmetry ("We have an accept() method so we should also have a reject() method.")
  • Working ahead of schedule ("At some point we'll need this, so let's add it now.")

My recent research about techniques for outside-in development made me believe that there are ways to solve these problems. These approaches to development are known as Acceptance Test-Driven Development (ATDD), or sometimes just TDD, Behavior-Driven Development (BDD), or Specification By Example (SBE). When starting out with the acceptance criteria, providing real and concrete examples of what the application is supposed to do, we should be able to end up with less code in general, and a better model that helps us focus on what matters. I've written about this topic recently.

The downsides of starting with the smallest bricks

Taking a more general perspective: starting with the domain model is a special case of starting with the "smallest bricks". Given the complexity of our work as programmers, it makes sense to begin "coding" a single, simple thing. Something we can think about, specify, test and implement in at most a couple of hours. Repeating this cycle, we could create several of these smaller building blocks, which together would constitute a larger component. This way, we can build up trust in our own work. This problem-solving technique helps us gradually reach the solution.

Besides the disadvantages mentioned earlier (to be summarized as the old motto "You Ain't Gonna Need It"), the object design resulting from this brick-first approach will naturally not be as good as it could be. In particular, encapsulation qualities are likely to suffer from it.

The downsides of your test suite as the major client of your production code

With only unit tests/specs and units (objects) at hand, you'll find that the only client of those objects will be the unit test suite itself. This does allow for experiments on those objects, trying out different APIs without ruining their stability. However, you will end up with a sub-optimal API. These objects may expose some of their internals just for the sake of unit testing.

You may have had this experience when unit-testing an object with a constructor, and you just want to make sure that the object "remembers" all the data you pass into it through its constructor, e.g.

final class PurchaseOrderTest extends TestCase
     * @test
    public function it_can_be_constructed_with_a_supplier_id(): void
        $supplierId = new SupplierId(...);

$purchaseOrder = new PurchaseOrder($supplierId);

self::assertEquals( $supplierId, $purchaseOrder->supplierId() ); } }

I mean, you know a purchase order "needs" a supplier ID, but why does it need it? Shouldn't there be some aspect about how you continue to use this object that reveals this fact? Maybe you're making a wrong assumption here, maybe you're even fully aware that you're just guessing.

Try following the rule that a domain expert should be able to understand the names of your unit test methods, and that they would even approve of the way you're specifying the object. It will be an interesting experiment that is worth pushing further than you may currently be comfortable with. The fact that an object can be constructed with "something something" as constructor arguments is barely interesting. What you can do with t

Truncated by Planet PHP, read more at the original (another 7739 bytes)