patterncppMinor
Minimal C++ Unit Test Framework
Viewed 0 times
minimalframeworktestunit
Problem
I've written a minimal unit test framework. The goal was to allow assertions of booleans, for equality and catched exceptions. Two things are bugging the most. That assertions have to rely on macros and using a function pointer type instead of one of the more modern C++ ways. Are there ways to further minimize? What C++14 features could help?
For execution the test unit must derive from tiny::Unit. See an example and repository here
Header:
```
#include
#include
#include
#include
/**
* Assertion macros for boolean expressions or equality tests.
*/
#define TINY_ASSERT_OK(expr) tiny::assertOk(expr, #expr, __FILE__, __LINE__)
#define TINY_ASSERT_EQUAL(actual, expected) tiny::assertEqual(actual, expected, __FILE__, __LINE__)
/**
* Assertion macros for expected exceptions
*/
#define TINY_ASSERT_TRY(dummy) do { bool tiny_exception = false; try {
#define TINY_ASSERT_CATCH(type) } catch (const type &ex) { tiny_exception = true; } if( tiny_exception == false ) { tiny::handleMissedException(#type, __FILE__, __LINE__); } } while(false)
namespace tiny
{
/**
* Function pointer type for test cases
*/
typedef void (*TestFunc) (void);
/**
* A Unit contains test cases. Every test case has to be registered.
*/
class Unit
{
public:
Unit(const std::string& name);
void registerTest(TestFunc foo, const std::string& testName);
unsigned runTests();
protected:
private:
struct TestCase
{
TestFunc foo;
std::string name;
};
Unit(); // = delete
Unit(const Unit& other); // = delete
std::vector m_testCases;
std::string m_name;
};
/**
* Exception if a test failed, other exceptions must be catched or
* the test will fail.
*/
class TestFailed : public std::exception
{
public:
TestFailed(const std::string& msg)
: m_message(msg)
{}
virtual ~TestFailed() throw() {}
For execution the test unit must derive from tiny::Unit. See an example and repository here
Header:
```
#include
#include
#include
#include
/**
* Assertion macros for boolean expressions or equality tests.
*/
#define TINY_ASSERT_OK(expr) tiny::assertOk(expr, #expr, __FILE__, __LINE__)
#define TINY_ASSERT_EQUAL(actual, expected) tiny::assertEqual(actual, expected, __FILE__, __LINE__)
/**
* Assertion macros for expected exceptions
*/
#define TINY_ASSERT_TRY(dummy) do { bool tiny_exception = false; try {
#define TINY_ASSERT_CATCH(type) } catch (const type &ex) { tiny_exception = true; } if( tiny_exception == false ) { tiny::handleMissedException(#type, __FILE__, __LINE__); } } while(false)
namespace tiny
{
/**
* Function pointer type for test cases
*/
typedef void (*TestFunc) (void);
/**
* A Unit contains test cases. Every test case has to be registered.
*/
class Unit
{
public:
Unit(const std::string& name);
void registerTest(TestFunc foo, const std::string& testName);
unsigned runTests();
protected:
private:
struct TestCase
{
TestFunc foo;
std::string name;
};
Unit(); // = delete
Unit(const Unit& other); // = delete
std::vector m_testCases;
std::string m_name;
};
/**
* Exception if a test failed, other exceptions must be catched or
* the test will fail.
*/
class TestFailed : public std::exception
{
public:
TestFailed(const std::string& msg)
: m_message(msg)
{}
virtual ~TestFailed() throw() {}
Solution
A question is hard to answer when the correct answer is, that it is good as it is.
Indeed the typical unit test framework implementation uses the
In Qt it is implemented a very similar way as you did:
In Boost Test it is a little more complex, but the concept is the same:
No matter how hard I think about it, I always get to the conclusion, that if you want to do it without these macros you will need some kind of stack trace (like in C#). But as C++ does not support reflections using these macros is your only option.
The one thing I would change in your code is adding the
Indeed the typical unit test framework implementation uses the
__FILE__ and the __LINE__ macros.In Qt it is implemented a very similar way as you did:
#define QVERIFY(statement) \
do {\
if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\
return;\
} while (0)In Boost Test it is a little more complex, but the concept is the same:
#define BOOST_TEST_PASSPOINT() \
::boost::unit_test::unit_test_log.set_checkpoint( \
BOOST_TEST_L(__FILE__), \
static_cast(__LINE__) ) \
#define BOOST_CHECK_IMPL( P, check_descr, TL, CT ) \
do { \
BOOST_TEST_PASSPOINT(); \
BOOST_TEST_TOOL_IMPL( check_impl, P, check_descr, TL, CT ), 0 );\
} while( ::boost::test_tools::dummy_cond ) \
#define BOOST_CHECK( P ) \
BOOST_CHECK_IMPL( (P), BOOST_TEST_STRINGIZE( P ), CHECK, CHECK_PRED )No matter how hard I think about it, I always get to the conclusion, that if you want to do it without these macros you will need some kind of stack trace (like in C#). But as C++ does not support reflections using these macros is your only option.
The one thing I would change in your code is adding the
do { ... } while (0) guard around your macro (here is why).Code Snippets
#define QVERIFY(statement) \
do {\
if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\
return;\
} while (0)#define BOOST_TEST_PASSPOINT() \
::boost::unit_test::unit_test_log.set_checkpoint( \
BOOST_TEST_L(__FILE__), \
static_cast<std::size_t>(__LINE__) ) \
#define BOOST_CHECK_IMPL( P, check_descr, TL, CT ) \
do { \
BOOST_TEST_PASSPOINT(); \
BOOST_TEST_TOOL_IMPL( check_impl, P, check_descr, TL, CT ), 0 );\
} while( ::boost::test_tools::dummy_cond ) \
#define BOOST_CHECK( P ) \
BOOST_CHECK_IMPL( (P), BOOST_TEST_STRINGIZE( P ), CHECK, CHECK_PRED )Context
StackExchange Code Review Q#69061, answer score: 7
Revisions (0)
No revisions yet.