I’m going to be randomly posting about things that come up as I build out what I am calling a DDD-Lite application, as much so I can record decisions I made along the way for anything else, so that if I decide to change something down the road, I have at least so record of why I did things the way I did in the first place.
To group them together, I’m tagging them so that they can be viewed as a group here.
When I was first learning about TDD (which was relatively recently, some time in 2006, I think), I knew a guy that was actually practicing it, and I used to ask him why he didn’t have test methods like this (I’m using SpecUnit syntax and that whole godawful underscore naming method stuff):
public void when_incrementing_an_integer_it_increments_properly()
int integerUnderTest = Int32.MinValue;
for (int i = Int32.MinValue + 1; i <= Int32.MaxValue; i++)
Surely (don’t call me ‘Shirley’), surely, your application uses integers, right? If you want 100% code coverage, you need to know this works, correct?
Now, no one that I know of thinks that TDD requires you to have tests like this (in case you are wondering, on a Quad Core Q6700 box with 4 GB of memory, the unit test timed out after 30 minutes). But, I mention this because I decided that, to the best of my ability, I was going to practice TDD unless and until I couldn’t anymore for a legitimate reason.
disclaimer: some might wonder, given my critical stance (some might say ‘open hostility’) towards TDD, whether I could possibly follow the practice. Though I guess the reference will be lost on almost everyone, this is nothing compared to my feelings about Quine’s behavioristic stance concerning the philosophy of language. I found just about every aspect of his philosophical work to be obviously and fatally flawed, but though no one openly advocated the specifics of his behaviorism anymore, the influence of his writings and theories (his indeterminacy of translation thesis in particular) was such that the only way I could properly reject them was to do everything I could to imagine in the best possible light why someone would believe his position, and from that standpoint, point out the weaknesses.
I bring this up for the hell of it, but also because of Paul Cowan’s recent post on DDD, and the response it generated. A lot of people responded that Cowan didn’t really understand DDD. I will accept that for the sake of argument, but point out that it is also largely irrelevant. Cowan meant his post to be semi-serious (which some people missed), but there was a central point to his post that I think is absolutely correct. It doesn’t matter whether Cowan ‘really’ understood DDD if his criticism was sound, but that isn’t something most people can handle. Few people can handle the philosophical rigor required to properly analyze theories and positions without taking the personality or persona of the person stating the theory or position, and that is unfortunate, but regardless, I want to be able to describe, analyze and practice TDD as best as I can. Even if ultimately I want to offer a replacement.
Immediately, then, when deciding to practice TDD, you have to decide whether you want to shoot for 100% code coverage, but the ‘int test’ makes it clear that the notion of ‘100% code coverage’ isn’t clear. And so when I decided to do TDD on this DDD-lite thing, I had an obvious problem.
As smarter people have said, TDD tells you what to do, but it doesn’t tell you where to do it. BDD is supposed to help out here (work with your stakeholders to find out what you need, blah blah blah, yada yada yada). However, this doesn’t help me here for (at least) two reasons:
1) Though I am not the only one, I am the main stakeholder as well as being the main developer/architect/blahblahblah.
2) I hate BDD more than I do TDD, and I feel the need to grok first things first, so to speak.
One of the other things that makes it really hard for me to do TDD has to do with design.
When done properly, I gather that TDD done properly offers (at least) the following benefits:
1) YAGNI done right: you only code what you need to code, when it needs to be coded. Instead of the possibly random and too-far-forward thinking that can come from upfront design (“we need to log what this file service does, so let’s design a logging service that can log anything we can think of upfront before we actually need it”), you design and code for what you need *now*.
2) Cleaner API: our tests help us to define the API our calling code will actually prefer, in terms of what we need *now*.
An immediate problem arises. What if you are building a greenfield application that is supposed to be a refinement of all the practices you’ve done previously within a given domain? I already know a lot of features and functionality that the domain is supposed to address, but TDD seems to involve what might be called ‘purposeful lobotimization.’ I already know a *lot* about what the domain model should look like, but TDD stresses that I shouldn’t build the domain model without supporting tests. So, I find myself defining a lot of tests, not in terms of developing a cleaner API, for instance, since I already know what the API should look like, but because that is what TDD says I should do. Personally, I find this *very* hard to do. Writing the tests first slows down the development process.
More generally, if you are designing an application, you should have some previous knowledge of the domain that you are working in (unless you are a pure contractor on a project). Because you already know this, you already have a general inkling of ways in which your domain is going to be used, even if you don’t have immediate calling code *right now* that defines that usage. But TDD says not to write that code yet.
Why is it that TDD doesn’t require you to test Int? Int is part of the .NET framework. Unless you have a specific bug related to the .NET framework, you shouldn’t have to test framework code. It should be assumed to work.
When writing your own code, I think this rule should apply (maybe). If you are creating framework-type code, maybe you test it at some basic level, but otherwise, you shouldn’t have to.
Currently, I am violating this rule, let me explain why.
Let us assume the domain is related to an inventory management system. Within this system, you know you need to handle an item/SKU. An item could have many properties and behaviours, but let’s assume a bare minimum, that any item has a name and has a set of attributes. For instance, a t-shirt will have a color and a size. So, from a domain modelling perspective, we know we need to create an Item classs that has a name property and an Attribute collection.
On the surface, the Attribute collection has similar characteristics to an Int. If we add an Attribute to the collection, do we really need to assert on the count of that collection? I would hope not.
But, in this domain, there is some logic involved. Suppose you have an item that has a Size attribute already defined in its Attribute collection. If you try to add another Size attribute, what should happen? I know that what should happen is that the Size attribute should be replaced, not added. TDD rightly tells me here that I should ensure this behavior exists. If I add a Size attribute to the collection where that attribute already exists, the count should stay the same and the value should be the new value added.
TDD, strictly applied, does seem to slow down the development process. The cost of this slowdown needs to be held up against the benefit of applying it. As of now, it seems to be a good thing to do.