Last active
August 26, 2016 04:04
-
-
Save SeijiEmery/8351b7f41241b3eaa0b47b056d80008c to your computer and use it in GitHub Desktop.
minimalistic c++ unit testing library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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__ */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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