Home | Libraries | People | FAQ | More |
As we continue to add unit tests for a library, we can get quite a large number of tests. If they are true unit tests and each test exercises a single class isolated from its collaborators, then most of the tests are not relevant to the changes we are making. The test executable accepts a number of command-line arguments that allow us to control the behavior of the test framework, including which tests to run.
We can list the available tests in a test executable with --report_level
set to detailed:
> test_hello --report_level=detailed Running 2 test cases... Test suite "Master Test Suite" passed with: 2 assertions out of 2 passed 2 test cases out of 2 passed Test case "hello_world_inserts_text" passed with: 1 assertion out of 1 passed Test case "hello_world_stream_with_badbit_throws_runtime_error" passed with: 1 assertion out of 1 passed
Caution | |
---|---|
The arguments to the test executable, such as |
Suppose we are continuing to make changes to hello_world
and we want to run only the tests that apply to that function. We can easily
identify the relevant tests in the output of --report_level
because we have used a test naming convention of prefixing all test case
names with the name of the system under test, hello_world
.
Using the --run_test
argument, we can specify a comma-separated list of test case names to run:
> test_hello --run_test=hello_world_inserts_text,hello_world_stream_with_badbit_throws_runtime_error Running 2 test cases... *** No errors detected EXIT STATUS: 0
Wow, that's really a mouthfull! Even with command recall, typing this command
for the first time will require typing the exact names of all the test cases
for hello_world
. As we add more test cases, we'll have
to extend the command line argument to account for each new test case.
We can solve this problem by using test suites, which organize tests into a named group:
struct hello_world_fixture { std::ostringstream dest; }; BOOST_AUTO_TEST_SUITE(test_hello); #define HELLO_WORLD_TEST_CASE(name_) \ BOOST_FIXTURE_TEST_CASE(hello_world_##name_, hello_world_fixture) HELLO_WORLD_TEST_CASE(inserts_text) { hello_world(dest); BOOST_REQUIRE_EQUAL("Hello, world!\n", dest.str()); } HELLO_WORLD_TEST_CASE(stream_with_badbit_throws_runtime_error) { dest.clear(std::ios_base::badbit); BOOST_REQUIRE_THROW(hello_world(dest), std::runtime_error); } BOOST_AUTO_TEST_SUITE_END();
Now --report_level
outputs the following:
> test_hello --report_level=detailed Running 2 test cases... Test suite "Master Test Suite" passed with: 2 assertions out of 2 passed 2 test cases out of 2 passed Test suite "test_hello" passed with: 2 assertions out of 2 passed 2 test cases out of 2 passed Test case "hello_world_inserts_text" passed with: 1 assertion out of 1 passed Test case "hello_world_stream_with_badbit_throws_runtime_error" passed with: 1 assertion out of 1 passed
The indentation shows that the test cases for hello_world
are organized under the test suite named test_hello_world
.
Note | |
---|---|
We can't name the suite |
Now we can supply the name of the test suite to --run_test
to run all the test cases in the suite:
> test_hello --run_test=test_hello Running 2 test cases... *** No errors detected EXIT STATUS: 0
As we add more test cases to the suite, we don't have to change the command we are using to run the tests and we don't need to remember the exact names of the test cases.
Suites arrange test cases into a hierarchy. You can have suites within suites to provide larger groupings to test a collection of related classes together. Once a test case is part of a suite, we must use the name of the enclosing suite with the name of the test case if we wish to run a single test case within the suite by name:
> test_hello --run_test=test_hello/hello_world_inserts_text Running 1 test case... *** No errors detected
The names of the enclosing suites are separated from each other and from
the test case name with a slash (/
). The names supplied
to --run_test
also allow for wildcard matching:
> test_hello --run_test=hello_world/*inserts_text Running 1 test case... *** No errors detected
Just as we can use a fixture with a test case to eliminate repeated setup and tear down, we can use a fixture with a test suite. All test cases in the test suite will derive from the test suite's fixture. If a test case in a test suite with a fixture specifies its own fixture, then the test case derives from the fixture specified on the test case and not on the test suite. If you want the test case to use both fixtures, then make your test case fixture derive from the test suite fixture.
We can use a test suite fixture for test_hello
to take
care of the duplicated setup between test cases. Since our test cases are
now within the scope of a suite, we don't need to give them a unique prefix
anymore. Refactoring the tests to use a suite fixture and discarding the
prefix gives us the following code:
#define BOOST_TEST_MAIN #include <boost/test/included/unit_test.hpp> #include "hello.hpp" #include <ios> #include <sstream> struct hello_world_fixture { std::ostringstream dest; }; BOOST_FIXTURE_TEST_SUITE(test_hello, hello_world_fixture); BOOST_AUTO_TEST_CASE(inserts_text) { hello_world(dest); BOOST_REQUIRE_EQUAL("Hello, world!\n", dest.str()); } BOOST_AUTO_TEST_CASE(stream_with_badbit_throws_runtime_error) { dest.clear(std::ios_base::badbit); BOOST_REQUIRE_THROW(hello_world(dest), std::runtime_error); } BOOST_AUTO_TEST_SUITE_END();
Now --report_level
outputs the following:
> test_hello.exe --report_level=detailed Running 2 test cases... Test suite "Master Test Suite" passed with: 2 assertions out of 2 passed 2 test cases out of 2 passed Test suite "test_hello" passed with: 2 assertions out of 2 passed 2 test cases out of 2 passed Test case "inserts_text" passed with: 1 assertion out of 1 passed Test case "stream_with_badbit_throws_runtime_error" passed with: 1 assertion out of 1 passed