.. _test-driven-poolballs: Test-Driven Poolballs ##################### .. include:: /header.inc .. vim:filetype=rst spell: We looked at the code needed to get your simulation running, but we did no testing in the process. Let's do that again, this time with testing in the spotlight. Where do we even start? As soon as you start thinking up your new classes, you should be thinking about tests. In ``Test-Driven Development``, you write a test and make sure it fails. Often it will fail because the code you write depends on a class you have not implemented yet. So, we will skip that first run. Instead, as we build our class, we will add tests that check our implementation code - before we write that code! Testing the Pooltable ********************* Let's write some tests for the ``Pooltable`` class. As shown before, we will be defining the table dimensions in the constructor for this class. Let's see that those get set properly: Here is a first cut at the class header file: .. literalinclude:: code/tdd/ex01/include/Pooltable.h :linenos: :caption: include/Pooltable.h Not much here. Immediately, I see a problem from the tester's viewpoint. If we set the dimensions, how will we figure out if they got set correctly> One solution is to add methods so another part of the program (specifically, they testing code) can access those attributes. C++ provides a way around this issue by allowing you to write classes or functions that you declare as "friends" of the class with private features you want to let them access. I have not figured out how to get Catch_ to use this scheme properly, so we will just set up accessor methods: .. literalinclude:: code/tdd/ex02/include/Pooltable.h :linenos: :caption: include/Pooltable.h Now we can write our first test: .. literalinclude:: code/tdd/ex02/lib/Pooltable.cpp :linenos: :caption: tests/test-pooltable.cpp We need some code in an implementation file to pass this test: .. literalinclude:: code/tdd/ex02/lib/Pooltable.cpp :linenos: :caption: lib/Pooltable.cpp Now, we can try this out: .. command-output:: make clean :cwd: code/tdd/ex02 .. command-output:: make :cwd: code/tdd/ex02 .. command-output:: make test :cwd: code/tdd/ex02 That was too simple, but it moves us forward, just as we want. Testing Poolballs ***************** Next, we need a basic ``Poolball`` class. The code is very similar: .. literalinclude:: code/tdd/ex03/include/Poolball.h :linenos: :caption: include/Poolball.h Now we can write our first test: .. literalinclude:: code/tdd/ex03/tests/test-poolball.cpp :linenos: :caption: tests/test-poolball.cpp We need some code in an implementation file to pass this test: .. literalinclude:: code/tdd/ex03/lib/Poolball.cpp :linenos: :caption: lib/Poolball.cpp Now, we can try this out: .. command-output:: make clean :cwd: code/tdd/ex03 .. command-output:: make :cwd: code/tdd/ex03 .. command-output:: make test :cwd: code/tdd/ex03 Once again, our tests pass. I like seeing that number of tests passed go up! Array of Poolballs ****************** Lets go back and set up an array of Poolballs. We need to modify the header file for the ``Pooltable`` class: .. literalinclude:: code/tdd/ex04/include/Pooltable.h :linenos: :caption: include/Pooltable.h That is a pretty simple change. However, now we face a question: how will we make sure the table now has the correct number of balls to play with, and each one is in the right spot? We have already tested the default constructor for the ``Poolball`` class, so they will be properly initialized. C++ automatically calls that constructor for each ball when it builds the array. The problem with that is simple. Every ball is in exactly the same location, which is not reasonable. We need to add another method to the ``Pooltable`` class whose job is to place the balls somehow on the table. That is a job for the ``placeBalls`` method! .. literalinclude:: code/tdd/ex04/lib/Pooltable.cpp :linenos: :caption: lib/Pooltable.cpp .. note:: example code for the ``placeBalls`` method was shown earlier. To check that each ball moved properly, we added a few more methods that will let us determine the current location of a specific ball: .. literalinclude:: code/tdd/ex04/tests/test-pooltable.cpp :linenos: :caption: tests/test-pooltable.cpp Now, we can try this out: .. command-output:: make clean :cwd: code/tdd/ex04 .. command-output:: make :cwd: code/tdd/ex04 .. command-output:: make test :cwd: code/tdd/ex04 So far, all we have done is verify that all balls in the array are sitting in the same position. The next step should add code to the ``placeBalls`` method, and test code to confirm that placement. Proceeding with the development ******************************* I am not going to show all th steps needed to complete this project, you have seen enough code to explore that on your own. We do need to talk about the overall test plan though. Ball Movement ============= Making the balls move it an obvious next step. The table will do that job, looping over each ball in the array. We can add a ``move`` method to the ``Poolball`` class, and test that easily. Bouncing against Walls ====================== Adding the logic for testing wall collisions will require setting a ball near each wall in the test code, them moving it and confirming that the required velocity flipped signs. Checking for Ball to Ball Collisions ==================================== This one is hard, since there is a lot of math involved. Testing this adequately needs a number of test situations, probably more that we need to include in this project. Still, you should test a few situations. Remember, testing is to increase your confidence that things are working correctly in your code. Testing Graphics **************** Notice that we have said nothing about testing the graphics aspects of this project. It is possible to test what an application looks like on the screen. But that kind of testing is WAY beyond this first course. We will not be testing anything other than basic object attributed here. Controlling the Animation Loop ****************************** In my testing of the ``Poolball`` simulation, I discovered that the code ran much faster on my PC than it did on my Mac. Exactly why was a bit of a puzzle until I started digging into how OpenGL/GLUT actually work. The glutMainLoop Routine ======================== The basic logic of the ``glutMainLoop`` routine looks like this: .. code-block:: c void glutMainLoop(void) { while(true) { if(content_of_window_has_changed) { drawScene(); if(keyboard_or_mouse_event) { handleKey(); if(nothing to do) { animate(); } } } You provide the three functions defined in this loop. Each pass through this loop creates something called a "frame", a term that dates back to the early days of movies. The "frame rate" is the speed of the loop in "frames per second", or ``fps``. Typical numbers for this speed are in the 20-30 fps range. Since we have not done anything to alter how the main loop runs, we do not really know how long it will take to get through the loop until we run the code. Depending on how things are set up on each machine, speeds might be different. Controlling the speed ********************* GLUT_ provides a way to control things. There is a function in GLUT_ that we can call to get the amount of time that has passed since the call to ``GlutInit`` was called. The value returned is in "milliseconds" (1/1000 seconds). .. code-block:: c double start_time; start_time = glutGet(GLUT_ELAPSED_TIME); This function can be called whenever we like to see how long it takes to do something. For example, in out ``animate`` function, we could do this: .. code-block:: c void animate(void) { double start_time, stop_time, duration; start_time = glutGet(GLUT_ELAPSED_TIME); ; table.check_collisions(ball); glutPostRedisplay(); ; stop_time = glutGet(GLUT_ELAPSED_TIME); duration = stop_time - start_time; fps = 1.0/duration cout << fps << endl; } Wasting Time ************ Once we know how fast our code is, we can slow things down so they work better. We pick a desired frame rate (say 60 fps), and calculate how much time we need to waste to get that rate. A ``pause_ms`` routines has been added to the library that can use used to delay your code a fixed number of milli-seconds (1/1000 of a second). There may be cases where you have so much work to do that you cannot get things done in time. In this case, you are not going to waste any time, and things will just slow down. You might be studying your code to find ways to speed things up in this case. We can modify the main code in the program to see how things work. Adding an output statement in the ``aninate`` loop will show you how fast your loop is actually running. From that, you can add a pause with an appropriate number. I will let you experiment with that if your animation is running too fast. There is a problem with this, though. The actual time it takes to get through the code in the animate function may actually be extremely short, far shorter than one millisecond. So, we get a duration of zero, which does not help. However, if the code is that fast, we can ignore the time it takes to do our work, and simple calculate how long we should take to get through the loop. If we want a frame rate of something like 60 fps, the amount of time we want to waste in each pass is 1/60 second, which works out to 16.6 ms. Try adding this delay in your loop and see if it works. Wrapping Up *********** This lecture is incomplete, but shows you how to approach development using testing to drive the process. To complete this adventure, you need to add in the missing methods in the classes specified for this project. The missing code is already in previous lecture notes.