c++unit-testingboost

Boost test: how to parametrize a test with input and output?


In Boost.Test, I'd like to test a function for different values, each value associated to an expected result.

Something like (I know, the test is stupid):

void SquareTest(int expectedResult, int input)
{
    BOOST_CHECK_EQUAL(expectedResult, MySquareFunctionToTest(input));
}

BOOST_AUTO_FUNCTION_TEST_CASE(SquareTest,  4, 2);
BOOST_AUTO_FUNCTION_TEST_CASE(SquareTest,  9, 3);
BOOST_AUTO_FUNCTION_TEST_CASE(SquareTest, 16, 4);

Is there a way to do that with Boost.Test ? The parametrized section int the documentation doesn't seem to match to my need.


Solution

  • I would suggest the simple:

    Live On Coliru

    #define BOOST_TEST_MODULE MyTest
    #include <boost/test/unit_test.hpp>
    
    int MySquareFunctionToTest(int x) { return x * x; }
    
    void SquareTest(int expectedResult, int input) {
        BOOST_CHECK_EQUAL(expectedResult, MySquareFunctionToTest(input));
    }
    
    BOOST_AUTO_TEST_CASE(SquareTest1) { SquareTest(4, 2); }
    BOOST_AUTO_TEST_CASE(SquareTest2) { SquareTest(9, 3); }
    BOOST_AUTO_TEST_CASE(SquareTest3) { SquareTest(16, 4); }
    

    If you really feel like getting the Enterprise(TM) version on:

    Data Driven Test Cases

    These were added for very good rationale, that probably resonate with you, since you were asking this question:

    The approach above has several drawbacks:

    • the logic for running the tests is inside a test itself: single_test in the above example is run from the test case combined_test while its execution would be better handled by the Unit Test Framework
    • in case of fatal failure for one of the values in param array above (say a failure in BOOST_TEST_REQUIRE), the test combined_test is aborted and the next test-case in the test tree is executed.
    • in case of failure, the reporting is not accurate enough: the test should certainly be reran during debugging sessions by a human or additional logic for reporting should be implemented in the test itself.

    (and much more)

    Example:

    #include <boost/test/data/test_case.hpp>
    namespace bdata = boost::unit_test::data;
    
    BOOST_DATA_TEST_CASE(SquareDataDrivenTest, bdata::make({2, 3, 4}), input) {
        SquareTest(input * input, input);
    }
    

    Of course, that's cheating. While

    bdata::make({std::pair{2, 4}, {3, 9}, {4, 16}})
    

    will work, it would require you to add printability to std::pair. To avoid that, consider:

    struct Params {
        int a, a_squared;
    
        friend std::ostream& operator<<(std::ostream& os, const Params& p){
            return os << "a=" << p.a << ", a_squared=" << p.a_squared;
        }
    };
    
    BOOST_DATA_TEST_CASE(SquareDataDrivenTest2,                        //
                         bdata::make({Params{2, 4}, {3, 9}, {4, 16}}), //
                         input) {
        auto [a, a2] = input;
        SquareTest(a2, a);
    }
    

    Live On Coliru

    https://coliru.stacked-crooked.com/a/8cbcc9f6bfb967b1

    #define BOOST_TEST_MODULE MyTest
    #include <boost/test/unit_test.hpp>
    #include <boost/test/unit_test_parameters.hpp>
    
    int MySquareFunctionToTest(int x) { return x * x; }
    
    void SquareTest(int expectedResult, int input) {
        BOOST_CHECK_EQUAL(expectedResult, MySquareFunctionToTest(input));
    }
    
    BOOST_AUTO_TEST_CASE(SquareTest1) { SquareTest(4, 2); }
    BOOST_AUTO_TEST_CASE(SquareTest2) { SquareTest(9, 3); }
    BOOST_AUTO_TEST_CASE(SquareTest3) { SquareTest(16, 4); }
    
    // Now doing these with Data-Driven test cases:
    
    #include <boost/test/data/test_case.hpp>
    namespace bdata = boost::unit_test::data;
    
    BOOST_DATA_TEST_CASE(SquareDataDrivenTest, bdata::make({2, 3, 4}), input) {
        SquareTest(input * input, input);
    }
    
    struct Params {
        int a, a_squared;
    
        friend std::ostream& operator<<(std::ostream& os, const Params& p){
            return os << "a=" << p.a << ", a_squared=" << p.a_squared;
        }
    };
    
    BOOST_DATA_TEST_CASE(SquareDataDrivenTest2,                        //
                         bdata::make({Params{2, 4}, {3, 9}, {4, 16}}), //
                         input) {
        auto [a, a2] = input;
        SquareTest(a2, a);
    }
    

    Printing:

    Running 9 test cases...
    
    *** No errors detected
    

    Or, -l all:

    Running 9 test cases...
    Entering test module "MyTest"
    /home/sehe/Projects/stackoverflow/test.cpp(11): Entering test case "SquareTest1"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    /home/sehe/Projects/stackoverflow/test.cpp(11): Leaving test case "SquareTest1"; testing time: 21us
    /home/sehe/Projects/stackoverflow/test.cpp(12): Entering test case "SquareTest2"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    /home/sehe/Projects/stackoverflow/test.cpp(12): Leaving test case "SquareTest2"; testing time: 18us
    /home/sehe/Projects/stackoverflow/test.cpp(13): Entering test case "SquareTest3"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    /home/sehe/Projects/stackoverflow/test.cpp(13): Leaving test case "SquareTest3"; testing time: 18us
    /home/sehe/Projects/stackoverflow/test.cpp(20): Entering test suite "SquareDataDrivenTest"
    /home/sehe/Projects/stackoverflow/test.cpp(20): Entering test case "_0"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    Assertion occurred in a following context:
        input = 2; 
    /home/sehe/Projects/stackoverflow/test.cpp(20): Leaving test case "_0"; testing time: 20us
    /home/sehe/Projects/stackoverflow/test.cpp(20): Entering test case "_1"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    Assertion occurred in a following context:
        input = 3; 
    /home/sehe/Projects/stackoverflow/test.cpp(20): Leaving test case "_1"; testing time: 18us
    /home/sehe/Projects/stackoverflow/test.cpp(20): Entering test case "_2"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    Assertion occurred in a following context:
        input = 4; 
    /home/sehe/Projects/stackoverflow/test.cpp(20): Leaving test case "_2"; testing time: 17us
    /home/sehe/Projects/stackoverflow/test.cpp(20): Leaving test suite "SquareDataDrivenTest"; testing time: 62us
    /home/sehe/Projects/stackoverflow/test.cpp(32): Entering test suite "SquareDataDrivenTest2"
    /home/sehe/Projects/stackoverflow/test.cpp(32): Entering test case "_0"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    Assertion occurred in a following context:
        input = a=2, a_squared=4; 
    /home/sehe/Projects/stackoverflow/test.cpp(32): Leaving test case "_0"; testing time: 19us
    /home/sehe/Projects/stackoverflow/test.cpp(32): Entering test case "_1"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    Assertion occurred in a following context:
        input = a=3, a_squared=9; 
    /home/sehe/Projects/stackoverflow/test.cpp(32): Leaving test case "_1"; testing time: 18us
    /home/sehe/Projects/stackoverflow/test.cpp(32): Entering test case "_2"
    /home/sehe/Projects/stackoverflow/test.cpp(8): info: check expectedResult == MySquareFunctionToTest(input) has passed
    Assertion occurred in a following context:
        input = a=4, a_squared=16; 
    /home/sehe/Projects/stackoverflow/test.cpp(32): Leaving test case "_2"; testing time: 18us
    /home/sehe/Projects/stackoverflow/test.cpp(32): Leaving test suite "SquareDataDrivenTest2"; testing time: 60us
    Leaving test module "MyTest"; testing time: 193us
    
    *** No errors detected
    

    Note how the data driven test cases report context with their invocations.