11

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)

nhershy
  • 643
  • 1
  • 6
  • 20
  • Are you linking it all into a single executable? Why? – LogicStuff Aug 31 '17 at 20:56
  • 6
    You should not compile your `main.cpp` in with the test program. The test program has its own `main()`. – Galik Aug 31 '17 at 21:01
  • 4
    You should have a separate build for unit testing, which excludes the `main.cpp` file. Alternatively, this separate build can create a define (e.g. `#define UNIT_TESTING`) which can be used to conditionally remove only the `main()` function from `main.cpp` (in case it contains other testable functions). – vgru Aug 31 '17 at 21:13
  • I did not realize that I should not compile main.cpp with the test program. How do I not compile it together, since it is all in the same project folder in Visual Studios? How can i create this separate build? (To clarifly, the main.cpp and TreeUnitTests.cpp are both under the Source folder and the Tree.h and Tree.hxx are under the Header folder. Both of those folders are in the same project folder) And can you also give more insight about the #define UNIT_TESTING definition that was mentioned? – nhershy Aug 31 '17 at 22:11
  • P.S. the catch.hpp is also under the Header folder. – nhershy Aug 31 '17 at 22:24

2 Answers2

9

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
vgru
  • 49,838
  • 16
  • 120
  • 201
  • 1
    This was exactly what I needed, thank you! Option 1 worked flawlessly and was very simple to implement. Also as a note to future-self or anyone else wondering, when I opened the configuration manager and created a new solution configuration called UnitTesting, I had to "copy settings from:" Debug to get it working how I wanted. Example: https://drive.google.com/file/d/0B-CcSAH4A8jFUWhERHhKdGYzazg/view?usp=sharing – nhershy Sep 03 '17 at 18:23
7

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.

Koby Duck
  • 1,118
  • 7
  • 15
  • Are you saying I need to put the '#define CATCH_CONFIG_RUNNER' in my main.cpp and then create my unit tests in my main function? And will this be done all before my other code already in main()? – nhershy Aug 31 '17 at 22:38
  • Assuming you don't need to do anything tricky in main, leave it more or less as it is in my example. Segregate your test cases into cpp files however you see fit. Catch uses meta-programming magic to make that work. I for example usually use one test cpp file per hpp, but in some cases split it up into smaller test sequences when the headers are large. – Koby Duck Aug 31 '17 at 22:45
  • I just learned about unit testing a few hours ago and this is my first attempt at implementing it in a c++ program, so I apologize for being a bit slow to catch on. Can you elaborate on your previous feedback? In my main function that I showed earlier (which is only commented out to show that I needed to do that to get my unit tests to run), do I need to take all the stuff that I currently have in it out, and instead simply add the code you provided? If so, where do I put my code? – nhershy Aug 31 '17 at 22:56
  • Copy my example and replace the contents of your main.cpp with it. Move all TEST_CASE sequences to other cpp files in any way you see fit. You could for example create a file named "tests.cpp" and place all TEST_CASE sequences there. – Koby Duck Aug 31 '17 at 23:03
  • @KobyDuck: Can you explain how to link compiled main.o file with test.cpp and how to run tests? I tried with your method but unable to compile test.cpp as it keeps throwing main() not defined. – kinjal patel Mar 22 '20 at 13:51