A good way to approach the implementation of a modest size program is to start at the lower levels of the structure chart and work your way up, testing each component as you complete it. Looking back at the structure chart for our simulation, we could start with the gameOver function. Once this function is typed into a module file, we can immediately import the file and test it. Here is a sample session testing out just this function.
I have selected test data that exercise all the important cases for the function. The first time it is called, the score will be 0 to 0. The function correctly responds with 0 (false); the game is not over. As the game progresses, the function will be called with intermediate scores. The second example shows that the function again responded that the game is still in progress. The last two examples show that the function correctly identifies that the game is over when either player reaches 15.
Having confidence that gameOver is functioning correctly, now we can go back and implement the simOneGame function. This function has some probabilistic behavior, so I'm not sure exactly what the output will be. The best we can do in testing it is to see that it behaves reasonably. Here is a sample session.
>>> import >>> rball1 (13, 15) >>> rball1 (15, 11) >>> rball1 (15, 11) >>> rball1 (11, 15) >>> rball1 (4, 15) >>> rball1 (1, 15) >>> rball1 (15, 3) >>> rball1 (15, 0) >>> rball1 (9, 15) >>> rball1 (6, 15)
.simOneGame .simOneGame .simOneGame .simOneGame .simOneGame .simOneGame .simOneGame .simOneGame .simOneGame .simOneGame
.5,.5) .5,.5) .3,.3) .3,.3) .4,.9) .4,.9) .9,.4) .9,.4) .4,.6) .4,.6)
Notice that when the probabilities are equal, the scores are close. When the probabilities are farther apart, the game is a rout. That squares with how we think this function should behave.
We can continue this piecewise implementation, testing out each component as we add it into the code. Software engineers call this process unit testing. Testing each function independently makes it easier to spot errors. By the time you get around to testing the entire program, chances are that everything will work smoothly.
Separating concerns through a modular design makes it possible to design sophisticated programs. Separating concerns through unit testing makes it possible to implement and debug sophisticated programs. Try these techniques for yourself, and you'll see that you are getting your programs working with less overall effort and far less frustration.
Was this article helpful?