Hey guys,
The following is the description of the concept of the cross-platform testsuite: please tell me what you think about it:
I did not use an existing framework because they either:
- depend completely on non-C++ macro "mountains" (tinytest)
- have no automatic testcase registration (tinytest)
- have complicated
REQUIRE(x == y, ...) magic macros which cannot easily be specialised p.e. for buffer comparison. (catch, lest)
- I plan to implement a special buffer comparison which writes both buffers to files so that one can use gnuplot to visualise the differences easily. BAM
The files holding the testcases are cpp. I plan to use one file for each testsuite, but it is not system-imposed to do so.
The following is the basic class layout without any macro hideaway:
// part of test framework
enum Result { OK, FAILED};
class Testcase {};
template <class Testcase>
class Testsuite {};
template <const char * name, class Testcase, int Line>
class TestcaseRegistration {};
namespace TonicTests {
class SpecialisedTestcase : public Testcase {
// setup, teardown etc.
};
// We needs to define a variable which holds all testcases for the suite.
Testcases* MyTestsuiteStorage = 0;
// The testsuite itself
class MyTestsuite
: public Testsuite< SpecialisedTestcase, &MyTestsuiteStorage >
{
class MyTestcase0;
TestcaseRegistration<"simple test", MyTestcase0, 0> testreg0;
class MyTestcase0 : public SpecialisedTestcase {
int Result void(std::ostream& failstream) {
int i = 1;
const Result result = test_eq(1, i, "i", failstream);
if (result != OK) return result;
return OK;
}
};
};
int main(int argc, const char* argv[]) {
MyTestsuite().run();
}
}
this all could be hidden by using some macro magic:
namespace TonicTests {
class SpecialisedTestcase : public Testcase {
// setup, teardown etc.
};
// MyTestsuite is necessary to be instantiated in main
TESTSUITE(MyTestsuite, SpecialisedTestcase, "a simple Testsuite")
TESTCASE("simple test")
int i = 1;
TEST(eq, 1, i, "i")
END_TESTCASE()
END_TESTSUITE()
}
int main(int argc, const char* argv[]) {
MyTestsuite().run();
}
It all works extremly simple:
TestcaseRegistration<> adds a instance of SpecialisedTestcase to MyTestsuiteStorage upon it's instantiation (which is ordered by declaration or TestcaseRegistration's in the class, maybe additional through __LINE__ template parameter).
- the
TestcaseRegistration<> template is a subclass of Testsuite<>. Thus it can access it's static methods easily it uses Testsuite<>::addTestcase() for the job described abose.
- the
TESTCASE() macro also uses the Testsuite<> namespace: the Type ``Testsuite<>::LocalTestcasedefines the Type whichMyTestcase0` should inherit from.
MyTestsuite::run() iterates through the MyTestsuiteStorage list/map (has yet to be decided) and calls MyTestcase0::run() upon each testcase instance.
Testcase::test_eq() writes a message to failstream if the test fails and returns FAILED which in turn causes MyTestsuite::run() to count the test as failed and output a pretty formatted message (or write a log etc.).
- The class names of testcases will get automatically generated based upon the
__LINE__ macro.
- a
TEST(...) macro expands to a simple function call which can be implemented in the SpecialisedTestcase easily. This way we can easily extend the testability. p.e. TEST(eq, ...) will be test_eq(...) but it also returns when test_eq() fails.
The magic here is the automatic Testcase registrations, so that we don't have to maintain a separate testcase list or generate code. the idea was taken from the catch framework and adapted to use classes instead of functions.
I want to do this all on a class level, because this is much easier to specialise for example for generator tests, etc.
My Main problem with this solution is that the braces will get hidden. This may annoy pro users and syntax highlighting. But I did not find a better Solution. Also this saves us a lot of useless characters, and looks quite nice, so this is my proposal.
See https://github.com/andik/Tonic/tree/cmake-unit-tests
Note: In the branch the principle class layout is done, but not the correct hideaway through macros, also I need to get rid of the #include stuff I do to get the testcases in. I want to make one cpp file for each suite. Also the class-naming there is not up to this concept. But yesterday night nearly instantly 10 of 16 generator test converted from the iPhone suite passed.
Maybe I'll publish this as a separate public domain Library and just include it into tonic so that this is available to other people also.
Hey guys,
The following is the description of the concept of the cross-platform testsuite: please tell me what you think about it:
I did not use an existing framework because they either:
REQUIRE(x == y, ...)magic macros which cannot easily be specialised p.e. for buffer comparison. (catch, lest)The files holding the testcases are cpp. I plan to use one file for each testsuite, but it is not system-imposed to do so.
The following is the basic class layout without any macro hideaway:
this all could be hidden by using some macro magic:
It all works extremly simple:
TestcaseRegistration<>adds a instance ofSpecialisedTestcasetoMyTestsuiteStorageupon it's instantiation (which is ordered by declaration orTestcaseRegistration's in the class, maybe additional through__LINE__template parameter).TestcaseRegistration<>template is a subclass ofTestsuite<>. Thus it can access it's static methods easily it usesTestsuite<>::addTestcase()for the job described abose.TESTCASE()macro also uses theTestsuite<>namespace: the Type ``Testsuite<>::LocalTestcasedefines the Type whichMyTestcase0` should inherit from.MyTestsuite::run()iterates through theMyTestsuiteStoragelist/map (has yet to be decided) and callsMyTestcase0::run()upon each testcase instance.Testcase::test_eq()writes a message tofailstreamif the test fails and returnsFAILEDwhich in turn causesMyTestsuite::run()to count the test as failed and output a pretty formatted message (or write a log etc.).__LINE__macro.TEST(...)macro expands to a simple function call which can be implemented in theSpecialisedTestcaseeasily. This way we can easily extend the testability. p.e.TEST(eq, ...)will betest_eq(...)but it also returns whentest_eq()fails.The magic here is the automatic Testcase registrations, so that we don't have to maintain a separate testcase list or generate code. the idea was taken from the catch framework and adapted to use classes instead of functions.
I want to do this all on a class level, because this is much easier to specialise for example for generator tests, etc.
My Main problem with this solution is that the braces will get hidden. This may annoy pro users and syntax highlighting. But I did not find a better Solution. Also this saves us a lot of useless characters, and looks quite nice, so this is my proposal.
See https://github.com/andik/Tonic/tree/cmake-unit-tests
Note: In the branch the principle class layout is done, but not the correct hideaway through macros, also I need to get rid of the
#includestuff I do to get the testcases in. I want to make one cpp file for each suite. Also the class-naming there is not up to this concept. But yesterday night nearly instantly 10 of 16 generator test converted from the iPhone suite passed.Maybe I'll publish this as a separate public domain Library and just include it into tonic so that this is available to other people also.