TDD for Tic Tac Toe using Java (part 3)
In previous episodes:
Cheers everyone. These days I am working on a project which has lots of scientific stuff under the hood - finds best route between point A and point B depending on various circumstances. So what you need to do is to evaluate lots of possibilities, build graph, measure coefficients...And it is something certainly which is horrible thing for manual testing by definition.
And still we had some kind of disagreement about what approach we should take to ensure quality on this project with project tech lead - her suggestion was to do heavy manual testing cause "TDD is not applicable on such kind of projects", mine was that unit-testing may be the only way to create basis for a good QA on a project, and TDD is probably the only way to guarantee we do unit-testing properly.
So, just for lulz, I decided to play with idea a little bit and chose some kind of similar application to work with - Tic Tac Toe game. One of the possible solutions to make computer play against you would be evaluate possible moves, build graph, measure coefficients (sounds kind of familiar, ah?).
In this and following part we're going to add some interaction with user and allow two players to play with each other. If you haven't read first part you can read it here. Second part can be found by here.
In the second part we stopped on adding InputListener, but we never touched printing logic here - method showBoard(). As for our ConsoleBoardView, all we want is to print board, this test should be pretty easy to add. We will immediately notice that there's no constructor to pass out test OutputStream, which is also not going to take long. Second news would be that View doesn't know anything about Board yet. Here we have two options, one is to add a link to a Board to our View, or make controller responsible for passing Board to View when necessary. I choose second option (I didn't think for too long so may it be absolutely wrong approach).
Ok. after giving it another thought I actually think it was wrong, because we in fact make our Model responsible for printing (toSting() method), but I will leave it as is for the current example sake.
Code looks a little bit dirty and smelly, but this is alright - in TDD you make test pass first, then do refactoring, if needed. With implementing this method we completing our View and should be able to start working on Controller. As always, I will start from constructor and I want it to take view and model as a parameters.
Now we have a Controller guy, and we want it to listen for the UserInputEvents, and should they happen - consolidate changes in Model and subsequent update of a View. After thinking for a while I decided to add another assert in a testConstructor(), checking that there's not null UserActionListener, and, moreover, that it was injected into a View. My changed constructor is shown below (Notice the wrong type that wasn't caught by my test - to avoid that completely I should have been adding an assert for type of the object returned by getter. This error, however, will be spotted as soon as I start writing tests for UserActionListenerImpl itself).
At this point we are ready do to the real magic - implement actionPerformed() method, which will do most of the Controller's job. Whenever action performed, we want our Controller to do the following:
- Update Model according to the move made
- Update the View according to the Model changes
- Listen to actionPerformed() event which has some UserAction parameter
- Based on that parameter see which move was made by user and update Model according to the move made
- Update the View according to the Model changes
In order to proceed with actual implementation we need to think more about UserAction class. So, I will actually move into UserActionTest class and write tests for UserAction. When I am thinking about user actions, there some that I can imagine to happen:
- User makes move
- User decides to start new game
- User decides to quit
And all these options may be very relevant to our controller - in all cases we need to do something with View and Board. After thinking about how I can represent those actions, I decided that inheritance my do the trick. So, for instance, what if I create abstract UserAction class, which has three ancestors:
Works perfectly! But would not work that good for the other actions. It turns out, that only UserMoveAction needs some parameters, while others just needs to be taken care of. So, instead of type hierarchy, I am introducing three methods in UserActionListener:
And also getting rid of all UserAction class hierarchy. Please note how shorter the code is after refactoring.
Next step would be implement newGameActionPerformed, which just cleans board. The test is pretty straight forward, and implementation is super-druper easy (essentially just copying a constructor code):
I will just skip quitActionPerformed() for now, as I don't really know how I want to do quit in an end program, so I am not ready to do this yet.
Now we have to go back to View though, and teach it to listen to user actions, as somebody should really trigger those Controller's events, and in MVC it most likely to be the View's job. In typical GUI application, View will silently wait for user to click or type something. In command line it is not going to work that way, so we will have to implement that part of a logic ourselves. Naturally, I am thinking about the implementation as of infinite loop, but the real question is how I am going to test that. So, from usage perspective, I am thinking about having something like render() method, which will wait for user input and then show current board state. Now things become trick because it is not that easy to test method which you can't really interact with. So below is what I had came up with.
.....And it is pretty much going to be about it. I mean, the program is not yet finished and I am going to continue working on it and then publish summary with my thoughts about the process. However I see that current format of the series is not working - I should have just done video recording. I will do this next time, but it is too late.
Thank those who followed me (I know there were not too many followers) and I hope you won't have disappointment after I publish the summary - this experiment was fun and I would call it successful.
Bye for now, see you later.