Real Programming S01E02: Look, Ma! A Test
Last time we did a rapid-fire model/view split, just so we could test. Now we gotta test this bad boy a little!
If you want to follow along in the code, the project is https://github.com/geepawhill/yz
Hey, it’s GeePaw and I still have Wally, and Molly’s off camera, and we’re back with Real Programming: the show that still doesn’t have a slogan, but that’s OK. We’ll keep going. Anyway, when last we left you, we had just done our first episode, and I tore ass through very quick models view split. Today, what I wanted to is, well, I gave the rationale, but I didn’t give a lot of detailed explanation.
The rationale, model is testable and view is not. So for today’s first shot, we’re going to go ahead and just get ourselves some tests. The first test that occurs to me is when I call a YZ model right from the beginning, what happens is you can’t roll the dice until you’ve done a start. Well, let’s test that out, shall we? OK. This is the model, and it’s got this member in it, canRoll. It’s a Boolean binding, and with all these bindings and properties, by default, in order to get to the value, you have to say what is your value.
So this is what the test looks like. Assert that model dot canRoll dot value is false. So a couple words about this test. This is just JUnit. There’s nothing particularly special about it. It looks weird to you because it’s Kotlin. So in the beginning, I instantiate a model and all I’m doing in the test here is assert that the model canRoll value is false. Now this assertion may look a little odd to you. This is an assertJ assertion, rather than the normal Hamcrest matchers because I like a assertJ better. It feels like a better system to me for specifying my tests.
So we got this test, let’s see what happens. Yeah, that’s what I’m talking about. Testing, testing. OK, so for the next shot, here’s the thing. One, I have a hole right now. I’m going to come back to that hole. I’m going to go down the main path and then I’m going to come back and retrofit one extra test. Let’s go down that main path a little bit further. Once you have called start, you now canRoll. So let’s get a test for that. OK.
So as so often happens, I thought I was going to test one thing, but I wound up testing another. Once I tried to say canRoll three times after start, what happened was I basically realized, yeah, that’s a little too much for me right now. Instead, let’s just say you canRoll right after a start. And again, we’ll give it a try, and Bob’s your uncle. For my next shot, let’s try rolling the dice and asserting that the values of the dice change. OK.
I got this far. Model dots start, and then for every die, it should change. But what does that mean to say that it should change? Well, if it’s going to change, the default model starts out with the dice one, two, three, four, five. Those are all legal dice value. Hmm. Well, what if I hacked that? What if instead of saying, they all start out like that, we instead change it all the zeros. Now, I can say it’s not equal to zero. It’s not really throwing me as a test, but it’s a start. Ooh, didn’t work, either. Oh, because I never rolled it. There you go.
And we’re good. Let’s think about that for just a second before we proceed, but it feels like another hole to me. I haven’t really accounted for the fact that there are times when the did have not been rolled at all. But maybe, that is ultimately the meaning of zero. We’ll come back to that near the end today. For my next trick, you can actually roll the dice three times, but now, we see a hole because right now, there’s nothing that would keep me from calling role, whether or not canRoll is true. Ooh, that’s interesting.
So this test up here said it’s against canRoll, but it doesn’t say anything about whether or what happens when you actually try to call the roll. Why is that? Well, it’s because of the implicit knowledge that I have. The implicit knowledge I have over here in main view is that the only time model dot roll gets called is when model dot canRoll happens to be true. All right? So that’s fine, that’s explicit information here in main view, but it’s not explicit information in YZ model. Hmm. Let’s change that. Let’s make sure it is explicit.
And now for the first time, I will actually be test driving a function. What I’m going to say is, if you try to roll it and you’re not in a canRoll state, well, incredibly boldly and bravely, we’re just going to throw an exception and freak right out. Let’s see what that test looks like. So here’s the resulting test, right? Throws on an unallowed roll, assert throws runtime exception model dot roll, which is to say this guy should be throwing some sort of runtime exception when the model is rolled illegally, which it would be in this state because we haven’t called start yet in this little fragment.
So let’s run it and see what happens. Excellent. You see what just happened there? Test-driven development. I wrote a test. The behavior isn’t there so the test fails. I need to go fix up that behavior. Let’s go do it. OK. There is our change, right? Pretty straightforward. If not canRoll value, then throw runtime exception illegal roll call. We’ll talk maybe later about whether that should be a runtime exception or not. And now, can’t roll the dice on a start.
Now that’s handy for me to add as proper test-driven development, but what’s especially good about it is it allows me to trust that canRoll and roll are tied together. So I can’t get in any trouble by calling roll even if canRoll says not because the system will throw an exception at me. So now having done that, what can I do about testing that I can roll three times and then I have to call start again? There’s the test, it just does the start, it does roll, roll, roll, and now, the canRoll should be false. Let’s see what happens.
Cool. Looking pretty good. So now, we’ve got one, two, three, four, five tests around this. Does the start reset after a roll? Well, let’s give it a try, just in case. I’m going to quick like a bunny copy this test and just tweak it a little bit. OK. So here’s this test. Start after three rolls allows you to roll again, and it’s exactly the same as this test except it has two extra lines. Let’s think about that for a second. But first, let’s see whether it runs or not. Sure enough, we’re good to go. Model dot start, roll, roll, roll.
Here’s an interesting question, do I need this assertion? I’ll tell you, it’s always a judgment call. It would be possible to break this test in isolation by making it always legal to roll. Right? And that’s why I have this assertion here, but without this assert, look here, we already have tests that assert that you can’t just roll in any old time you want. So my argument here is that the other tests will catch this. This is a judgment call. There’s a part of you wants to be as black box, as opaque about the code as you can be.
But in this case, you know, it’s not opacity of the code. I’m not pretending not to know what the code does. Instead, what I’m doing is actually knowing perfectly well what the rest of those tests do. OK. That’s really it for this particular shot. Looks very modest. Right? In just a few minutes, we banged out six tests against this model object that we created. And so you might think to yourself, well, wow, it’s easy when it’s a toy. Well, first of all, yes, it is easy when it’s a toy. No question about that.
We’ll talk about how hard it can be later when you start with a project that isn’t already TDDed or isn’t in good shape. But one of the reasons, of course, that it was easy is because I made that split in the very first episode. So you want to think about that. Now, we did write these however many we wrote, five or six tests. Now an interesting aspect of all this, you know, so we wrote those tests, that handful of tests, they’re very cheesy and, yet, in six tests, we made two design decisions. Do you remember them both?
The one design decision we made was to make sure that the explicit aspect that you cannot roll when it says canRoll is actually implemented directly in the code. Why did we do that? I mean, after all, you’ve got another class guaranteeing that it never gets called if it hasn’t been started yet. The reason we did that is because we’re building for change. And there is no guarantee that another user, another view might not use this model at a different time in a different way, and we want to be sure that if I start tweaking the model, I maintain the behavior that everybody else depends on for that model which is that you cannot roll if you have not started.
Second design decision, we decided that the starting number of pips for a die that has not been rolled is zero. Didn’t make a big deal out of it, but that’s an interesting decision, and it may have some complex properties later. We’ll have to discover that as we go. At this point, I want to do really two more things before we’re done for the day. One is I want to take my time and really make sure you understand some of the Java FX-isms that are going on, in particular, that you’re comfortable with subject observer because we’re going to be using it a lot and that you understand why I did that split in the first place one more time. OK?
OK. Let’s start with subject observer, not just because it’s important in Java FX, it’s almost important in virtually any of your windowing sort of environments, and it’s important that you have a very comfortable grasp on what’s going on. All right. So let’s take a look at this. What we’ve got here is every observer pattern inevitably involves three different pieces. There is a subject of some kind. There is an abstract observer. And there’s a concrete observer.
And the idea is this. The subject is made to depend on his understanding of the abstract observer which is usually just an interface. At the same time, the observer is actually derived from the abstract observer. And the basic concept is that the concrete observer says to the subject, hey, I’m watching. And when he says I’m watching, the subject now remembers it.
So that two-part initialization happens every single time. We call subject, and we say subject add something. The terminology here is fuzzy, there’s a lot of different pieces, but the gist of it is I’m saying, hey, I’m watching you. And the subject says, OK, I’ll remember that. Now, when the subject changes, his job is to go through and tell every observer. That’s it. That is the observer pattern.
Now the question emerges almost immediately, well, why the hell would you go to all that trouble? There’s a couple of reasons why. One reason is some subjects have multiple observers. And one observer may also be a changing observer. So now we’ve got some different possibilities here. If the subject just sits there and maintains a list of all of its observers, then multiple people can observe it.
Further, if the subject either changes itself or somebody who is an observer or somebody who even is not an observer changes the subject, all the observers will find out. That is the reason why we like this pattern so much. Now conceptually, implementation-wise, what it actually does is it breaks any dependency from subject to observer. That dependency is broken. It means that the subject doesn’t have to know anything about the concrete observers. It just says to know they implement this abstraction and that’s all that he cares about. If he gets that, he’s good enough.
All right. Now let’s take that basic concept of the subject and the observer and let’s see what it looks like in Java FX. The basic idea here is that every window in our Java FX screen observes one or more properties. So the property here is really the subject. And the windows are all, in Java FX, they’re called nodes. The nodes are all observers. OK?
So essentially, that’s what’s going on. Now what’s a property? Well a particular property that we use is simple integer property. And he has exactly a property that he holds an integer, although interestingly enough, under the hood, he actually communicates around it using a class that isn’t int that’s actually a number. But that’s neither here nor there. It doesn’t matter to us for the moment.
So when I have a label, a label is a kind of window which is a kind of observer. And a simple integer property is a property, which is a kind of subject. The label watches the simple integer property. And now anytime not just the label or some other window or some other player or the property itself changes that guy, the label is notified and the label can update itself without any further intervention or complication. Let’s make sure we understand that by looking at it in code, and then I have one more point for you about subject observer. OK.
We’ll start with our running our app again just so you can remember where it’s at. Notice that it now starts with all 00000. You have to say start, but as soon as you do, you can roll. And when you roll, boom, the numbers in those labels change. Now here’s the thing. What’s going on? The label is watching the simple integer property. And as it happens with all the built in properties, labels know how to do the conversion to string, so it just quietly converts it to string for rendering itself.
And because he is observing that property, if you change the value of that property, you’ll get notified and he will update himself. And it is as simple as that. That’s how subject observer works in the world of Java FX. Nearly always, you’ll see terms like property, which is an observable. There’s an even superclass called observable. There are also observable containers like lists and maps and things like that. And in general, the Java FX controls know how to watch appropriate properties in order to do their work.
So that’s the gist of subject observer. But I got one more thing to show you here, and that is up here. What is the deal with this? So first of all, first thing you need to understand is that all we’re doing here, really, is with that canRoll thing, he happens to know whether we’ve rolled three times or not, whether we’re allowed to roll at any given time. Enable when sets up another sort of subject observer relationship.
Enable when tells the button or any control that it happens to be inside, hey, look, this time, don’t tie your content to this particular property, but the property is a Boolean, I want you to tie your behavior to that particular property which is really a fascinating little bit of work. Because what we’re saying now is that the logic inside this for the behavior of this view is virtually 100% data declarations.
Now I don’t want to lie to you, these are not data declarations per se. Each one of these is a method with an end lambda and various default arguments and blah, blah, blah. But conceptually, this is just a simple tree that is showing us all the controls on the screen. And we want it that way. We want it that way so bad. Why? Because these guys are incredibly difficult to test. I keep saying incredibly difficult, they’re not incredibly difficult, but they’re hard, they’re annoying. And the resulting tests are both slow and flaky. That’s why we don’t want to test that.
That’s why it is so important that this guy stay as simple and crisp as possible. And that is why we immediately, as soon as anybody says to do anything about anything, we immediately jump to the model to do the work. But I do really want to call this, so I’m going to call it out one more time just so it’s perfectly clear. This guy over here, the model, he’s not dice. We are using this property, I know this doesn’t look like a property, but it’s just like the other observable that we saw, simple integer property, except it’s a Boolean.
We’re using this property in order to control the behavior of our program. So you can put things in the model that aren’t about the domain of Yahtzee, but are very much about the model that is needed to successfully render and control the view. OK. That’s today’s short shot, and it was a very small step. Nevertheless, I hope you got a feel for the fact that this is how I work. I always try to take steps that are as ridiculously tiny as possible.
Two more points to make. Second point is this. Why did I rush so hard to that model view split in the beginning? Well, today, you got to see it. It’s because I was getting to a place where I could write some tests. Now that I have some tests and now that I have a clean split between untestable and testable, I can go crazy adding more tests and adding more function capability and operation to the system.
Of course, the tests that I wrote, they don’t even actually capture Yahtzee yet. They were just goofing around, setting up that rig. And I’m happy with that. Third thing. That code’s not very good. I’m not proud of that. I just spat it right out and got it going. This is your foreshadowing for next time. Let’s tighten that up. So until then, well, have a great week, and we’ll talk to you soon. Thanks very much for watching.