Introduction to TDD (Test-Driven Development)
- Due Oct 28, 2019 by 10pm
- Points 0
- Submitting a file upload
- File Types cpp and h
Test-Driven Development (TDD)
One of the trends in software engineering has been to write tests for your code prior to writing your actual code. There are many benefits from doing so, including:
- when writing your tests, you know if your code is usable
- you think about your code before you build it
- you only build enough functionality to pass your tests, so you don't overbuild
- the tests act as documentation for how the code should be used
- etc.
We're going to build a small class and while learning how to do TDD.
Getting the testing framework
We're going to use the Google Test framework for building our tests. The framework is pretty large and complicated, but we're only going to focus on a small part of it. The framework is already installed on the lab machines, so you can start using it right away. The first thing you should do is to copy the "standard" Google Test Makefile into your source directory:
cp /usr/src/googletest/googletest/make/Makefile .
Then, you need to edit the Makefile so that it will work in the new location.
- Change the line that points to the location of the Google Test frame work
- Change the line that points to the location of the code under test
- Tell the Makefile which tests to build
- Modify the rules that will create the testing environment
For step #1, find the line that begins GTEST_DIR and modify it to be
GTEST_DIR = /usr/src/googletest/googletest
For step #2, find the line nearby that begins USER_DIR and modify it to be
USER_DIR = .
For step #3, find the line a little further down the begins TESTS and modify it to be
TESTS = my_first_unittest
For step #4, we're going to have to do a bit more work. Near the bottom of the Makefile are three example rules that show how to use the testing framework named sample1.o, sample1_unittest.o, and sample1_unittest. In these rules, change anywhere that says sample1 with my_first. Also, change anywhere that uses the .cc suffix to use the .cpp suffix instead.
The reason there are three rules in this Makefile is that you will have one source file for the object you're building (my_first.cpp), a second file for the interface to the object you're buidling (my_first.h), a third file for the tests you're building (my_first_unittest.cpp), and they will be combined into an executable that you're using for testing your code (my_first_unittest).
Building the minimal test
Create your interface file first (my_first.h). In there, you should create the interface to the object you're testing:
class MyFirst { int i;
public:
MyFirst(); };
Next, create the implementation file (my_first.cpp):
#include "my_first.h" MyFirst::MyFirst() : i(42) { }
Finally, create the test file (my_first_unittest.cpp):
#include "my_first.h" #include "gtest/gtest.h" TEST(MyFirstTest, First) { EXPECT_EQ(0, 0); }
And compile your testing program by using make, which should create the final executable named my_first_unittest. You should be able to run the test program to get output similar to:
Running main() from gtest_main.cc [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from MyFirstTest [ RUN ] MyFirstTest.First [ OK ] MyFirstTest.First (0 ms) [----------] 1 test from MyFirstTest (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 1 test.
Note the appearance of the #include "my_first.h" in both the source and test files. This is how we can use the object being tested. Also note the line with a #include "gtest/gtest.h", which is how we can use the Google Test framework.
Now let's add a test that fails. At the end of my_first_unittest.cpp, add the following test:
TEST(MyFirstTest, Second) { EXPECT_EQ(1, 0); }
You can go ahead and recompile the code using make and run the new executable. You should get new output that looks like:
Running main() from gtest_main.cc [==========] Running 2 tests from 1 test case. [----------] Global test environment set-up. [----------] 2 tests from MyFirstTest [ RUN ] MyFirstTest.First [ OK ] MyFirstTest.First (0 ms) [ RUN ] MyFirstTest.Second ./my_first_unittest.cpp:9: Failure Expected: 1 To be equal to: 0 [ FAILED ] MyFirstTest.Second (0 ms) [----------] 2 tests from MyFirstTest (0 ms total) [----------] Global test environment tear-down [==========] 2 tests from 1 test case ran. (0 ms total) [ PASSED ] 1 test. [ FAILED ] 1 test, listed below: [ FAILED ] MyFirstTest.Second 1 FAILED TEST
You can see the difference pretty clearly between the two runs. The way the tests work is that you use the EXPECT_EQ and similar functions to describe what you want to be true and it ensures those values hold. There are a number of functions including EXPECT_NE, EXPECT_LT, and EXPECT_GE. By creating simple tests that focus on one thing at a time, we can be sure that our full class does exactly what it is supposed to.
The BigInteger class
The class we're going to test as we build will be a class that acts like an integer, but can store any value--no matter how large. On our x86 machines, the standard C++ int can only store values up to around 2 billion. Later in the class, we are going to want to compute some really big numbers and so an int is not going to be large enough. For simplicity's sake, we're only going to build a subset of the integer functionality that we need:
- Creating BigIntegers from ints and strings
- Adding two BigIntegers together
- Multiplying two BigIntegers together
- Comparing two BigIntegers with each other (== and <)
Setting up the files
You should modify the Makefile to be able to handle the new BigInteger class that you are making. It should have three new rules that look like the my_first_unittest rules and you should add a new unit tester for the new class. I would suggest you call the files big_integer.h, big_integer.cpp, and big_integer_unittest.cpp so that it is easier for you see how to do this process. In fact, you can download my starter code for the BigInteger class because I want you to focus on building the tests for this lab. Note that your tests should be able to find multiple errors in my code because it is intentionally incomplete and incorrect.
Writing the tests
You should look at all the methods for the BigInteger class and create as many tests as you think necessary to test the full functionality of a correctly implemented version of the class. When you run against the starter code, most of the tests will fail. Once you think you have a good set of tests, have one of your TAs check that they agree that you haven't missed anything critical.
Writing the code
Once you have permission from a TA or myself, you can start writing your implementation of the BigInteger class. You only have to write enough code to make your tests pass. Once you've done so, the lab is complete. Note that we will add to this class in future labs, so you want to make sure you've done a good job implementing the class and testing that it works properly.