Skip to content

Instantly share code, notes, and snippets.

@SeijiEmery
Last active August 26, 2016 04:04
Show Gist options
  • Save SeijiEmery/8351b7f41241b3eaa0b47b056d80008c to your computer and use it in GitHub Desktop.
Save SeijiEmery/8351b7f41241b3eaa0b47b056d80008c to your computer and use it in GitHub Desktop.
minimalistic c++ unit testing library
//
// unittest.hpp
//
// Created by semery on 8/25/16.
// Copyright © 2016 Seiji Emery. All rights reserved.
//
#ifndef __unittest_hpp__
#define __unittest_hpp__
// Mini unit-testing API
//
// ENABLE_UNITTESTS (#define 1 / 0)
// enable / disable unit testing.
// If disabled, no unit testing code will be generated except testing stubs with your user code,
// which will be unused and should get eliminated by the linker.
// I'm assuming that we can't wrap a macro expansion in #ifdef, or we would use that to eliminate
// all unittest code.
//
// UNITTEST_REPORT_ON_SUCCESS (#define 1 / 0)
// If enabled, prints "All tests passed." when a test case passes; otherwise, is silent and
// only prints when a testcase contained errors. Prints to std::cerr in either case.
//
//
// UNITTEST_METHOD(name)
// define a method unittest_##name with the appropriate unittesting scaffold.
//
// UNITTEST_MAIN_METHOD(name)
// same as UNITTEST_METHOD, but with the function name 'unittest', not 'unittest_##name'.
// The intended usage is to define a main testing method for a class or global file / module,
// named (so name shows up in the unit testing log), but so you can just call unittest() /
// MyClass::unittest().
// eg.
// class Foo {
// static UNITTEST_MAIN_METHOD(Foo)
// RUN_UNITTEST(failTest, UNITTEST_INSTANCE);
// UNITTEST_END_METHOD
//
// static UNITTEST_METHOD(failTest)
// TEST_ASSERT(1, "should succeed");
// TEST_ASSERT_EQ(1, 2, "should fail");
// UNITTEST_END_METHOD
// };
//
// int main () {
// return RUN_UNITTEST_MAIN(Foo) ? 0 : -1;
// }
// // =>
// // Assertion Failed: /path/to/somefile.cpp:12: 1 != 2 should fail
// // Unittest FAILED: failTest: 1 / 2 tests passed.
// // Unittest FAILED: Foo::unittest: 0 / 1 tests passed.
//
//
// UNITTEST_END_METHOD
// end a method block started with UNITTEST_METHOD.
//
// RUN_UNITTEST(name, [bool printResults = true])
// executes the unittest unittest_##name, and returns true/false if all tests within
// that method passed. If printResults is true, prints results to std::cerr.
//
// RUN_UNITTEST(name, testContext, [bool printResults = true])
// same as the above, but instead of r
//
// UNITTEST_INSTANCE
// expands to the local testing instance if used in a UNITTEST_METHOD block,
// usable by RUN_UNITTEST (will produce errors if used outside)
//
//
// TEST_ASSERT(bool cond, std::string msg, [const char* file = __FILE__], [size_t line = __LINE__])
// assert that cond is true (if false, prints msg to std::cerr with file + line info,
// and marks this unit test as failing (does _not_ terminate early)).
// Usable only within a UNITTEST_METHOD block.
//
// TEST_ASSERT_EQ(a, b, std::string msg, [const char* file = __FILE__], [size_t line = __LINE__])
// assert that a == b using operator== (a and b _may_ be different types).
// If fails, writes values of a, b, msg, and file / line info to std::cerr, and marks this
// unit test as failing (does _not_ terminate early).
// Separate implementations are written for value types A, B and reference types A&, B&.
// Usable only within a UNITTEST_METHOD block.
//
#ifndef ENABLE_UNITTESTS
#define ENABLE_UNITTESTS 1
#endif
#ifndef UNITTEST_REPORT_ON_SUCCESS
#define UNITTEST_REPORT_ON_SUCCESS 0
#endif
#if ENABLE_UNITTESTS
#include <iostream>
struct UnitTest_Results {
const char* name;
unsigned passed = 0, attempted = 0;
UnitTest_Results (const char* name) : name(name) {}
void assertThat (bool cond, std::string msg, const char* file, size_t line) {
if (cond) ++attempted, ++passed;
else ++attempted, std::cerr << "Assertion Failed: " << file << ":" << line << ": " << msg << '\n';
}
void assertThat (bool cond, const char* file, size_t line) {
if (cond) ++attempted, ++passed;
else ++attempted, std::cerr << "Assertion Failed: " << file << ":" << line << '\n';
}
template <typename A, typename B>
void assertEq (const A& a, const B& b, std::string msg, const char* file, size_t line) {
if (a == b) ++attempted, ++passed;
else ++attempted, std::cerr << "Assertion Failed: " << file << ":" << line << ": '"
<< a << "' != '" << b << "' " << msg << '\n';
}
template <typename A, typename B>
void assertEq (const A& a, const B& b, const char* file, size_t line) {
if (a == b) ++attempted, ++passed;
else ++attempted, std::cerr << "Assertion Failed: " << file << ":" << line << ": '"
<< a << "' != '" << b << "'\n";
}
// Print results (optional), and collect results into another collection @testCollection.
// (eg. collect multiple sub test results into a single test result. passed / attempted
// will be incremented by 1 for each collection that testCollection is called on, with
// passed incremented iff passed == attempted for that test).
UnitTest_Results& checkResults (UnitTest_Results& testCollection, bool printResults = true) {
if (printResults) reportTestResults();
++testCollection.attempted;
if (passed == attempted) ++testCollection.passed;
return testCollection;
}
// Print results (optional), and return whether all results
bool checkResults (bool printResults = true) {
if (printResults) reportTestResults();
return passed == attempted;
}
private:
void reportTestResults () {
if (passed != attempted)
std::cerr << "Unittest FAILED: " << name << ":\t"
<< passed << " / " << attempted << " tests passed.\n";
#if UNITTEST_REPORT_ON_SUCCESS
else
std::cerr << "Unittest PASSED: " << name << ":\tAll tests passed.\n";
#endif // UNITTEST_REPORT_ON_SUCCESS
}
};
#define UNITTEST_MAIN_METHOD(name) \
UnitTest_Results unittest () { \
UnitTest_Results _testResults { #name };
#define UNITTEST_METHOD(name) \
UnitTest_Results unittest_##name () { \
UnitTest_Results _testResults { #name };
#define UNITTEST_END_METHOD \
return _testResults; }
#define RUN_UNITTEST(name, args...) \
unittest_##name().checkResults(args)
#define RUN_UNITTEST_MAIN(name, args...) \
name::unittest().checkResults(args)
#define UNITTEST_INSTANCE \
(_testResults)
#define TEST_ASSERT(args...) (_testResults.assertThat(args, __FILE__, __LINE__))
#define TEST_ASSERT_EQ(args...) (_testResults.assertEq(args, __FILE__, __LINE__))
#else // ENABLE_UNITTESTS
#define UNITTEST_MAIN_METHOD(name) void unittest () {
#define UNITTEST_METHOD(name) void unittest_##name () {
#define UNITTEST_END_METHOD }
#define RUN_UNITTEST(args...) (true)
#define RUN_UNITTEST_MAIN(args...) (true)
#define UNITTEST_INSTANCE (0)
#define TEST_ASSERT(args...)
#define TEST_ASSERT_EQ(args...)
#endif // ENABLE_UNITTESTS
#endif /* __unittest_hpp__ */
#define ENABLE_UNITTESTING // enable unit tests. unittest code will just generate empty statements + method stubs if this is not defined.
#include "unittest.hpp"
class Foo {
...
void bar () {
...
}
// Note: UNITTEST_METHOD just declares a method (in this case, 'unittest_bar'),
// and sets up a local testing context. This method is defined without any qualifiers,
// but in this, and almost all circumstances, we'll want to declare it as static:
// static class methods b/c they should not be instance methods, and static global
// functions b/c we probably do not want to expose the unittests to outside code
// (outside of a single method that runs all tests in that file, for instance).
// The keyword is left optional, however, in case you might need non-static for some reason.
//
// Also, the brackets are actually optional (UNITTEST_METHOD and END_UNITTEST_METHOD include the
// actual function brackets), but adding extra brackets is fine and it enables code folding
// and navigation features in most IDES.
static UNITTEST_METHOD(bar) {
// setup state for bar tests...
// Test with TEST_ASSERT, TEST_ASSERT_EQ, etc
TEST_ASSERT(myBar.xyz, "did not match xyz!");
TEST_ASSERT_EQ(myBar1.foo, expected_foo, "Invalid foo!");
TEST_ASSERT_EQ(myBar2.foo, expected_foo);
TEST_ASSERT(myBar.doBorg() == 12);
} UNITTEST_END_METHOD
void baz () {
...
}
static UNITTEST_METHOD(baz) {
// Setup state for baz tests...
// ...
} UNITTEST_END_METHOD
// UNITTEST_MAIN provides a "unittest" method that we can call all of our other tests from,
// and it still takes a name (in this case we're using our class name) for logging.
// It is otherwise not particularly interesting.
static UNITTEST_MAIN_METHOD(Foo) {
// Here, we're using UNITTEST_INSTANCE to insert the test results
// from testing foo + bar into our current testing context, which will be
// returned when this function exits.
// Note: this is implemented as a local variable, and so is invalid outside of UNITTEST_METHOD blocks.
RUN_UNITTEST(foo, UNITTEST_INSTANCE);
RUN_UNITTEST(bar, UNITTEST_INSTANCE);
// Ommitting UNITTEST_INSTANCE actually changes the function signature
// (most of the unittest macros are backed by multiple functions), so instead
// of putting the results into an existing test context, it just returns true
// if all unit tests ran successfully.
assert(RUN_UNITTEST(bar), "Failed Foo::bar unit tests!");
} UNITTEST_END_METHOD
}
// And we can test standalone functions as well, ofc:
SomeType doFubar () {
...
}
static UNITTEST_METHOD(doFubar) {
// ...
TEST_ASSERT_EQ(doFubar(), myExpectedType);
// ...
} UNITTEST_END_METHOD
// Additionally, we'll want to have a function that runs all tests in this class / module:
UNITTEST_METHOD(myModule_runAllTests) {
// Note: unfortunately, naming a unit test "unittest" (ie. Foo::unittest) requires some slightly different
// semantics, you'll have to call RUN_UNITTEST_MAIN (with the class) to call a static class method
// declared with a UNITTEST_MAIN_METHOD, and you have to do that to get and call a method named
// Foo::unittest (as opposed to Foo::unittest_Foo).
//
// And just to make things confusing, UNITTEST_MAIN_METHOD and RUN_UNITTEST_MAIN actually have
// nothing to do with the int main() method; they're just stupid names I came up with for allowing
// a plain "unittest" method usable inside of classes, namespaces, and structs.
RUN_UNITTEST_MAIN(Foo, UNITTEST_INSTANCE);
RUN_UNITTEST(doFubar);
} UNITTEST_END_METHOD
// Finally, you will probably have an actual int main() method somewhere.
// To run all unit tests before the program is executed, write a final method
// called runAllUnitTests or something, and run that in main() before everything
// else.
//
// To disable unit tests, just #undef ENABLE_UNITTESTING
UNITTEST_METHOD(runAllUnitTests) {
RUN_UNITTEST(myModule_runAllTests);
RUN_UNITTEST(myOtherModule_runAllTests);
// etc...
} UNITTEST_END_METHOD
int main (int argc, const char** argv) {
// Note: all logging is done implicitely at each TEST_ASSERT, TEST_ASSERT_EQ,
// and RUN_UNITTEST call. It's currently hardcoded to just write to std::cerr
// and runs all tests on one thread, but multithreading and a custom logging
// could be patched in pretty easily.
//
// All value comparison and stringification is done via
// operator== and operator<< (std::ostream&, <your type>),
// so this is completely customizable and works with all builtin and stdlib types.
//
if (!RUN_UNITTEST(runAllUnitTests))
return -1;
// do everything else...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment