Real Programming S01E08: Tighter, Tighter
That cool change from last time needs finishing, and let’s tighten down the tests & code to their minimum.
If you want to follow along in the code, the project is at github.
Hey, welcome to Real Programming, the show where the slogan– we’ve had a little bit of a problem. Apparently, when the team heard us say that we wanted a burn down, they took it literally. It’s OK. We’ve got backups. I’m sure we’ve got backups. I hope we’ve got backups.
Anyway, the last time out, what we did was we smooshed things around so that we use an event bus to sort of transfer our events around to the background in a way that seems like it’s going to be better. But we didn’t really finish that. So let’s take a look and see what we can find out.
So this is in our model, and what you can see going on here is that we’re handling PipChange events, but down here in the update, we’re still manually fixing can roll using the update function. I want to get rid of that Update function all together, which means I need a canRoll event or a canRollChange event. That’s pretty straightforward and easy to do. We’ll do it the same way we do last time.
We could start, if we wanted, with changing the game so that it sends the event, or we can start with the model so that it catches the event. Let’s do the model. This test will look something like this. Notice it’s almost identical to the PipsChange events. Instead though, we’re forcing a canRollChange of true and then asserting that the value is equal to true.
We don’t have canRoll change. Let’s go ahead and put that event over in the game for now, and see how that goes. It’s as simple as making that declaration right there. canRollChange, it takes a Boolean value. We can say canRoll true, canRoll false. Back in the model test, I’m thinking when we run this test, we’re going to fail. And of course, we do. That’s because the model starts with that value set to whatever was in the game as false, and even though he gets this event, he doesn’t handle it at all.
So we need a handler, and we need to probably change that starting event thing to be hardwired as well. Here’s the hardwire part. We’ll just guarantee you that it starts false. Now, we need a new event handler, only this time, instead of handling a PipChange, we’ll handle a canRollChange. There’s that handler. Let’s run our tests again. Nice. Now, they pass.
Now, at this point, we should be able to get rid of update. All it was doing before was controlling that canRoll value. So if we get rid of it, let’s see what happens. Stuck me because of the update. So now, take a look at this. What we’re seeing here, these tests are the old-style tests. They don’t deal with the event-handling model at all. They were expecting us to reach inside the game, and we don’t need them. We can just get rid of them. Bam.
I like how much simpler my model is and how much simpler my tests are for my model, and we’re all in green, except, of course, we’re not sending that event, so maybe we should and test for that over in the game test. Now, when we get to YzGameTest, what we see is, we don’t actually have anything yet that handles events there. How did we do that before? I think it was over in the dice. Let’s check it out.
Oh, yeah. That’s right. We made this bus. We initialized the bus or we used the bus, and we made ourselves capture it, and then we implemented this little business. So I am wishing to myself that this code was already extracted somewhere into a separate little test class or helper class that we can use repeatedly.
I’m not going to do that yet. I’m going to copy paste this bad boy for a second. Jiggle it a little. And then after we get our next test passing, then we can go back and unify that code again. Sometimes I like to go ahead and let the split happen just to see what is the real difference. If I try to write it as narrowly as possible in one context and in another, can I then still unifying or not?
To move this over, we didn’t have to use a custom bus because the game already has a bus inside it. So all we have to do is say game.bus.register instead of an arbitrary bus.register. Once again, we’re handling any, and we’re stuffing things into events, and now we should be able to write one of those nice little event tests.
So now we’ve got this little event bus in here. Let’s get a test to see that we get a canRoll true anytime we do a start. Looks like this. We call start. Now, after we call start, the events is going to have some events in it in theory. We want to make sure it contains an event for canRoll true. Of course, it doesn’t, so let’s go make game do that.
So I went ahead and implemented it by using an extracted method here, canRollChange, yes or no, true. I went ahead and put the method in rather than just doing this in line. I’m pretty sure we’re going to want canRollChange for another purpose in just a split second. Let’s see if this gets us through our test. And it does.
So here’s that split second I was just telling you about. We want to test that it sends canRoll false if we’ve rolled it three times. That looks like this. When we run this guy, it should fail, and sure enough, it does.
So what we want to say is, rollsInRound plus equal to 1, if the rollsInRound is now 3, then we want to canRollChange false. Let’s see if that takes care of us. Now, that’s what I’m talking about.
Now, again, let’s take a look at these tests because we’ve got extras here. You can’t roll the dice at the start. Well, there is no more need for this game canRoll variable, that’s my belief anyway, so there’s no need for this test. Let’s prove that thesis by deleting good old canRoll and seeing what we get out of it.
We’re going to get some errors, of course. If not canRoll, OK, let’s don’t delete it. Let’s do this. Let’s leave it alone, but let’s make it private. Doesn’t like that because he’s breaking these tests. Now, I’ve deleted most of them, but this one, start after three rolls, allows a roll.
Let’s see. Well, we know, we’ve already proven that we send a canRoll on start, so in fact, I think this is taken care of as well, and we’re passing our tests. Cool. This is going swimmingly. Now, an interesting thing here. So I use this repeated technique here to grab the events out of a bus. I think that I would like to do this in some other way than just copy pasting it around all over the place.
So what if we had a guy who was a specialist in doing exactly this, handling in either events and then giving me some sort of container class that I can use in order to get things right. We have two different use cases here, and in this case, it’s game.bus.register, but over here in the dice test, we actually used a manual bus.
Well, now, that’s interesting because why? Why use a manual bus? Oh, because we were supposed to be initializing the dice using the bus from the game. That will, I believe, fall out over time, but for now, what I want to do is give myself a testing bus capability of some kind.
Where’s that guy? Testing bus isn’t a great name for it. How about an EventCatcher? Let’s try that. We’re going to put him on the Test folder. He’s not part of the Shipping app. Let’s see what that looks like. So we can initialize him and give him the correct bus to use, and then I believe most of the rest of this is just copy pasting from our two tests.
Let’s see. Looks like it compiles, at least. Let’s get him over there and use him in one of our two tests that use the bus. To do that, we need to declare an EventCatcher, but of course, now, we also need to take care of these two references to events to use the catcher’s event queue. Let’s see if these tests still run after we do that. Of course, they do because that’s just how good we are. You know what I’m saying? Huh? Huh? You know what I’m saying?
I’m going to do something. I’m going to rename this. I already don’t like this EventCatcher name. So my refactoring itch is bugging me here. I’m OK with this name EventCatcher. I am not OK with this variable name EventCatcher. What else is being caught? Only events. They’re the only thing that are being caught. So that makes me think, what if I did something a little different. What if we, first of all, rename this to be, let’s see, it’s a sink. Cool.
Next, I look at EventCatcher and I say, well, then that’s an event sink, isn’t it? The sink terminology is coming from sink and source, which are pretty common in these sorts of environments. One more piece of this, man, I wish I could make sink act like the list of events. Ah, you know what, I believe we can.
All right, look at this. This is a messy syntax, but boy, it sure does lead to a tight API. In addition to getting a bus, we also get a base. What is a base? It’s a mutable list that can hold anything, and if you don’t give me one, I will invent a mutable list right on the spot and assign it to it.
Now, what I say, and this is cool Kotlin syntax here, is that implement the list of any interface– notice, I didn’t need to make a mutable list; I don’t want to be putting events from here unless I’m already inside the code– by using base. Wow. That is slick, isn’t it? So now, I’ll be able to take an EventSink object and treat it exactly as if it were an ordinary list, which will greatly simplify my syntax.
Down here in the Subscribe, I didn’t have to change this. I can’t add it to myself because myself is a list of any, and you can’t change a list, but I can add it to base, and that’s why I made this guy a private vow. Now, that’s going to break YzGameTest because we’re dereferencing events here. But we don’t need to dereference events anymore. The sync itself is a list.
That fixes the syntax error. Let’s see if we still are running our tests. Sweet. All of our tests are still passing. I like this. This is slick. Now, this is some pretty advanced stuff. I don’t necessarily need or want to use it all the time, but in this particular case, what I’m really doing is, I’m greatly simplifying my ability to grab all the events and then inspect all the events and to do it very directly and simply in my test code.
So I’m pretty happy with this new API. And even though it involves this complicated little bit of code right here, I think it’s pretty powerful. Let’s take this concept that we used here of unifying this sink out. And we hadn’t changed our dice test, so let’s make our dice test use the same technique.
So we’re going to change this type of events to an EventSink of type bus, which means that this assertion and any other assertion that goes against the events will still work just fine. But because we’re using the EventSink now, we don’t need this initialization, and we don’t need this handler. Let’s see if that gets us all the way there. And indeed, it does.
So there is actually a little bit of a lesson in that. I don’t want hard-to-read tests, ever. I want my tests to be easy to read. So if I can make an API change like that clever one that we made just now that makes everything easier for me to read in the test, well, I like that a lot.
An issue. I’m pretty happy with this code right now. Operationally, I believe everything is perfect, but I’m not as happy with the tests. So remember, that we’re really doing two apps in one code base. There’s the Shipping app– that’s the one I’m reasonably happy with right now– and there’s the Making app, and that one, I’m not fully satisfied with yet. Why not?
Well, it’s because of those tests that we deleted. Did I accidentally delete something I shouldn’t have, and are there more possible tests that we should be deleting, and are there more tests we should be adding? Let’s take a look. Let’s go all the way back to dice tests and make sure it does everything that the dice do.
Here’s DiceTest in all its glory. The first thing we did was we establish that the dice sends PipChange events. That’s great. We also established that the dice as pips all start out as unknowns. I’ve got no problem here with that. Now, the fact that the dice change during a roll, well, this test is very similar to this test, but not exactly the same.
Here, what we’re testing is that the dice send out events, whereas down here, what we’re testing is that it changes its internal values correctly. So they’re very similar, but they’re not exactly serving the same purpose, I’m happy. What about this reset test here? Reset resets everything to unknowns. dice.roll would have trashed all the values. Reset clears them. We look at it.
Now, let’s go look at the dice themselves and see if there’s any other functionality in there right now. Well, gosh, I look at it, and there really isn’t. I mean, we’ve dereferenced the pips for our tests. We’ve established that we actually post our changes. We’ve done a reset. But here’s something that we didn’t catch, did we?
Well, when I do a reset, not only do I assign something to the pips.die, but shouldn’t I also be sending PipChange events? Well, that’s something, isn’t it? We set the dice change during a roll. Dice sends PipChange events, but we didn’t set the dice change during a reset. Well, kind of we did. This catches it, but it doesn’t test for the events.
So in the same sense that these two tests form a pair, it feels like there might be a missing guy here. Let’s put that guy here. I changed the name here to roll, and I copy pasted it down here to reset. What’s the difference between these? In this case, these are all going to now be unknown events.
So now we have these nice two pairs of tests that are both about changing the values for the dice as well as asserting that there are change events. Let’s see if they pass. The dice do not send change events on a reset. Well, we added a test. We found a bug. I love this country.
And I believe I can fix this just by literally copy pasting this. Let’s see. Much better. But I just literally copy pasted this too, didn’t I? It’s a lot of syntax. What if we said– let’s give ourselves a function here. So when I did that, I noticed that it really actually does the same stuff. roller.roll is different, but reassign each time to pips die something. That will be the value that we assign. And pip sub die is itself the index.
So let’s swap that in and see what we get. When we do that, this actually simplifies a little bit, changePips die, roller.roll. This guy says changePips die unknown. changePips has these old two values, making an assignment to the internal and posting a value out there in the world. Let’s run our tests and see if we get away with that, and we do.
So that was worth it with respect to dice and dice test. Let’s go to game test and see how that goes. Sends a canRoll true on start. It sends canRoll false after three rolls. OK, that seems pretty legit. One interesting aspect of this is that this guy is a list, and he actually contains all the events. If sink was a mutable list, I’d be able to say sink.clear.
Now, what would be the effect of this? Well, it would dump everything, and then do the last roll. And that last roll would leave us with– well, it would leave us with PipChange events, but it would also leave us with only one possible canRollChange whose value would be false. That’s one way to do it. What’s another way?
Well, what if I just look at the very last event? We would do that like this. Assert that sink.last. Remember, in addition to being a clever EventSink, sink is a list. Last comes for free with the list class. Sink.last is equal to canRollChange. I like that. Do we still pass? Yes, we do. That makes me happier. Now, what else do we have inside game test?
Now, what about this last guy, rolls change the pips? Well, we know that dice.roll changes the pips, and we know that games pips objects is just delegated directly to the dice pips. Not entirely sure I need this. Let’s check out, first of all, who’s using game.pips.
Now, in IntelliJ, that’s as simple as Control-B. It’s the only usage of game.pips is in this test. So if I go back to game and delete pips, I delete this. Test would go away, and I think I’m OK with that because outsiders now only detect pip changes. They never inspect the pips specifically. So it isn’t so much the rolls change the pips isn’t being tested for, it is. We test that in dice.pips.
This is testing that rolls change the game pips. If we don’t need game pips, we don’t need this test. So we’ll kill off game.pips, that will instantly break our test, and we’ll kill off that test. Twice now, by doing this process of looking at our tests, we have identified some useful changes. Let’s go take a look at the model test.
Wow, the model test just has two items in it. He handles PipChange events correctly. He handles canRollChange events correctly. Is model really that simple now? Kind of. There’s simple delegates, roll and start. There’s nothing fancy to these guys at all. There’s the dice that are made visible here. Oh, they’re really not. These are die models. These are chunks of the dice.
An earlier question I had, and I’m not going to handle this right now, but I wondered if I really needed a dice model here. I don’t know, you know what I mean, to collect up this array and wrap it in some form. I’ll have to think about that and get back to you, but for now, I’m going to leave it there.
I know I’m registered to the event bus, and I think we’re pretty good here. Let’s go back and look at the test one more time. Is there anything going on I don’t test for? I don’t think there really is. There’s the fact that it has dice, but that’s just basically a simple data declaration. It’s used by handling the pip changes. There’s the direct delegations of roll and start, again, with the canRollChange. The only curious part of this so far is just the dice, and I think we’re going to leave those alone for now.
So we’re in pretty good shape, I think. I’m much happier with the Shipping app. It’s simpler. It’s easier, but at the same time, I’m happier with the Making app. It’s also simpler and easier to understand. Still have a little wobble around dice and die model. We may have to think about that a little bit for next time.
So today, we had a good time, right? Quick like a bunny. This was a fairly short episode, but we actually got a lot done. We moved the canRoll mechanism over to our new event bus architecture. Got it working just fine. We took a close look at each component of the system so far. Of course, it’s still a very simple system at this point, and we mapped it to its tests. And we satisfied ourselves by adding, actually, a couple of tests and deleting a whole bunch more that we had actually covered everything that we have done up until this point.
So I’m pretty happy. I’m looking forward to having– I feel sure we’re going to have some sort of news about the slogan next time and for running a little spike on this UI action. Thanks for watching. We’ll see you soon.