Next: Class manipulation tool, Previous: Memory handling, Up: Top [Contents][Index]
It is very important testing your classes before you integrate them into a larger part of code. The Unit testing support in ooc is inspired by the Unit 3 testing framework used mainly in Java. If you are not familiar with the topic I recommend some reading about the "test-driven development" approach to catch the basis and importance of unit testing.
Unit test classes are subclasses of TestCase
class, these are called test cases. A test case in ooc is represented by an executable that can be invoked from the operating system or can be run in a simulator in case of microcontrollers.
The test cases have several test methods, that do different tests. These test methods are executed in the order as they are defined in the test method order table.
The test cases could be organized into test suites. The latter means a batch file in ooc that executes the test cases in a sequence.
Its recommended collecting your testable classes in a static directory (*.o in Linux, *.lib in Windows, etc.) to make things simplier. Although this is not necessary, this makes the linking of your testcase more simply, and enables you creating fake classes as needed.
Creating a unit test is easy!
~$ ooc --testsuite
This creates the necessary makefiles and test suite batch files into this directory. Edit those files according to the comments in them as necessary. If you are working with an IDE, you can configure it to use these makefiles for convenience. It can be used with the automake tools as well under Linux.
~$ ooc --new MyClassTest --template test
As a naming convention I recommend using "Test" as a tail for your test classes. This helps identify them, but also lets you use the automatic makefile rules in Linux! In Windows you must add this file to the Makefile: edit it according to the comments in it.
before_class(), before(), after(), after_class()
methods in your test file if they are necessary for your test methods. The skeletons can be found in the created file already.
Implement your test method as a function of the following prototype:
static void myclasstest_method1( MyTest self );
You can expect that the before()
method has been completed succefully before invoking your test method, and the after()
method will be invoked after completing your test method.
The test method can throw an Exception
or a subclass of it. If the exception is uncaught within the method, the test case repots the test as failed. If the Exception
is not caught within your test method, the after()
method will not be invoked!
You must put your test method into the test method order table, to let your test case know what methods to invoke! You can do this using the TEST
macro:
static ROM_ALLOC struct TestCaseMethod methods[] = { TEST(myclasstest_method1), {NULL, NULL} /* Do NOT delete this line! */ };
Important! In ooc your test methods are not independent! This means that if one of your test methods makes changes into any global or test case member variables, or into the environment, the succeeding test methods will inherit those changes. This is a very important difference from Unit 3, where it is garanteed that all test methods starts in a fresh environment.
This is why we do the whole staff! :-) Within our test methods we would like to make assertions on the status of our testable classes. For this purpose we can use a group of assertCondition
macros. (Don’t be confused: these macros do nothing with those ones in the <assert.h> headers!) The assertCondition
macros check the given condition, and if the assertion is not met, print an error message identifying the line of code. The execution of code continues in both cases.
A group of assertCondition
macros can print messages defined by the user as well. These macros look like assertConditionMsg
.
An example for using the assertCondition
macros from the ListTest
unit test case, testing the list iterator:
static void iterator( ListTest self ) { list_append( self->foolist, foo_new() ); assertTrue( self->foolist->first == list_first( self->foolist ) ); assertTrue( self->foolist->last == list_last( self->foolist ) ); assertTrue( list_first( self->foolist ) == list_last( self->foolist ) ); list_append( self->foolist, foo_new() ); assertTrue( self->foolist->first == list_first( self->foolist ) ); assertTrue( self->foolist->last == list_last( self->foolist ) ); assertFalse( list_first( self->foolist ) == list_last( self->foolist ) ); assertTrue( list_last( self->foolist ) == list_next( self->foolist, list_first( self->foolist ) ) ); assertTrue( list_first( self->foolist ) == list_previous( self->foolist, list_last( self->foolist ) ) ); }
For the complete list of assertCondition
macros see the ooc API documentation, or the testcase.h
header file.
There is a macro that reports a failure unconditionally, this is the fail
(or the failMsg
) macro. It prints the error message unconditionally, and continues. It can be used if the condition to be tested is not a simple assertion, and the code runs on a bad path.
Normally the test cases do not print any message on the screen: they inform you about the actual method by printing its name, but this info will be deleted if the method was executed succesfully. On a Linux or Windows system there won’t be any messages left on the display in case of expected results!
~$ ./listtest ~$
If some assertions fails, you will see a message similar to this one:
~$ ./listtest [4] ListTest::iterator() Failed: [listtest.c : 325] Test case ListTest failed: 30/1 (methods run/failed) ~$
This could be an example, if our ListTest::iterator()
method detected an error in the list iterator implementation. Let’s see the meaning of the message!
ListTest::iterator()
informs about the name of the method. The part before the period shows the name of the class, the part after the period identifies the name of the failed test method.
assertConditionMsg
assertion, the message would be displayed here.
In some cases your test method may throw an exception that is not caught by your method. These exceptions are caught by the TestCase class, displayed as follows, and the code execution continues with the next test method.
~$ ./listtest [4] ListTest::iterator() Unexpected exception: Exception, code: 1, user code: 0 ~$
The caught exception was an Exception
class with code 1. Since this is a core ooc exception, evaluating the "exception.h"
header it can be seen, this is err_out_of_memory
, probably our test method run out of memory.
Use the before_class(), before(), after(), after_class()
methods to prepare your test case for test methods. Similar to Unit 3: before_class()
and after_class()
will run only once: when instantiating your test case class, that is a subclass of TestCase
.
The before()
and after()
methods will run for each test method execution. Generally in before()
you can prepare the necessary classes for your test methods, while in after()
you should destroy them.
You can use assertConditionMsg
assertions freely in these virtuals. It might be usefull especially in the after()
method to check that the test method did not corrupt the integrity of your test class.
You can throw ecxeptions in before()
and before_class()
methods, but never throw in after()
!
For complicated test cases you can override these methods, since those are virtual methods (but do not do this, if you do not understand exactly how and when they are called. Check the source code if you have any doubt!)
The exexcution order of the virtual methods is:
For each instantiation of your test case: { Your testcase's parents' constructors Your testcase's constructor For each testcase_run( TestCase ): { Your test's parents' before_class() methods Your test's before_class() method For each test method in the test method table: { Your test's parents' before() methods Your test's before() method Yor test method Your test's after() method Your test's parents' after() methods } Your test's after_class() method Your test's parents' after_class() methods } Your testcase's destructor Your testcase's parents' destructors }
Test the expected exceptions as follows! Let’s check the index overrun behavior of the Vector class:
try { vector_get_item( self->vector, -1 ); /* Should throw an Exception! */ fail(); /* Should never be executed! */ } catch_any assertTrue( exception_get_error_code( exception ) == err_wrong_position ); end_try;
Tests for possible memory leaks is an essential issue in C! Always run your test cases to discover memory leaks, because this helps you make your code robust and reliable!
In ooc the recommended way is using Valgrind (http://valgrind.org), a memory checker (and much more!) tool available under Linux. (For Windows I do not know such a tool, please infrom me if there is a similar possibility.)
To perform a memory check, run your testcase in Valgrind:
~$ valgrind --leak-check=yes --quiet ./myclasstest
To minimize your headache, I recommend running only error free test cases under Valgrind! :-) Hunting for assertion failures together with memory problems could be a nightmare!
Note I.: In the Linux standard C library there may be some optimizations that are reported by Valgrind as a possible memory leak, but in fact they are not an error, especially not yours. If you face with this problem, you can suppress them. To supress them, create an empty test case, run it in Valgrind and create a suppress file. For details see the Valgrind documentation: "2.5. Suppressing errors" (http://valgrind.org/docs/manual/manual-core.html#manual-core.suppress).
Note II.: ooc converts the Unix signals (SIGSEGV, SIGFPE) into ooc exceptions. This let us handle and display those problems the same way as other exceptions in ooc. Unfortunately Valgrind overrides these signal handlers, and as a consequence, your test case can not be run under Valgrind if you force emmitting these Unix signals (e.g. intentionally dereferencing a NULL pointer and checking for SegmentationFault
exception.)
There are several best practices in Unit testing. Some of them could be used in ooc as well.
If you must write many test cases that requires identical preparation or a special environment, then you may consider creating a test case to make this preparation and which could be the parent for your test cases.
ooc TestCase
can be inherited!
In this technique you can mimic the behaviour of an object that is not available during the execution of your test case: you simply replace a class with a fake class that behaves identical (or at least as expected), but does not do its job.
Write your fake class in your test suite directory: all the method names must be identical to the original ones! Link your test case as usual, except that you define your fake object file earlier in the parameter line than the library containing your testable classes. The linker will use the symbols in the order they appear in the parameter list: replacing you class with the fake.
In Linux and Windows this is very simple! Just create your fake class with the ooc tool in the suite directory (you may copy it from your source directory). Do not copy the header file, since it should be the original one! As a naming convention the source file name must NOT end with Test
, tail it with Fake
instead.
Modifiy your code in the fake class. The rest is done by the supplied Makefiles! (In Windows, you must add your file to the Makefile first!)
I have no clue in this moment, how to elaborate this technique in C. :-(
For effective unit testing you need a design in wich your classes do not rely on their dependencies. Establishing your classes uncoupled helps you reuse them much easier and lets you implement unit testing. (For more infromation on this topic I recommend some googling on dependency injection, dependency lookup and such topics in Java.) Theoretically it is possible writing a cotainer class in ooc that could handle the object/class dependencies based on an external descriptor file (e.g. XML), like in some Java implementations. Although it would be possible, it will not be feasible: small microcontollers do not have the power to execute effectively. So I recommend using the "dependency lookup" idiom in ooc with a very simple implementation:
extern void injectMyClass( MyClass self );
This is your injector method for your class.
Static Void MyClass_constructor( MyClass self, void * param ) { chain_constructor( MyClass, self, NULL ); injectMyClass( self ); assert( ooc_isInstanceOf( self->settings, Settings ) ); }
injector.c
.
#include "myclass.h" #include "implement/myclass.h" #include "settings.h" void injectMyClass( MyClass self ) { self->settings = settings_new(); }
You can inject singleton objects too, if you create them before any call to injectors, or you can use reference counted objects for your dependencies alternatively.
Implement your other injectors in this file too.
Altough in Java there is only a idiom called "inject while constructing", in ooc it makes sense "inject while initializing". Implement injectors for your initialization methods separately, when it makes sense. (e.g. Injecting virtual functions, like mixins into the virtual table.)
Important! Take care of your design in your classes, that if there is a circular dependency (typically a reference-back) between your classes, this approach will result in an endless loop! Break up the circular dependency, and pass the back reference as a constructor parameter!
Next: Class manipulation tool, Previous: Memory handling, Up: Top [Contents][Index]