One of the exercises I love to do in my TDD classes is to build a lightsaber in TDD. (Yes, of course that’s how they’re made).
In the exercise, I go through listing all kinds of features and use cases, and the first test we usually write is for turning the lightsaber on. Most times it looks like this:
@Test public void WhenTurnedOn_LightSaberStatusIsOn() { Lightsaber saber = new Lightsaber(); saber.TurnOn(); assertEquals(Status.On, saber.getStatus()); }
It’s got a weird-ish name, and it’s probably not the first test I’d write (I’d do the off status after creation). But that’s not my peeve.
If you’re coming from a development background, like me, a getStatus method on a lightsaber seems perfectly ok. After all, how else would you check if the lightsaber is really on?
There are two issues with this. The smaller one is the premature generalization: Using an enum as a result. Sure, if you’re using a getStatus method, you want to return a meaningful value. Yet, if you TDD like a pro, the tests should tell you when to generalize, like after you have a couple of return values.
But I’ll let that one slide, there’s a bigger issue here.
Talk this way
Did you ever hear a Jedi master, or a Sith lord ask: “What status is that lightsaber? I need to get it”.
No. They don’t talk like that.
Regular humans don’t talk like that.
The only people who talk like that are programmers.
Chances are, if you’re a programmer and you’re still reading, you probably still don’t see the problem. Let me spell it out: We’re coding using terms that are different than the ones used in the business domain.
This results in maintaining two models, the business and the code (and sometimes the test model), all either using different terms, or not carrying the exact meaning. So we need translations. Translations bring mistakes with them (read: bugs).
As time goes by, and we add more code, the two models diverge. Making changes (say, a new requirement) needs more effort, and is more risky. We need to continuously update the business model, and re-translate into the code model. That is hard and error prone.
If we don’t put effort into it, the difference between the models grow. We breed complexity by using two languages, and we pay in a lot more effort.
What’s a better way? Well maybe something like this:
@Test public void WhenTurnedOn_LightSaberIsOn() { Lightsaber saber = new Lightsaber(); saber.TurnOn(); assertEquals(true, saber.isOn()); }
Now that’s Jedi talk. You can already feel the Force flowing through it.
5 Comments
David V. Corbin · June 29, 2017 at 5:23 pm
Great article…
I am not going to let the “enum” aspect slide… If one knows that there will be a set of defined values, then an enum from the get go is appropriate. In most cases this is true even if there are only two values. TDD will drive the set of values for the Enum…
As to naming and terminology, tools based on business descriptions such as Gherkin can be quite helpful in bridging the gap, and automating much of the translation.
Aaron Evans · June 30, 2017 at 12:53 am
David,
But do you really want to decide if an enum is the way you want to implement it. Maybe lightsabers will only ever have two statuses — on and off — but maybe not. Maybe there will someday be a “charging” status for cordless lightsabers or maybe status will want to be something else (damaged or made with synthetic kyber crystals) in the future. Or maybe you’ll want to localize and now you’re stuck translating an English enum into Klingon pictograms.
Why worry about that up front. Just check whether it’s on. You can still go ahead and implement isOn() { return this.status == Lightsaber.Status.ON; }
It reads better and is more extensible.
Gil Zilberfeld · July 1, 2017 at 8:03 pm
Actually, in the exercise, after a couple of tests, I spring on them Rey’s double saber and ask about its “status” and if that’s a lightsaber at all. In real life, in the first rounds, in two states, I’d go with the boolean on/off representation. If there is one more I see, I’d probably go with the enum right off the bat.
Gil Zilberfeld · July 1, 2017 at 7:59 pm
Hey, if I allowed people to just jump three moves ahead what kind of example am I setting? I agree :). If you can see three moves ahead clear enough, go ahead.
As for Gherkin, Cucumber and their ilk – I don’t see them adding value if only devs write and review them (which is often the case). Writing xUnit tests can use “real” language without losing readabilty. I found BDD tools and languages work, as a start point when the non-coder play (or allowed to play :() along).
Felipe Carvalho · July 5, 2017 at 10:50 am
Awesome reading! Thank you so much for this piece golden wisdom!