# Test-Driven Poolballs¶

Read time: 27 minutes (6963 words)

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:

include/Pooltable.h
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Copyright 2019 Roie R. Black #pragma once class Pooltable { private: int width; int height; public: // constructor Pooltable(int w, int 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:

include/Pooltable.h
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Copyright 2019 Roie R. Black #pragma once class Pooltable { private: int width; int height; public: // constructor Pooltable(int w, int h); // accessors int getWidth(void); int getHeight(void); }; 

Now we can write our first test:

tests/test-pooltable.cpp
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Copyright 2019 Roie R. Black #include "Pooltable.h" // constructor Pooltable::Pooltable(int tw, int th) { width = tw; height = th; } // accessors int Pooltable::getHeight(void) { return height; } int Pooltable::getWidth(void) { return width; } 

We need some code in an implementation file to pass this test:

lib/Pooltable.cpp
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Copyright 2019 Roie R. Black #include "Pooltable.h" // constructor Pooltable::Pooltable(int tw, int th) { width = tw; height = th; } // accessors int Pooltable::getHeight(void) { return height; } int Pooltable::getWidth(void) { return width; } 

Now, we can try this out:

$make clean rm -f demo testapp  $ make
g++ -o demo -framework OpenGL -framework GLUT src/main.o lib/Pooltable.o

$make test g++ -o testapp -framework OpenGL -framework GLUT tests/test-pooltable.o tests/test_main.o lib/Pooltable.o ./testapp =============================================================================== All tests passed (2 assertions in 1 test case)  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: include/Poolball.h   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // Copyright 2019 Roie R. Black #pragma once class Poolball { private: float radius; float xPos, yPos; float xSpeed, ySpeed; public: // constructor Poolball(); Poolball(float x, float y, float vx, float vy); // accessors float getXpos(void); float getYpos(void); float getXspeed(void); float getYspeed(void); float getRadius(void); };  Now we can write our first test: tests/test-poolball.cpp   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // Copyright 2019 Roie R. Black #include "catch.hpp" #include "Poolball.h" const float X = 50; const float Y = 60; const float VX = 2; const float VY = 3; TEST_CASE("basic poolball tests", "poolball") { Poolball ball(X,Y,VX, VY); REQUIRE(ball.getXpos() == X); REQUIRE(ball.getYpos() == Y); REQUIRE(ball.getXspeed() == VX); REQUIRE(ball.getYspeed() == VY); REQUIRE(ball.getRadius() == 25.0); } TEST_CASE("tets default constructor", "poolball") { Poolball ball; REQUIRE(ball.getXpos() == 50.0); REQUIRE(ball.getYpos() == 60.0); REQUIRE(ball.getXspeed() == 2.0); REQUIRE(ball.getYspeed() == 3.0); REQUIRE(ball.getRadius() == 25.0); }  We need some code in an implementation file to pass this test: lib/Poolball.cpp   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // Copyright 2019 Roie R. Black #include "Poolball.h" // constructor Poolball::Poolball() { radius = 25.0; xPos = 50; yPos = 60; xSpeed = 2; ySpeed = 3; } Poolball::Poolball(float x, float y, float vx, float vy) { xPos = x; yPos = y; xSpeed = vx; ySpeed = vy; radius = 25; } // accessors float Poolball::getXpos(void) { return xPos; } float Poolball::getYpos(void) { return yPos; } float Poolball::getXspeed(void) { return xSpeed; } float Poolball::getYspeed(void) { return ySpeed; } float Poolball::getRadius(void) { return radius; }  Now, we can try this out: $ make clean
rm -f demo testapp

$make g++ -o demo -framework OpenGL -framework GLUT src/main.o lib/Poolball.o lib/Pooltable.o  $ make test
g++ -o testapp -framework OpenGL -framework GLUT tests/test-poolball.o tests/test-pooltable.o tests/test_main.o lib/Poolball.o lib/Pooltable.o
./testapp
===============================================================================
All tests passed (12 assertions in 3 test cases)


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:

include/Pooltable.h
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // Copyright 2019 Roie R. Black #pragma once #include "Poolball.h" const int MAXBALLS = 5; class Pooltable { private: int width; int height; Poolball balls[MAXBALLS]; public: // constructor Pooltable(int w, int h); // accessors int getWidth(void); int getHeight(void); float getBallXpos(int i); float getBallYpos(int i); // mutators void placeBalls(void); }; 

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!

lib/Pooltable.cpp
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // Copyright 2019 Roie R. Black #include "Pooltable.h" // constructor Pooltable::Pooltable(int tw, int th) { width = tw; height = th; } // accessors int Pooltable::getHeight(void) { return height; } int Pooltable::getWidth(void) { return width; } void Pooltable::placeBalls(void) { } float Pooltable::getBallXpos(int i) { return balls[i].getXpos(); } float Pooltable::getBallYpos(int i) { return balls[i].getYpos(); } 

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:

tests/test-pooltable.cpp
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // Copyright 2019 Roie R. Black #include "catch.hpp" #include "Pooltable.h" const int WIDTH = 500; const int HEIGHT = 300; TEST_CASE("basic pooltable tests", "pooltable") { Pooltable table(WIDTH, HEIGHT); REQUIRE(table.getWidth() == WIDTH); REQUIRE(table.getHeight() == HEIGHT); } TEST_CASE("test ball array setup" "pooltable") { Pooltable table(WIDTH, HEIGHT); for (int i = 0; i<5; i++) { REQUIRE(table.getBallXpos(i) == 50); REQUIRE(table.getBallYpos(i) == 60); } } 

Now, we can try this out:

$make clean rm -f demo testapp src/main.o lib/Poolball.o lib/Pooltable.o tests/test-poolball.o tests/test-pooltable.o tests/test_main.o  $ make
g++ -c src/main.cpp -o src/main.o -std=c++11 -Iinclude -Wno-deprecated-declarations
g++ -c lib/Poolball.cpp -o lib/Poolball.o -std=c++11 -Iinclude -Wno-deprecated-declarations
g++ -c lib/Pooltable.cpp -o lib/Pooltable.o -std=c++11 -Iinclude -Wno-deprecated-declarations
g++ -o demo -framework OpenGL -framework GLUT src/main.o lib/Poolball.o lib/Pooltable.o

\$ make test
g++ -c tests/test-poolball.cpp -o tests/test-poolball.o -std=c++11 -Iinclude -Wno-deprecated-declarations
g++ -c tests/test-pooltable.cpp -o tests/test-pooltable.o -std=c++11 -Iinclude -Wno-deprecated-declarations
g++ -c tests/test_main.cpp -o tests/test_main.o -std=c++11 -Iinclude -Wno-deprecated-declarations
g++ -o testapp -framework OpenGL -framework GLUT tests/test-poolball.o tests/test-pooltable.o tests/test_main.o lib/Poolball.o lib/Pooltable.o
./testapp
===============================================================================
All tests passed (22 assertions in 4 test cases)


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:

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).

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:

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.