Re: [Cppunit-devel] class overview I: defining tests
Brought to you by:
blep
|
From: Baptiste L. <gai...@fr...> - 2001-07-10 13:24:50
|
Quoting "Steve M. Robbins" <ste...@vi...>:
> class Test
> ----------
>
> CppUnit has a small hierarchy of classes rooted at class Test. Test
> is a pure virtual class with no implementation code. I will assume it
> is intended to be an interface class. By this I mean it exists only
> to define the interface that a "test-like" classes must implement. If
> we were writing Java, it would be an interface.
Just to put it out since we haven't yet, Test/TestCase/TestSuite are
respectively the Component/Leaf/Composite of a Composite pattern.
>
> The Test interface has one principal method:
>
> void run( TestResult* res )
> - run the test and collect the results in res
Don't dismiss getName(), it is an essential element of "reporting" result.
> class TestCase
> --------------
>
> The concrete class that represents a test should be TestCase.
> In fact, however, TestCase is a framework to define one OR MORE tests.
> The detailed description of TestCase says it defines the "fixture
> to run multiple tests".
>
> The term "fixture" is bothering me. The documentation suggests that a
> TestCase _is_ a fixture. If so, why have two terms? Does the word
> "fixture" have a standardized meaning?
>
> TestCase is a subclass of Test, and implements run() as
>
> setUp()
> runTest()
> tearDown()
>
> According to the "cookbook", a TestCase object is intended as the
> basic mechanism for defining a test. One should subclass TestCase
> overriding runTest() appropriately. The code in runTest() would
> typically make a number of assertions.
I'm not sure what "fixture" means in english. The dictionnary did not provide
anything that made sense. The "running" definition I have is that a fixture
provides "context" and "facility" to run a test. I do not know of any
standardized meaning, though it's also used in JUnit if I remember well.
I'll continue the discussion about the TestCase/Fixture in TestCaller...
> class TestCaller
> ----------------
>
> Here is where I really start getting muddled.
>
> A TestCaller is a subclass of TestCase that "provides access to a test
> case method". Although nowhere clarified in the docs, "a test case
> method" appears to refer to any member function of a TestCase that
> takes no parameters and returns void.
>
> All through the TestCaller documentation, the term "fixture" is
> used to mean "a TestCase instance".
>
> As near as I can tell, a TestCaller exists so that one can subclass a
> TestCase, define a number of "void foo(void)" methods in it, and then
> use a TestCaller to make an individual test case out of each such
> method.
>
> I'm confused now as to whether an object of type TestCase should
> be a single test case or a set of related test cases. The name
> suggests it is a single test case. However, the TestCaller class
> appears to promote the practice of defining a set of related tests
> in a TestCase, and then generating a test case (TestCaller) for each.
I finally get why you are so confused. Indeed you pointed out a weird stuff
in the design.
To write a unit test, you basically have two ways:
- subclass TestCase, and override the runTest() method to implement your test.
- have a class that define a setUp() and a tearOff() methods, and some tests
methods. Then create a TestCaller to create a TestCase for each test method.
As you can see, TestCaller does not really need a TestCase. As to why
TestCase is used, you'll have to go down to the original CppUnit version.
The fact are (concerning the use of TestCase to create test suite of
TestCaller):
- TestCase provides a base class, which define setUp() and tearOff() with
empty implementation.
- TestCase defines extraneous methods, such as getName(), runTest()...
- TestCase is not use as a TestCase.
Having a base class is a good thing (At least I think so, it make writing the
macros much easier).
Having the extraneous methods and a "wrong" use of TestCase is confusing.
To solve that problem, I would suggest introducing a new base class:
/*! This class represents an abstract fixture used to run test with TestCaller.
*
* A TestFixture provides a context and facilities to a set of TestCase
* created with a TestCaller.
*
* setUp() is called to initialize the context before running a test, and
* tearOff() to clean up after running a test.
*/
class TestFixture
{
public:
virtual ~TestFixture() {}
virtual void setUp() {}
virtual void tearOff() {}
};
Well, the definition need work, but the idea is there. The TestFixture is a
place where you factor out common piece of code of many test case: object
initialization, comparison...
We would use the TestFixture class, just like we are using the TestCase class
to define many tests:
class MyTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE( MyTest );
CPPUNIT_TEST( testSomething );
CPPUNIT_TEST( testSomethingElse );
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearOff();
void testSomething();
void testSomethingElse();
};
Hope this help solve the confusing stuff. (Note that JUnit is even weirder in
that aspect).
>
> In addition, the TestCaller class is able to ignore an "expected"
> exception in its runTest() method. This is a useful facility!
> Why is it not available in the base TestCase class?
It is more than "ignoring": if the expected exception is not caught, the test
failed. This means that we are implementing a test.
I believe the base TestCase class should remain "test free" (provide facility
to run and report result, but to not do any test). But you rise an interesting
question: should the "exception" expected test be in a TestDecorator rather
than in TestCaller ? This would also make the TestCaller "test free".
> classes TestDecorator, RepeatedTest, TestSetUp
> ----------------------------------------------
>
> TestDecorator and RepeatedTest are straightforward.
>
> TestSetUp is a mystery, though. It appears very similar to
> the TestCase class in that it wraps the Test::run() method
> with virtual setUp() and tearDown() methods. But it doesn't
> do any catching of exceptions like TestCase does.
> What is the purpose of this class?
To use delegation instead of subclassing to set a testing context.
Here is an example of use:
Problem: I need to set a global context each time I run the tests using the
TestRunner. This could be initializing Thread Local Storage to set up the
Database connexion, initializing resource such as COM...
Solution: I provide a way for the user to "wrap" the test being run. The user
could use TestSetUp to do that.
Baptiste.
---
Baptiste Lepilleur <gai...@fr...>
http://gaiacrtn.free.fr/index.html
Language: English, French
|