Real Programming S01E06: Getting A Little Testy

Part 6 of 10 in the series Real Programming

Real Programming S01E06: Getting A Little Testy!

We did good work last time, but we left some testing issues, so let’s fix that up.

If you want to follow along in the code, the project is at github.


Real programming, episode 6. The show where the slogan well, we ran into a couple of technical glitches. But we seem to have found workarounds for it, and we don’t think we’d slipped schedule really at all. So it’s all going really well. Today is a test-tacular day. I want to spend some time talking and thinking about the tests that we have and look at them piece by piece and probably make some changes to it. Right now, our code has four distinct objects in it, a view, a model, a game, and dice. And what we want to do today is I want to look at the tests for each one of these. I want to look at their responsibilities. And I want to make sure that there’s a nice alignment there.

So we’re going to start down at the bottom. And we’re going to actually find a mistake almost immediately from last episode. Here’s our dice class, which takes a roller, sets up a little array of pips. And when you roll it, it changes those pips. This functionality reset, we added at the last minute last time. And then, finally we have a constant down here for the unknown values. Now when I look at my tests, my tests are pretty straightforward. There’s only a couple of them. The dice start out as unknowns, and the dice change during a roll. Well, that’s not unreasonable. That covers my roll pretty well. And it covers my initialization pretty well.

But look what happened. It doesn’t cover this reset function at all. Bad GeePaw, no biscuit. I added that reset function at the last minute during our last run, and I didn’t have a test for it. So we call this over-coding. When you’re working in an area where you really are maintaining TDD discipline and in that area, you change or add functionality without having a test that tests that that functionality is needed. And I have to tell you, it’s actually quite common. It isn’t a mortal sin. It’s a venial sin. You’ll be OK.

You just have to go and backfill the test. The backfill here is pretty straightforward. Let’s just put in a test here that says the reset resets us as unknowns. Here’s our new test. We know that a roll changes the starting values from unknown to in this case 5, 4, 3, 2, 1. So dice.roll, now do a reset. Now assert that it’s equal to unknown. So that’s good. But now let’s take a closer look, just one more quick look at dice. Dice themselves roll, reset. The dice here, their job is really to represent the mechanics of actually having five physical dice.

Now later, when we add the ability to hold some of the dice and not roll them, that ability will go in here. Notice that dice doesn’t really enforce any rules of any kind. It just does the mechanics. So that’s what I’m going to put on our little picture here that we’ve got for the drawing. We can say that the dice handle the mechanics of rolling. And at this point, the dice and the dice tester are very tightly correlated. I feel pretty good about them. I’m going to give them a nice clean checkmark. So let’s take a look at the game object. The game object is a little heftier than the dice object, but not very much so. And that’s just fine.

Essentially, the game’s object uses the dice. And what it does is it takes the– it tracks how many rolls we’ve had in a round. It uses the start for a round. I’m pretty sure that’s going to change. This is actually going to become something like new round or something like that because it’s not the way the real game works. But that’s OK. And then it does the roll if you can’t roll, throw an exception. Otherwise roll the dice and increment the rolls in the round. Pretty straightforward. I would say looking at this that the game’s responsibility right now is actually enforcing the rules. That’s what game does. It manages the rules. You could actually notice it’s doing a couple of things. It is both enforcing the rules, and it’s managing the sort of surface that game shows to the outside world.

We don’t like class responsibilities that only have and. So make a note of that. That might be a little smelly, and we might want to do something about that at a later date. For now though, it’s pretty straightforward. Now when we look at the game’s test, you can really get a sense of what I mean about enforcing the rules. You can’t roll the dice at the start is a rule. It throws on unallowed roll. That’s a rule. You can roll after the start. There are three rolls allowed without a start. Rolls changed the pips. OK, that’s not very much about the rules exactly. Let’s come back to that one. And a start after three rolls allows another roll. OK, that’s fine.

This is the only one of these tests that isn’t really about rules. What it’s really doing is it’s testing an aspect of the game that is the mechanics of the dice. Rolls changing the pips, that’s something dice do. That isn’t something the game does at this point. I’m going to mark that test as highly questionable. I’m going to leave it for just a little bit while longer. And we might get rid of it in a little bit. We’ll talk about it after we’ve talked about the model test. So over here on our little picture, the game class enforces the rules.

And I’m going to say that the game’s tests are good enough with this one question about why is that one test in there testing something that isn’t just enforcing the rules? Let’s move on and take a look at the model. Remember that the model’s functionality, the whole purpose of the model is to be out there fronting things out to the view. It serves as a channel between the view and the actual core business functionality, which in our case is represented by the game. So essentially, he has a role which he passes on to the game and then an update. And the update just changes all of these values, so that anybody watching the model gets notifications that things are changing.

So that’s really kind of interesting, isn’t it, because the model’s job here is to serve as this channel. And the more interesting part of this job is almost entirely contained in update, right. Everything else I do is done by the game. Game.roll, then update. Game.start, then update. Update is really the only interesting operation. Now coming in here by the way, I notice something immediately. Look here. I still have that rolls left property that was left over from last time. So I’m just going to go ahead right on the spot and kill that off. And we’ll run our tests, of course, to make sure that they pass, OK. Everything’s fine.

All right, so with us in this particular state, now let’s take a look at the model test. Can’t roll the dice at the start, well, that’s a game job determining that, isn’t it. Can roll after– these tests are all essentially approximately the same tests that we have against the game. Now I look at that, and I say that is an issue. And I think it’s a pretty interesting issue. And it actually raises a very large question about what kinds of things we test and when we test them. So when we update our picture, the model is a channel between the view and the game. What about the tests though? Am I willing to check off that box? Well, no. I’m not.

What we need to do is have a conversation about the pieces premise and how it works. So let’s go there. I am a micro-test test-driven developer. And one of the things that means is that I subscribe to something that we call the pieces premise. It goes like this. To get the whole thing to do exactly what you want it to do, start by getting each piece to do exactly what you want it to do. That’s all there is to the pieces premise, and it sounds a little strange. So let’s try it out in the case of a real app and see what it’s really about under the hood.

This application has three pieces to it, an A, and a B, and a C. And they have dependencies on one another. A uses B to do some of its work. And it uses C to do some of its work. Now the pieces premise from micro-testing says what we want out of life is to have tests that test specifically that A does what it’s supposed to do, assuming B and C do what it’s supposed to do. And ditto B and ditto C. So it looked kind of like this. We would have a test around B, test around C, and a test around A. And in each case, what we focused on is the specific responsibility of each of those classes. That’s one of the reasons I in the previous drawing wrote down for you what I saw those responsibilities as being.

Now an extremely common non-micro-test approach is to do something like this. We will write a test on A. In fact, with this test A and its cases in place, you would never need a test for B or a test for C. We call this testing through. And what it’s doing is it’s testing pieces by testing the whole. Now why would that be a problem? The simple answer is because most applications don’t just have three parts. Most applications have hundreds or even thousands of parts. And testing through the top of the dependency tree, in fact creates a variety of awkwardness is for us.

To give you an example, in a real live app, a big one, If you tested everything by testing through the top, you would have awkward run time. The tests would take a long time to run. You might also have awkward setup. You might have to go out– let’s say that piece C depended on a database. That would mean that in order to run your test A through everything, you would have to go out here and hook up that database. That would be awkward setup because controlling a faraway resource like a database is actually fairly expensive.

But there’s other issues too. What about awkward debugging? What do I mean? Well, suppose we have 1,000 tests up here inside test A that are testing this huge hierarchy. And one of those tests goes wrong. Here’s the problem. When that test goes red on you, you don’t know where in this tree the behavior changed. So you don’t have any good way to quickly locate the problem and repair it. That’s a very serious issue. So these awkwardnesses, they really add up. And a micro-test test-driven developer is actually very careful each time to take a look at each class, decide specifically what that class is doing for us, and test specifically what it’s doing for us.

Now with that in mind, let’s go back and take another look at yz model. What’s yz model doing for us? Well, really serving as a channel is what we defined the functionality is. And the heart of serving as a channel is this guy right here. It’s update. The most important part by far and also the most complex part by far is the update operation. But when you go over here and look at the model test, you’ll see that this is testing stuff that is rules, rules, rules, rules. It’s not actually testing specifically and explicitly the update operation. And that is a problem.

So what is the fix for this? Well, let’s see. Update updates the pips. And update updates can roll. And it does that driven by the game. It’s the game. Now right now that game is unreachable by us outside the model. But you can easily imagine if I knew what that game was, then I could change the game in interesting ways. And I could repeatedly call the update. And I could make sure that the update does exactly what it’s supposed to do. Let’s go over here and write those tests. I’m going to put them all up at the top because my anticipation is that when I’m done, I’m actually going to get rid of all these tests.

So the first change I’m going to make is I’m going to get myself an explicit game. And I’m going to make it so that when you create a model, you pass it the game. This will let me write tests that drive the game in various ways and then inspect that the model is updating correctly. Of course, in order to do that, I had to rearrange this a little. I had to make the model actually take its internal game instead of making it on the spot. So that’s an example of the steering premise, right. I’m changing my design to make it easier for me to write the tests that I want to test.

All right, so let’s see what’s going on here with this guy. Updates the pips from the game. If I start the game and roll, it will not have different pips. I want to expressly call update. Notice that in order to do that, in this case, our update used to be private. And I just made it public. I thought about this a little bit. There are several different things that one could do in a situation like this. I could do what I did, which was simply expose the functionality. I could derive a new class off of yz model just for testing and give that guy access to a protected function update. Or I could do the most standard thing, but that would not work in this case, which would be to take this guy and somehow expose him on a whole other class where he’s public and call him from there.

Anyway, I looked over this. And you know what, it’s hard for me to see an outsider calling this inappropriately and doing any harm. There’s really no circumstance in which the updates shouldn’t be happening. So if you call an update, you’re not going to get into any trouble. So I decided we’ll just make him public. Now going back to the test, now, I make a change to the game. And I update the model. And when I update that model, it should now be the case that the pips have all changed away from being 0. By the way, that really shouldn’t be 0. It really should be dice.unknown. They should not be Dice.UNKNOWN. We run that test, works just fine.

What else do we need to update from the game? Well, we would need to update the can roll? Updates can roll from the game. Now is there really anything else in model that has to be updated? I don’t think there is. These two guys are the key aspects of the surface of can roll and dice. Now you could argue that that operation was certainly already deeply embedded in the tests that we were doing. But those tests were not explicit about what they were testing. And although it was a very mild beginning to gradually grow all those model tests out, you know, before we’re said and done, the Yahtzee game is going to be pretty complicated. And we will almost certainly find ourselves in this situation.

Now the thing about all this is all of it is by way of a judgment call. We constantly are making judgment calls in our code about what to do when we’re doing test-driven development. In fact, it is another one of the premises. We’ve talked about the steering premise, but this is entire thing is an exercise in the judgment premise. We are permanently and happily dependent on individual humans using their individual judgment.

Going back to this code, I now feel pretty happy going ahead and getting rid of these tests because the essential part of the behavior of model, the thing that I am most worried about breaking is the update. And even though right now there’s only two things to be update, over time there’s going to be more and more of them. And that’s where we’re ultimately headed.

So now when I go back to my picture, I’m feeling a lot better about this. And I’m just going to check it off. And that leaves us looking at the view, which I have to say, I’m pretty confident about our earlier decision about the view. This view does no complex logic. It really doesn’t. It simply hands everything it does right off to the model and lets the model decide what is going to happen. And because it feels so much like just a straight forward data statement, and also because it is so comparatively expensive to write tests here, I’m actually perfectly happy.

So going back to our view, what is the responsibility of the view? It is really hook controls to model. And if I ask myself, well, should I be marking this guy with a checkmark or not? He has zero tests. That’s why we didn’t look at any tests. I’m reminded of Hank Aaron’s reaction to Barry Bonds breaking his home run record. Bonds was later accused of steroid abuse. And Hank Aaron said he was OK with Bonds breaking the record, but that there was no asterisk attached to his entry in the Baseball Hall of Fame. Instead of a check mark, I’m going to draw a little asterisk and let it go from there.

So we’ve done a pretty good job here. We just did a very tight detailed review of our tests. We got three checkmarks and an asterisk. I’m happy with what we’ve got done, but what’s next? I still don’t have a game. What’s the most important part of a game? Well, I’m competitive, you know. The most important part of the game for me is going to be the score. Next time around, I want to be able to score these dice, maybe not in the full fancy Yahtzee way. And I want that score to show up on my screen. Furthermore, I want to be able to run more than one round. We’ll see if we can squeeze both of those into one segment next time we get together. I’m GeePaw. Thanks for hanging out. It was fun.

close
Want new posts straight to your inbox once-a-week?