Skip to content

proposal for cross-platform testsuite #259

@andik

Description

@andik

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions