mirror of
				https://github.com/coolaj86/fizzbuzz.git
				synced 2024-11-16 17:29:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			510 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable File
		
	
	
	
	
| Documentation TUT How-To minimum steps to make TUT work for you
 | |
| 
 | |
| What is TUT
 | |
| 
 | |
| TUT is a pure C++ unit test framework. Its name - TUT - stands for 
 | |
| Template Unit Tests.
 | |
| 
 | |
| Features
 | |
| 
 | |
| TUT provides all features required for unit testing:
 | |
| 
 | |
|     * Similar tests can be grouped together into test groups. Each 
 | |
|       test group has its unique name and is located in a separate 
 | |
|       compilation unit. One group can contain almost unlimited number 
 | |
|       of tests (actually, the limit is the compiler template 
 | |
|       recursion depth).
 | |
|     * User can run all the tests (regression), or just some selected 
 | |
|       groups or even some tests in these groups.
 | |
|     * TUT provides special template functions to check the condition 
 | |
|       validity at run-time and to force test failure if required. 
 | |
|       Since C++ doesn't provide a facility for obtaining stack trace 
 | |
|       of the throwed exception and TUT avoids macros, those functions 
 | |
|       accept string marker to allow users easely determine the source 
 | |
|       of exception.
 | |
|     * TUT contains callback that can be implemented by the calling code 
 | |
|       to integrate with an IDE, for example. Callbacks tell listener 
 | |
|       when a new test run started, when test runner switches to the 
 | |
|       next tests group, when a test was completed (and what result it 
 | |
|       has), and when test run was finished. The callbacks allow users 
 | |
|       to produce their own visualization format for test process and results.
 | |
|     * Being a template library, it doesn't need compilation; just 
 | |
|       include the <tut.h> header into the test modules.
 | |
| 
 | |
| TUT tests organization
 | |
| 
 | |
| Test application
 | |
| 
 | |
| C++ produces executable code, so tests have to be compiled into a single 
 | |
| binary called test application. The application can be built in automated 
 | |
| mode to perform nightly tests. They also can be built manually when a 
 | |
| developer hunts for bugs.
 | |
| 
 | |
| The test application contains tests, organized into test groups.
 | |
| 
 | |
| Test groups
 | |
| 
 | |
| The functionality of a tested application can be divided into a few separate 
 | |
| function blocks (e.g. User Rights, Export, Processing, ...). It is natural 
 | |
| to group together tests for each block. TUT invokes this test group. Each 
 | |
| test group has a unique human-readable name and normally is located in a 
 | |
| separate file.
 | |
| 
 | |
| Tests
 | |
| 
 | |
| Each single test usually checks only one specific element of functionality. 
 | |
| For example, for a container a test could check whether size() call 
 | |
| returns zero after the successful call to the clear() method.
 | |
| 
 | |
| Writing simple test
 | |
| 
 | |
| Preamble
 | |
| 
 | |
| You are going to create a new class for your application. You decided to 
 | |
| write tests for the class to be sure it works while you are developing or, 
 | |
| possibly, enhancing it. Let's consider your class is shared pointer: 
 | |
| std::auto_ptr-alike type that shares the same object among instances.
 | |
| 
 | |
| Prior to test writing, you should decide what to test. Maximalist's 
 | |
| approach requires to write so many tests that altering any single 
 | |
| line of your production code will break at least one of them. 
 | |
| Minimalist's approach allows one to write tests only for the most 
 | |
| general or the most complex use cases. The truth lies somewhere in 
 | |
| between, but only you, developer, know where. You should prepare 
 | |
| common successful and unsuccessful scenarios, and the scenarios for 
 | |
| testing any other functionality you believe might be broken in some way.
 | |
| 
 | |
| For our shared_ptr we obviosly should test constructors, assignment operators, referencing and passing ownership.
 | |
| 
 | |
| Skeleton
 | |
| 
 | |
| If you don't have any implemented class to test yet, it would be good to 
 | |
| implement it as a set of stubs for a first time. Thus you'll get an 
 | |
| interface, and be able to write your tests. Yes, that's correct: you 
 | |
| should write your tests before writing code! First of all, writing tests 
 | |
| often helps to understand oddities in the current interface, and fix it. 
 | |
| Secondly, with the stubs all your tests will fail, so you'll be sure 
 | |
| they do their job.
 | |
| 
 | |
| Creating Test Group
 | |
| 
 | |
| Since we're writing unit tests, it would be a good idea to group the 
 | |
| tests for our class in one place to be able to run them separately. 
 | |
| It's also natural in C++ to place all the grouped tests into one 
 | |
| compilation unit (i.e. source file). So, to begin, we should create 
 | |
| a new file. Let's call it test_shared_ptr.cpp. (Final variant of the 
 | |
| test group can be found in examples/shared_ptr subdirectory of the 
 | |
| distribution package)
 | |
| 
 | |
| // test_shared_ptr.cpp
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
| };
 | |
|             
 | |
| 
 | |
| As you see, you need to include TUT header file (as expected) and 
 | |
| use namespace tut for tests. You may also use anonymous namespace if 
 | |
| your compiler allows it (you will need to instantiate methods from 
 | |
| tut namespace and some compilers refuse to place such instantiations 
 | |
| into the anonymous namespace).
 | |
| 
 | |
| A test group in TUT framework is described by the special template 
 | |
| test_group<T>. The template parameter T is a type that will hold all 
 | |
| test-specific data during the test execution. Actually, the data 
 | |
| stored in T are member data of the test. Test object is inherited 
 | |
| from T, so any test can refer to the data in T as its member data.
 | |
| 
 | |
| For simple test groups (where all data are stored in test local 
 | |
| variables) type T is an empty struct.
 | |
| 
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|   struct shared_ptr_data
 | |
|   { 
 | |
|   };
 | |
| }
 | |
|             
 | |
| But when tests have complex or repeating creation phase, you may put 
 | |
| data members into the T and provide constructor (and, if required, 
 | |
| destructor) for it. For each test, a new instance of T will be 
 | |
| created. To prepare your test for execution TUT will use default 
 | |
| constructor. Similarly, after the test has been finished, TUT 
 | |
| calls the destructor to clean up T. I.e.:
 | |
| 
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|   struct complex_data
 | |
|   {
 | |
|     connection* con;
 | |
|     complex_data(){ con = db_pool.get_connection(); }
 | |
|     ~complex_data(){ db_pool.release_connection(con); }
 | |
|   };
 | |
| 
 | |
|   // each test from now will have con data member initialized
 | |
|   // by constructor:
 | |
|   ...
 | |
|   con->commit();
 | |
|   ...
 | |
|             
 | |
| 
 | |
| What will happen if the constructor throws an exception? TUT will treat 
 | |
| it as if test itself failed with exception, so this test will 
 | |
| not be executed. You'll see an exception mark near the test, and 
 | |
| if the constructor throwed something printable, a certain message will appear.
 | |
| 
 | |
| Exception in destructor is threated a bit different. Reaching destruction 
 | |
| phase means that the test is passed, so TUT marks test with warning 
 | |
| status meaning that test itself was OK, but something bad has happend 
 | |
| after the test.
 | |
| 
 | |
| Well, all we have written so far is just a type declaration. To work 
 | |
| with a group we have to have an object, so we must create the test group 
 | |
| object. Since we need only one test group object for each unit, we can 
 | |
| (and should, actually) make this object static. To prevent name clash with 
 | |
| other test group objects in the namespace tut, we should provide a 
 | |
| descriptive name, or, alternatively, we may put it into the anonymous 
 | |
| namespace. The former is more correct, but a descriptive name usually works 
 | |
| well too, unless you're too terse in giving names to objects.
 | |
| 
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|     struct shared_ptr_data
 | |
|     {
 | |
| 
 | |
|     };
 | |
| 
 | |
|     typedef test_group<shared_ptr_data> tg;
 | |
|     tg shared_ptr_group("shared_ptr");
 | |
| };
 | |
|             
 | |
| As you see, any test group accepts a single parameter - its human-readable 
 | |
| name. This name is used to identify the group when a programmer wants to 
 | |
| execute all tests or a single test within the group. So this name shall 
 | |
| also be descriptive enough to avoid clashes. Since we're writing tests 
 | |
| for a specific unit, it's enough to name it after the unit name.
 | |
| 
 | |
| Test group constructor will be called at unspecified moment at the test 
 | |
| application startup. The constructor performs self-registration; it calls 
 | |
| tut::runner and asks it to store the test group object name and location. 
 | |
| Any other test group in the system undergoes the same processing, i.e. 
 | |
| each test group object registers itself. Thus, test runner can iterate 
 | |
| all test groups or execute any test group by its name.
 | |
| 
 | |
| Newly created group has no tests associated with it. To be more precise, 
 | |
| it has predefined set of dummy tests. By default, there are 50 tests in a 
 | |
| group, including dummy ones. To create a test group with higher volume 
 | |
| (e.g. when tests are generated by a script and their number is higher) 
 | |
| we must provide a higher border of test group size when it is instantiated:
 | |
| 
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|     struct huge_test_data
 | |
|     {
 | |
|     };
 | |
| 
 | |
|     // test group with maximum 500 tests
 | |
|     typedef test_group<huge_test_data,500> testgroup;
 | |
|     testgroup huge_test_testgroup("huge group");
 | |
| };
 | |
|             
 | |
| 
 | |
| Note also, that your compiler will possibly need a command-line switch 
 | |
| or pragma to enlarge recursive instantiation depth. For g++, for 
 | |
| example, you should specify at least --ftemplate-depth-501 to increase 
 | |
| the depth. For more information see your compiler documentation.
 | |
| 
 | |
| Creating Tests
 | |
| 
 | |
| Now it's time to fill our test group with content.
 | |
| 
 | |
| In TUT, all tests have unique numbers inside the test group. Some 
 | |
| people believe that textual names better describe failed tests in 
 | |
| reports. I agree; but in reality C++ templates work good with numbers 
 | |
| because they are compile-time constants and refuse to do the same 
 | |
| with strings, since strings are in fact addresses of character 
 | |
| buffers, i.e. run-time data.
 | |
| 
 | |
| As I mentioned above, our test group already has a few dummy tests; 
 | |
| and we can replace any of them with something real just by writing 
 | |
| our own version:
 | |
| 
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|     struct shared_ptr_data{};
 | |
| 
 | |
|     typedef test_group<shared_ptr_data> testgroup;
 | |
|     typedef testgroup::object testobject;
 | |
|     testgroup shared_ptr_testgroup("shared_ptr");
 | |
| 
 | |
|     template<>
 | |
|     template<>
 | |
|     void testobject::test<1>()
 | |
|     {
 | |
|         // do nothing test
 | |
|     }
 | |
| };
 | |
|         
 | |
| 
 | |
| So far this test does nothing, but it's enough to illustrate the concept.
 | |
| 
 | |
| All tests in the group belong to the type test_group<T>::object. This 
 | |
| class is directly inherited from our test data structure. In our case, it is
 | |
| 
 | |
| class object : public shared_ptr_data { ... }
 | |
|         
 | |
| This allows to access members of the data structure directly, since at 
 | |
| the same time they are members of the object type. We also typedef the 
 | |
| type with testobject for brevity.
 | |
| 
 | |
| We mark our test with number 1. Previously, test group had a dummy test 
 | |
| with the same number, but now, since we've defined our own version, it 
 | |
| replaces the dummy test as more specialized one. It's how C++ template 
 | |
| ordering works.
 | |
| 
 | |
| The test we've written always succeeds. Successful test returns with no 
 | |
| exceptions. Unsuccessful one either throws an exception, or fails at 
 | |
| fail() or ensure() methods (which anyway just throw the exception when failed).
 | |
| 
 | |
| First real test
 | |
| 
 | |
| Well, now we know enough to write the first real working test. This test 
 | |
| will create shared_ptr instances and check their state. We will define a 
 | |
| small structure (keepee) to use it as shared_ptr stored object type.
 | |
| 
 | |
| #include <tut.h>
 | |
| #include <shared_ptr.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|     struct shared_ptr_data
 | |
|     {
 | |
|         struct keepee{ int data; };
 | |
|     };
 | |
| 
 | |
|     typedef test_group<shared_ptr_data> testgroup;
 | |
|     typedef testgroup::object testobject;
 | |
|     testgroup shared_ptr_testgroup("shared_ptr");
 | |
| 
 | |
|     /**
 | |
|      * Checks default constructor.
 | |
|      */
 | |
|     template<>
 | |
|     template<>
 | |
|     void testobject::test<1>()
 | |
|     {
 | |
|         shared_ptr<keepee> def;
 | |
|         ensure("null",def.get() == 0);
 | |
|     }
 | |
| };
 | |
|         
 | |
| 
 | |
| That's all! The first line creates shared_ptr. If constructor throws 
 | |
| an exception, test will fail (exceptions, including '...', are catched 
 | |
| by the TUT framework). If the first line succeeds, we must check 
 | |
| whether the kept object is null one. To do this, we use test object 
 | |
| member function ensure(), which throws std::logic_error with a given 
 | |
| message if its second argument is not true. Finally, if destructor of 
 | |
| shared_ptr fails with exception, TUT also will report this test as failed.
 | |
| 
 | |
| It's equally easy to write a test for the scenario where we expect to get 
 | |
| an exception: let's consider our class should throw an exception if it 
 | |
| has no stored object, and the operator -> is called.
 | |
| 
 | |
| /**
 | |
|  * Checks operator -> throws instead of returning null.
 | |
|  */
 | |
| template<>
 | |
| template<>
 | |
| void testobject::test<2>()
 | |
| {
 | |
|     try
 | |
|     {
 | |
|         shared_ptr<keepee> sp;
 | |
|         sp->data = 0;
 | |
|         fail("exception expected");
 | |
|     }
 | |
|     catch( const std::runtime_error& ex )
 | |
|     {
 | |
|         // ok
 | |
|     }
 | |
| }
 | |
|         
 | |
| 
 | |
| Here we expect the std::runtime_error. If operator doesn't throw it, 
 | |
| we'll force the test to fail using another member function: fail(). It 
 | |
| just throws std::logic_error with a given message. If operator throws 
 | |
| anything else, our test will fail too, since we intercept only 
 | |
| std::runtime_error, and any other exception means the test has failed.
 | |
| 
 | |
| NB: our second test has number 2 in its name; it can, actually, be any 
 | |
| in range 1..Max; the only requirement is not to write tests with the 
 | |
| same numbers. If you did, compiler will force you to fix them anyway.
 | |
| 
 | |
| And finally, one more test to demonstrate how to use the 
 | |
| ensure_equals template member function:
 | |
| 
 | |
| /**
 | |
|  * Checks keepee counting.
 | |
|  */
 | |
| template<>
 | |
| template<>
 | |
| void testobject::test<3>()
 | |
| {
 | |
|     shared_ptr<keepee> sp1(new keepee());
 | |
|     shared_ptr<keepee> sp2(sp1);
 | |
|     ensure_equals("second copy at sp1",sp1.count(),2);
 | |
|     ensure_equals("second copy at sp2",sp2.count(),2);
 | |
| }
 | |
|         
 | |
| 
 | |
| The test checks if the shared_ptr correctly counts references during 
 | |
| copy construction. What's interesting here is the template member 
 | |
| ensure_equals. It has an additional functionality comparing with similar 
 | |
| call ensure("second_copy",sp1.count()==2); it uses operator == to check 
 | |
| the equality of the two passed parameters and, what's more important, it 
 | |
| uses std::stream to format the passed parameters into a human-readable 
 | |
| message (smth like: "second copy: expected 2, got 1"). It means that 
 | |
| ensure_equals cannot be used with the types that don't have operator <<; 
 | |
| but for those having the operator it provides much more informational message.
 | |
| 
 | |
| In contrast to JUnit assertEquals, where the expected value goes before 
 | |
| the actual one, ensure_equals() accepts the expected after the actual 
 | |
| value. I believe it's more natural to read ensure_equals("msg", count, 5) 
 | |
| as "ensure that count equals to 5" rather than JUnit's 
 | |
| "assert that 5 is the value of the count".
 | |
| 
 | |
| Running tests
 | |
| 
 | |
| Tests are already written, but an attempt to run them will be unsuccessful. 
 | |
| We need a few other bits to complete the test application.
 | |
| 
 | |
| First of all, we need a main() method, simply because it must be in all 
 | |
| applications. Secondly, we need a test runner singleton. Remember I said 
 | |
| each test group should register itself in singleton? So, we need that 
 | |
| singleton. And, finally, we need a kind of a callback handler to visualize 
 | |
| our test results.
 | |
| 
 | |
| The design of TUT doesn't strictly set a way the tests are visualized; 
 | |
| instead, it provides an opportunity to get the test results by means of 
 | |
| callbacks. Moreover it allows user to output the results in any format he 
 | |
| prefers. Of course, there is a "reference implementation" in the 
 | |
| example/subdirectory of the project.
 | |
| 
 | |
| Test runner singleton is defined in tut.h, so all we need to activate it 
 | |
| is to declare an object of the type tut::test_runner_singleton in the main 
 | |
| module with a special name tut::runner.
 | |
| 
 | |
| Now, with the test_runner we can run tests. Singleton has method get() 
 | |
| returning a reference to an instance of the test_runner class, which in 
 | |
| turn has methods run_tests() to run all tests in all groups, 
 | |
| run_tests(const std::string& groupname) to run all tests in a given group 
 | |
| and run_test(const std::string& grp,int n) to run one test in the specified group.
 | |
| 
 | |
| // main.cpp
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|     test_runner_singleton runner;
 | |
| }
 | |
| 
 | |
| int main()
 | |
| {
 | |
|     // run all tests in all groups
 | |
|     runner.get().run_tests();
 | |
| 
 | |
|     // run all tests in group "shared_ptr"
 | |
|     runner.get().run_tests("shared_ptr");
 | |
| 
 | |
|     // run test number 5 in group "shared_ptr"
 | |
|     runner.get().run_test("shared_ptr",5);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
|   
 | |
| It's up to user to handle command-line arguments or GUI messages and map those 
 | |
| arguments/messages to actual calls to test runner. Again, as you see, TUT 
 | |
| doesn't restrict user here.
 | |
| 
 | |
| But, the last question is still unanswered: how do we get our test results? 
 | |
| The answer lies inside tut::callback interface. User shall create its subclass, 
 | |
| and write up to three simple methods. He also can omit any method since they 
 | |
| have default no-op implementation. Each corresponding method is called in the 
 | |
| following cases:
 | |
| 
 | |
|     * a new test run started;
 | |
|     * test finished;
 | |
|     * test run finished.
 | |
| 
 | |
| Here is a minimal implementation:
 | |
| 
 | |
| class visualizator : public tut::callback
 | |
| {
 | |
| public:
 | |
|   void run_started(){ }
 | |
| 
 | |
|   void test_completed(const tut::test_result& tr)
 | |
|   {
 | |
|       // ... show test result here ...
 | |
|   }
 | |
| 
 | |
|   void run_completed(){ }
 | |
| };        
 | |
| 
 | |
| The most important is the test_completed() method; its parameter has type 
 | |
| test_result, and contains everything about the finished test, from its group 
 | |
| name and number to the exception message, if any. Member result is an enum 
 | |
| that contains status of the test: ok, fail or ex. Take a look at the 
 | |
| examples/basic/main.cpp for more complete visualizator.
 | |
| 
 | |
| Visualizator should be passed to the test_runner before run. Knowing that, 
 | |
| we are ready to write the final version of our main module:
 | |
| 
 | |
| // main.cpp
 | |
| #include <tut.h>
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|   test_runner_singleton runner;
 | |
| }
 | |
| 
 | |
| class callback : public tut::callback
 | |
| {
 | |
| public:
 | |
|   void run_started(){ std::cout << "\nbegin"; }
 | |
| 
 | |
|   void test_completed(const tut::test_result& tr)
 | |
|   {
 | |
|     std::cout << tr.test_pos << "=" << tr.result << std::flush;
 | |
|   }
 | |
| 
 | |
|   void run_completed(){ std::cout << "\nend"; }
 | |
| };
 | |
| 
 | |
| int main()
 | |
| {
 | |
|   callback clbk;
 | |
|   runner.get().set_callback(&clbk);
 | |
| 
 | |
|   // run all tests in all groups
 | |
|   runner.get().run_tests();
 | |
|   return 0;
 | |
| }
 | |
|         
 | |
| That's it. You are now ready to link and run our test application. Do it as often as possible; 
 | |
| once a day is a definite must. I hope, TUT will help you to make your application more 
 | |
| robust and relieve your testing pain. Feel free to send your questions, suggestions and 
 | |
| critical opinions to me; I'll do my best to address them asap.
 |