Unit Testing with Catch C++ is interfering with my main()

7k views Asked by At

I'm new to unit testing and decided to use the Catch framework for c++ because it seemed easy to integrate with its one header file. However, I have a multifile binary search tree program (files are: main.cpp, Tree.h, Tree.hxx, TreeUnitTests.cpp, catch.hpp). I can only get my unit tests to run if I comment out my int main() function in main.cpp. I understand that it is conflicting with '#define CATCH_CONFIG_MAIN' declaration in my TreeUnitTests.cpp, but I cannot get the unit tests to run if I do not include that declaration. How can I get both to run without having to comment my main() every time I want to run the unit tests?

This is the header file I am using: https://raw.githubusercontent.com/philsquared/Catch/master/single_include/catch.hpp

And the Catch tutorial I found it on and used as a guide: https://github.com/philsquared/Catch/blob/master/docs/tutorial.md

Some relevant files for reference: main.cpp:

//******************* ASSN 01 QUESTION 02 **********************

#include "Tree.h"
#include <iostream>

using namespace std;

/*
int main()
{

    //creating tree with "5" as root
    Tree<int> tree(5);
    tree.insert(2);
    tree.insert(88);
    tree.inorder();
    cout << "does tree contain 2?: ";
    cout << tree.find(2) << endl;
    cout << "does tree contain 3?: ";
    cout << tree.find(3) << endl;

    Tree<int> copytree(tree);
    cout << "copied original tree..." << endl;
    copytree.preorder();
    cout << "after deletion of 2:\n";
    copytree.Delete(2);
    copytree.postorder();


    return 0;
}
*/

TreeUnitTests.cpp:

#include <iostream>
#include "Tree.h"
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

TEST_CASE("Pass Tests")
{
    REQUIRE(1 == 1);
}

TEST_CASE("Fail test")
{
    REQUIRE(1 == 0);
}

(my tests are not real tests, only to verify that the Catch framework was working correctly. I guess you can say it's a meta test)

2

There are 2 answers

1
vgru On BEST ANSWER

Since you are using Visual Studio, the right approach would be to use the Configuration Manager (accessible by right clicking the solution in the Solution Explorer tool window) and create a separate solution configuration.

In the "New Solution Configuration" form, specify a meaningful name for the configuration (e.g. UnitTesting). There is also a drop-down list named "Copy from:" where you can select the configuration from which the settings will be copied into the new configuration. Don't leave this at <Empty>, but rather select some source configuration which you used to build the source so far (because it will have the include folders and other settings correctly set). Make sure you also create matching project configurations for all the projects in the solution by checking the check box "Create new project configurations".

Once you create the configuration, you select it the same way you would switch between Debug and Release configurations, using the toolbar drop-down list:

Click to select your new config Click to select your new UnitTesting configuration

Now, when you open property pages for a certain project or a file within that project (right click the file or the project, select Properties), you can select a specific configuration (UnitTesting in your case), and specify certain options which will only be active for this single configuration.

You can also select All Configurations in the property pages to apply settings to, obviously, all configurations. This is important when adding additional include directories and common preprocessor settings. If you accidentally add some include directory to the Debug configuration only, the compiler won't be able to find the header files after switching to UnitTesting.

So, to make this configuration behave differently, you can do something like:

1. Exclude main.cpp from the UnitTesting build config

For example, you might right click main.cpp, open Properties, and exclude the file from build in this configuration only:

Excluding a file from build Excluding a file from the build for a specific configuration

2. Use a preprocessor macro to conditionally exclude the main() function

Or, you might open project properties and set a specific preprocessor macro which will be only defined in this configuration:

enter image description here Creating a UNIT_TESTING macro for the UnitTesting configuration, again MS Paint is used to add flair to the figure

So, for the latter approach, your actual main.cpp would be changed to something like:

// remove if UnitTesting configuration is active
#ifndef UNIT_TESTING
int main(void)
{
    ...
}
#endif

The first approach is neat because it doesn't require you to change production code at all, but the latter approach might be more obvious to other unsuspecting people looking at your code in Visual Studio, when they begin to wonder why certain code isn't being compiled at all. I sometimes forget a certain file is missing from a build configuration and then stare at weird compile errors for a while.

And, with the second approach, you can also easily provide your custom Catch entry point at that same place:

#ifdef UNIT_TESTING

    // this is the main() used for unit testing

#   define CATCH_CONFIG_RUNNER
#   include <catch.hpp>

    int main(void)
    {
        // custom unit testing code
    }    

#else

    int main(void)
    {
        // actual application entry point
    }

#endif
5
Koby Duck On

When you leave out CATCH_CONFIG_RUNNER, Catch implements main for you. For most tests it's good enough, but if you need more control then you need tell it to use your main and in it bootstrap Catch.

Use something simple like this:

main.cpp

#define CATCH_CONFIG_RUNNER // Configure catch to use your main, and not its own.
#include <catch.hpp>
#include <iostream>
#include <exception>

int main(int argCount, char** ppArgs)
{
    try {
        // bootstrap Catch, running all TEST_CASE sequences.
        auto result = Catch::Session().run(argCount, ppArgs);
        std::cin.get(); // Immediate feedback.
        return (result < 0xFF ? result : 0xFF);
    } catch (const std::exception& ex) {
        auto pMessage = ex.what();
        if (pMessage) {
            std::cout << "An unhandled exception was thrown:\n" << pMessage;
        }
        std::cin.get(); // Immediate feedback.
        return -1;
    }
}

tests.cpp

#include <catch.hpp>

TEST_CASE("Pass Tests")
{
    REQUIRE(1 == 1);
}

TEST_CASE("Fail test")
{
    REQUIRE(1 == 0);
}

This is more or less what I'm using in production code and it works great.