{"id":18432662,"url":"https://github.com/mrzechonek/tut-framework","last_synced_at":"2025-04-07T18:33:54.002Z","repository":{"id":12595136,"uuid":"15266009","full_name":"mrzechonek/tut-framework","owner":"mrzechonek","description":"C++ Template Unit Test Framework ","archived":false,"fork":false,"pushed_at":"2021-07-01T17:14:51.000Z","size":1348,"stargazers_count":50,"open_issues_count":3,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2023-08-07T06:43:12.303Z","etag":null,"topics":["c-plus-plus","unit-testing"],"latest_commit_sha":null,"homepage":"http://mrzechonek.github.io/tut-framework","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrzechonek.png","metadata":{"files":{"readme":"README","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-12-17T21:10:11.000Z","updated_at":"2023-02-28T06:48:45.000Z","dependencies_parsed_at":"2022-08-28T11:31:39.942Z","dependency_job_id":null,"html_url":"https://github.com/mrzechonek/tut-framework","commit_stats":null,"previous_names":[],"tags_count":2,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrzechonek%2Ftut-framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrzechonek%2Ftut-framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrzechonek%2Ftut-framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrzechonek%2Ftut-framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrzechonek","download_url":"https://codeload.github.com/mrzechonek/tut-framework/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223289633,"owners_count":17120707,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-plus-plus","unit-testing"],"created_at":"2024-11-06T05:29:11.343Z","updated_at":"2024-11-06T05:29:11.874Z","avatar_url":"https://github.com/mrzechonek.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"Documentation TUT How-To minimum steps to make TUT work for you\n\nWhat is TUT\n\nTUT is a pure C++ unit test framework. Its name - TUT - stands for \nTemplate Unit Tests.\n\nFeatures\n\nTUT provides all features required for unit testing:\n\n    * Similar tests can be grouped together into test groups. Each \n      test group has its unique name and is located in a separate \n      compilation unit. One group can contain almost unlimited number \n      of tests (actually, the limit is the compiler template \n      recursion depth).\n    * User can run all the tests (regression), or just some selected \n      groups or even some tests in these groups.\n    * TUT provides special template functions to check the condition \n      validity at run-time and to force test failure if required. \n      Since C++ doesn't provide a facility for obtaining stack trace \n      of the throwed exception and TUT avoids macros, those functions \n      accept string marker to allow users easely determine the source \n      of exception.\n    * TUT contains callback that can be implemented by the calling code \n      to integrate with an IDE, for example. Callbacks tell listener \n      when a new test run started, when test runner switches to the \n      next tests group, when a test was completed (and what result it \n      has), and when test run was finished. The callbacks allow users \n      to produce their own visualization format for test process and results.\n    * Being a template library, it doesn't need compilation; just \n      include the \u003ctut/tut.hpp\u003e header into the test modules.\n\nTUT tests organization\n\nTest application\n\nC++ produces executable code, so tests have to be compiled into a single \nbinary called test application. The application can be built in automated \nmode to perform nightly tests. They also can be built manually when a \ndeveloper hunts for bugs.\n\nThe test application contains tests, organized into test groups.\n\nTest groups\n\nThe functionality of a tested application can be divided into a few separate \nfunction blocks (e.g. User Rights, Export, Processing, ...). It is natural \nto group together tests for each block. TUT invokes this test group. Each \ntest group has a unique human-readable name and normally is located in a \nseparate file.\n\nTests\n\nEach single test usually checks only one specific element of functionality. \nFor example, for a container a test could check whether size() call \nreturns zero after the successful call to the clear() method.\n\nWriting simple test\n\nPreamble\n\nYou are going to create a new class for your application. You decided to \nwrite tests for the class to be sure it works while you are developing or, \npossibly, enhancing it. Let's consider your class is shared pointer: \nstd::auto_ptr-alike type that shares the same object among instances.\n\nPrior to test writing, you should decide what to test. Maximalist's \napproach requires to write so many tests that altering any single \nline of your production code will break at least one of them. \nMinimalist's approach allows one to write tests only for the most \ngeneral or the most complex use cases. The truth lies somewhere in \nbetween, but only you, developer, know where. You should prepare \ncommon successful and unsuccessful scenarios, and the scenarios for \ntesting any other functionality you believe might be broken in some way.\n\nFor our shared_ptr we obviosly should test constructors, assignment operators, referencing and passing ownership.\n\nSkeleton\n\nIf you don't have any implemented class to test yet, it would be good to \nimplement it as a set of stubs for a first time. Thus you'll get an \ninterface, and be able to write your tests. Yes, that's correct: you \nshould write your tests before writing code! First of all, writing tests \noften helps to understand oddities in the current interface, and fix it. \nSecondly, with the stubs all your tests will fail, so you'll be sure \nthey do their job.\n\nCreating Test Group\n\nSince we're writing unit tests, it would be a good idea to group the \ntests for our class in one place to be able to run them separately. \nIt's also natural in C++ to place all the grouped tests into one \ncompilation unit (i.e. source file). So, to begin, we should create \na new file. Let's call it test_shared_ptr.cpp. (Final variant of the \ntest group can be found in examples/shared_ptr subdirectory of the \ndistribution package)\n\n// test_shared_ptr.cpp\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n}\n\nAs you see, you need to include TUT header file (as expected) and \nuse namespace tut for tests. You may also use anonymous namespace if \nyour compiler allows it (you will need to instantiate methods from \ntut namespace and some compilers refuse to place such instantiations \ninto the anonymous namespace).\n\nA test group in TUT framework is described by the special template \ntest_group\u003cT\u003e. The template parameter T is a type that will hold all \ntest-specific data during the test execution. Actually, the data \nstored in T are member data of the test. Test object is inherited \nfrom T, so any test can refer to the data in T as its member data.\n\nFor simple test groups (where all data are stored in test local \nvariables) type T is an empty struct.\n\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n  struct shared_ptr_data\n  { \n  };\n}\n\nBut when tests have complex or repeating creation phase, you may put \ndata members into the T and provide constructor (and, if required, \ndestructor) for it. For each test, a new instance of T will be \ncreated. To prepare your test for execution TUT will use default \nconstructor. Similarly, after the test has been finished, TUT \ncalls the destructor to clean up T. I.e.:\n\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n  struct complex_data\n  {\n    connection* con;\n    complex_data(){ con = db_pool.get_connection(); }\n    ~complex_data(){ db_pool.release_connection(con); }\n  };\n\n  // each test from now will have con data member initialized\n  // by constructor:\n  ...\n  con-\u003ecommit();\n  ...\n}\n\nWhat will happen if the constructor throws an exception? TUT will treat \nit as if test itself failed with exception, so this test will \nnot be executed. You'll see an exception mark near the test, and \nif the constructor throwed something printable, a certain message will appear.\n\nException in destructor is threated a bit different. Reaching destruction \nphase means that the test is passed, so TUT marks test with warning \nstatus meaning that test itself was OK, but something bad has happend \nafter the test.\n\nWell, all we have written so far is just a type declaration. To work \nwith a group we have to have an object, so we must create the test group \nobject. Since we need only one test group object for each unit, we can \n(and should, actually) make this object static. To prevent name clash with \nother test group objects in the namespace tut, we should provide a \ndescriptive name, or, alternatively, we may put it into the anonymous \nnamespace. The former is more correct, but a descriptive name usually works \nwell too, unless you're too terse in giving names to objects.\n\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n    struct shared_ptr_data\n    {\n    };\n\n    typedef test_group\u003cshared_ptr_data\u003e tg;\n    tg shared_ptr_group(\"shared_ptr\");\n}\n\nAs you see, any test group accepts a single parameter - its human-readable \nname. This name is used to identify the group when a programmer wants to \nexecute all tests or a single test within the group. So this name shall \nalso be descriptive enough to avoid clashes. Since we're writing tests \nfor a specific unit, it's enough to name it after the unit name.\n\nTest group constructor will be called at unspecified moment at the test \napplication startup. The constructor performs self-registration; it calls \ntut::runner and asks it to store the test group object name and location. \nAny other test group in the system undergoes the same processing, i.e. \neach test group object registers itself. Thus, test runner can iterate \nall test groups or execute any test group by its name.\n\nNewly created group has no tests associated with it. To be more precise, \nit has predefined set of dummy tests. By default, there are 50 tests in a \ngroup, including dummy ones. To create a test group with higher volume \n(e.g. when tests are generated by a script and their number is higher) \nwe must provide a higher border of test group size when it is instantiated:\n\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n    struct huge_test_data\n    {\n    };\n\n    // test group with maximum 500 tests\n    typedef test_group\u003chuge_test_data,500\u003e testgroup;\n    testgroup huge_test_testgroup(\"huge group\");\n}\n\nNote also, that your compiler will possibly need a command-line switch \nor pragma to enlarge recursive instantiation depth. For g++, for \nexample, you should specify at least --ftemplate-depth-501 to increase \nthe depth. For more information see your compiler documentation.\n\nCreating Tests\n\nNow it's time to fill our test group with content.\n\nIn TUT, all tests have unique numbers inside the test group. Some \npeople believe that textual names better describe failed tests in \nreports. I agree; but in reality C++ templates work good with numbers \nbecause they are compile-time constants and refuse to do the same \nwith strings, since strings are in fact addresses of character \nbuffers, i.e. run-time data.\n\nAs I mentioned above, our test group already has a few dummy tests; \nand we can replace any of them with something real just by writing \nour own version:\n\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n    struct shared_ptr_data{};\n\n    typedef test_group\u003cshared_ptr_data\u003e testgroup;\n    typedef testgroup::object testobject;\n    testgroup shared_ptr_testgroup(\"shared_ptr\");\n\n    template\u003c\u003e\n    template\u003c\u003e\n    void testobject::test\u003c1\u003e()\n    {\n        // do nothing test\n    }\n}\n\nSo far this test does nothing, but it's enough to illustrate the concept.\n\nAll tests in the group belong to the type test_group\u003cT\u003e::object. This \nclass is directly inherited from our test data structure. In our case, it is\n\nclass object : public shared_ptr_data { ... }\n        \nThis allows to access members of the data structure directly, since at \nthe same time they are members of the object type. We also typedef the \ntype with testobject for brevity.\n\nWe mark our test with number 1. Previously, test group had a dummy test \nwith the same number, but now, since we've defined our own version, it \nreplaces the dummy test as more specialized one. It's how C++ template \nordering works.\n\nThe test we've written always succeeds. Successful test returns with no \nexceptions. Unsuccessful one either throws an exception, or fails at \nfail() or ensure() methods (which anyway just throw the exception when failed).\n\nFirst real test\n\nWell, now we know enough to write the first real working test. This test \nwill create shared_ptr instances and check their state. We will define a \nsmall structure (keepee) to use it as shared_ptr stored object type.\n\n#include \u003ctut/tut.hpp\u003e\n#include \u003cshared_ptr.h\u003e\n\nnamespace tut\n{\n    struct shared_ptr_data\n    {\n        struct keepee{ int data; };\n    };\n\n    typedef test_group\u003cshared_ptr_data\u003e testgroup;\n    typedef testgroup::object testobject;\n    testgroup shared_ptr_testgroup(\"shared_ptr\");\n\n    /**\n     * Checks default constructor.\n     */\n    template\u003c\u003e\n    template\u003c\u003e\n    void testobject::test\u003c1\u003e()\n    {\n        shared_ptr\u003ckeepee\u003e def;\n        ensure(\"null\",def.get() == 0);\n    }\n};\n\nThat's all! The first line creates shared_ptr. If constructor throws \nan exception, test will fail (exceptions, including '...', are catched \nby the TUT framework). If the first line succeeds, we must check \nwhether the kept object is null one. To do this, we use test object \nmember function ensure(), which throws std::logic_error with a given \nmessage if its second argument is not true. Finally, if destructor of \nshared_ptr fails with exception, TUT also will report this test as failed.\n\nIt's equally easy to write a test for the scenario where we expect to get \nan exception: let's consider our class should throw an exception if it \nhas no stored object, and the operator -\u003e is called.\n\n/**\n * Checks operator -\u003e throws instead of returning null.\n */\ntemplate\u003c\u003e\ntemplate\u003c\u003e\nvoid testobject::test\u003c2\u003e()\n{\n    try\n    {\n        shared_ptr\u003ckeepee\u003e sp;\n        sp-\u003edata = 0;\n        fail(\"exception expected\");\n    }\n    catch( const std::runtime_error\u0026 ex )\n    {\n        // ok\n    }\n}\n        \n\nHere we expect the std::runtime_error. If operator doesn't throw it, \nwe'll force the test to fail using another member function: fail(). It \njust throws std::logic_error with a given message. If operator throws \nanything else, our test will fail too, since we intercept only \nstd::runtime_error, and any other exception means the test has failed.\n\nNB: our second test has number 2 in its name; it can, actually, be any \nin range 1..Max; the only requirement is not to write tests with the \nsame numbers. If you did, compiler will force you to fix them anyway.\n\nAnd finally, one more test to demonstrate how to use the \nensure_equals template member function:\n\n/**\n * Checks keepee counting.\n */\ntemplate\u003c\u003e\ntemplate\u003c\u003e\nvoid testobject::test\u003c3\u003e()\n{\n    shared_ptr\u003ckeepee\u003e sp1(new keepee());\n    shared_ptr\u003ckeepee\u003e sp2(sp1);\n    ensure_equals(\"second copy at sp1\",sp1.count(),2);\n    ensure_equals(\"second copy at sp2\",sp2.count(),2);\n}\n\nThe test checks if the shared_ptr correctly counts references during \ncopy construction. What's interesting here is the template member \nensure_equals. It has an additional functionality comparing with similar \ncall ensure(\"second_copy\",sp1.count()==2); it uses operator == to check \nthe equality of the two passed parameters and, what's more important, it \nuses std::stream to format the passed parameters into a human-readable \nmessage (smth like: \"second copy: expected 2, got 1\"). It means that \nensure_equals cannot be used with the types that don't have operator \u003c\u003c; \nbut for those having the operator it provides much more informational message.\n\nIn contrast to JUnit assertEquals, where the expected value goes before \nthe actual one, ensure_equals() accepts the expected after the actual \nvalue. I believe it's more natural to read ensure_equals(\"msg\", count, 5) \nas \"ensure that count equals to 5\" rather than JUnit's \n\"assert that 5 is the value of the count\".\n\nRunning tests\n\nTests are already written, but an attempt to run them will be unsuccessful. \nWe need a few other bits to complete the test application.\n\nFirst of all, we need a main() method, simply because it must be in all \napplications. Secondly, we need a test runner singleton. Remember I said \neach test group should register itself in singleton? So, we need that \nsingleton. And, finally, we need a kind of a callback handler to visualize \nour test results.\n\nThe design of TUT doesn't strictly set a way the tests are visualized; \ninstead, it provides an opportunity to get the test results by means of \ncallbacks. Moreover it allows user to output the results in any format he \nprefers. Of course, there is a \"reference implementation\" in the \nexample/subdirectory of the project.\n\nTest runner singleton is defined in tut/tut.hpp, so all we need to activate it \nis to declare an object of the type tut::test_runner_singleton in the main \nmodule with a special name tut::runner.\n\nNow, with the test_runner we can run tests. Singleton has method get() \nreturning a reference to an instance of the test_runner class, which in \nturn has methods run_tests() to run all tests in all groups, \nrun_tests(const std::string\u0026 groupname) to run all tests in a given group \nand run_test(const std::string\u0026 grp,int n) to run one test in the specified group.\n\n// main.cpp\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n    test_runner_singleton runner;\n}\n\nint main()\n{\n    // run all tests in all groups\n    runner.get().run_tests();\n\n    // run all tests in group \"shared_ptr\"\n    runner.get().run_tests(\"shared_ptr\");\n\n    // run test number 5 in group \"shared_ptr\"\n    runner.get().run_test(\"shared_ptr\",5);\n\n    return 0;\n}\n  \nIt's up to user to handle command-line arguments or GUI messages and map those \narguments/messages to actual calls to test runner. Again, as you see, TUT \ndoesn't restrict user here.\n\nBut, the last question is still unanswered: how do we get our test results? \nThe answer lies inside tut::callback interface. User shall create its subclass, \nand write up to three simple methods. He also can omit any method since they \nhave default no-op implementation. Each corresponding method is called in the \nfollowing cases:\n\n    * a new test run started;\n    * test finished;\n    * test run finished.\n\nHere is a minimal implementation:\n\nclass visualizator : public tut::callback\n{\npublic:\n  void run_started(){ }\n\n  void test_completed(const tut::test_result\u0026 tr)\n  {\n      // ... show test result here ...\n  }\n\n  void run_completed(){ }\n};        \n\nThe most important is the test_completed() method; its parameter has type \ntest_result, and contains everything about the finished test, from its group \nname and number to the exception message, if any. Member result is an enum \nthat contains status of the test: ok, fail or ex. Take a look at the \nexamples/basic/main.cpp for more complete visualizator.\n\nVisualizator should be passed to the test_runner before run. Knowing that, \nwe are ready to write the final version of our main module:\n\n// main.cpp\n#include \u003ctut/tut.hpp\u003e\n\nnamespace tut\n{\n  test_runner_singleton runner;\n}\n\nclass callback : public tut::callback\n{\npublic:\n  void run_started(){ std::cout \u003c\u003c \"\\nbegin\"; }\n\n  void test_completed(const tut::test_result\u0026 tr)\n  {\n    std::cout \u003c\u003c tr.test_pos \u003c\u003c \"=\" \u003c\u003c tr.result \u003c\u003c std::flush;\n  }\n\n  void run_completed(){ std::cout \u003c\u003c \"\\nend\"; }\n};\n\nint main()\n{\n  callback clbk;\n  runner.get().set_callback(\u0026clbk);\n\n  // run all tests in all groups\n  runner.get().run_tests();\n  return 0;\n}\n\nThat's it. You are now ready to link and run our test application. Do it as often as possible; \nonce a day is a definite must. I hope, TUT will help you to make your application more \nrobust and relieve your testing pain. Feel free to send your questions, suggestions and \ncritical opinions to me; I'll do my best to address them asap.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrzechonek%2Ftut-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrzechonek%2Ftut-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrzechonek%2Ftut-framework/lists"}