Is it possible to envoke Boost.UnitTest tests within main in a way similar to the Boost.MinimalTestFacility?

StackOverflow https://stackoverflow.com/questions/21331038

  •  02-10-2022
  •  | 
  •  

Question

I am extending a library for computational fluid dynamics, so I am dealing with legacy code. The applications involve initializing sometimes very large objects, most of which are codependent. The initialization is dependent on configuration and input files stored in a directory.

Trying to use a testing framework compared to my own testing hacked libs should make sense, since there are various test cases and families, and tests, and I could benefit from having the test tree and shiny reports + ability to automatize the tests.

However, I heve encountered a problem when trying to call specific tests at specific points in the program. This problem has occured already when I tried using Google Test - see this question.

Here is a model of the problem using Boost.Test:

#define BOOST_TEST_MODULE hugeObjectEvenTest 
#define BOOST_TEST_NO_MAIN
#include <boost/test/included/unit_test.hpp>
#include<random>
#include<iostream>

BOOST_AUTO_TEST_SUITE (hugeObjectEvenTest) 

BOOST_AUTO_TEST_CASE (test1)
{
    BOOST_CHECK(hugeObject.value() % 2 == 0);
}

BOOST_AUTO_TEST_SUITE_END()

class HugeClass
{
    int value_ = 0; 

    public:

        HugeClass() = default; 

        HugeClass(int x) : value_(x) {}; 

        int value () { return value_; }

        void setValue (int val) { value_ = val; }
};

int main(int argc, const char *argv[])
{
    HugeClass hugeObject; 

    std::random_device rd;
    std::default_random_engine e1(rd());
    std::uniform_int_distribution<int> dist(0,100); 

    for(int i = 0; i < 10; ++i)
    {
        hugeObject.setValue(dist(e1));  
        std::cout << hugeObject.value() << std::endl;
    }

    return 0;
}

This is merely a model of a numerical solver application, like the one found here.

What I think I need is a global fixture, that is able to take a reference to the hugeObject.

The instances just like hugeObject are modified (modeled with random number generation) during the simulation, within the simulation loop (modeled with the for loop).

All I want to do, is to execute specific tests at specific points within main, and benefit from having a test tree, and all the other perks of using a testing framework. Something similar to the functionality of the Minimal Test Facility.

Is this at all possible with Boost.Test? Like Google Test, selecting specific tests can be done by parsing during execution. This is of no use whatsoever for my problem. I have used both GTest and BoostTest for unit tests, where the initialization of fixtures is local and not dependent on main (argc, argv, configuration and input files), and I had no problems.

Edit: I will get flamed for this probably, but when dealing with legacy code, I believe it would be beneficial to somehow be able to access the objects in main via const refs (to ensure that the tests don't modify objects), in a simpler way than inheriting from fixture classes. In my case, doing that means a day of work, compared to simple BOOST_TEST_REQUIRE placed within main when using the minimal testing framework. Of course, with the minimal framework I have no test tree, etc, so I am back where I started: at my own hacked test lib.

Was it helpful?

Solution

One possible way to do this is to perform your own manual test registration, separate the tests you want to execute together into suites and run them manually. For example:

using namespace boost::unit_test;

void test1() { std::cout << "Running test 1\n"; }
void test2() { std::cout << "Running test 2\n"; }
void test3() { std::cout << "Running test 3\n"; }

void init_test_tree() {

    test_suite *ts1 = BOOST_TEST_SUITE( "suite_a");
    ts1->add( BOOST_TEST_CASE( &test1 ) );
    ts1->add(BOOST_TEST_CASE( &test2 ));
    framework::master_test_suite().add(ts1);

    test_suite *ts2 = BOOST_TEST_SUITE( "suite_b");
    ts2->add( BOOST_TEST_CASE( &test3 ) );
    framework::master_test_suite().add(ts2);
}

bool empty_init() { return true; }

int main( int argc, char *argv[] ) {

    init_test_tree();

    std::cout << "Run suite a\n";
    framework::run( framework::master_test_suite().get("suite_a"));

    std::cout << "Run suite b\n";
    framework::run( framework::master_test_suite().get("suite_b"));

    std::cout << "Run the tree\n";
    // pass empty initialization function as we've already constructed the test tree
    return unit_test_main(&empty_init, argc, argv);
}

OTHER TIPS

Registering your own test cases manually is tedious, boring and error-prone and I don't recommend it. Instead, you can simply define your own main() instead of having Boost.Test provide it for you. Write a main that looks like this:

HugeClass hugeObject; 

boost::unit_test::test_suite *init_function(int argc, char *argv[])
{
    // create test cases and suites and return a pointer to any enclosing
    // suite, or 0.
    return 0;
}

int main(int argc, const char *argv[])
{
    std::random_device rd;
    std::default_random_engine e1(rd());
    std::uniform_int_distribution<int> dist(0,100); 

    for(int i = 0; i < 10; ++i)
    {
        hugeObject.setValue(dist(e1));  
        std::cout << hugeObject.value() << std::endl;
    }
    return boost::unit_test::unit_test_main(init_function, argc, argv);
}

If you do this, you get:

  • Automatic test case registration
  • Use of test suites
  • The ability to do any special stuff first in main() before any part of Boost.Test runs

One annoying side-effect of writing your own main: the signature for the init_function is different depending on if you linked with the static version of Boost.Test or the shared library (dynamic) version of Boost.Test. The differences are discussed in my Boost.Test documentation rewrite in the sections on the static library and the shared library.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top