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)
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 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:
main.cpp
from the UnitTesting
build configFor example, you might right click main.cpp
, open Properties
, and exclude the file from build in this configuration only:
Excluding a file from the build for a specific configuration
main()
functionOr, you might open project properties and set a specific preprocessor macro which will be only defined in this configuration:
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With