Thread: [Cppunit-devel] class overview I: defining tests
Brought to you by:
blep
|
From: Steve M. R. <ste...@vi...> - 2001-07-08 03:53:08
|
In response to Baptiste's message about reorganizing include/, I expressed a desire to understand the overall structure of CppUnit. I have gleaned some information from Baptiste's response and from poring over the code. With this summary (in two parts) of what I've learned so far, I'm hoping to provoke some more discussion. I'm ignoring the assertions and helper macros, for now. Suppose I want to incorporate testing into my software. There are two fundamental operations that need to be supported by the CppUnit testing framework. First, I need to define the tests. Second, I need to execute them and report results. This message deals with classes of the first category. A companion message deals with classes for reporting results. 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. The Test interface has one principal method: void run( TestResult* res ) - run the test and collect the results in res 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. class TestSuite --------------- Straightforward collection of Test objects. The run() method calls run() on each member of the collection in turn. 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. 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? 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? -Steve -- by Rocket to the Moon, by Airplane to the Rocket, by Taxi to the Airport, by Frontdoor to the Taxi, by throwing back the blanket and laying down the legs ... - They Might Be Giants |
|
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
|
|
From: Steve M. R. <ste...@vi...> - 2001-07-11 14:42:38
|
Hi,
Baptiste: thanks for your comments; they are helping clarify the
CppUnit design in my mind.
Everyone: other comments welcome!
On Tue, Jul 10, 2001 at 03:24:45PM +0200, Baptiste Lepilleur wrote:
> 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.
Thanks! That's a useful pointer.
> > 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.
True. While writing, I was in the mind frame of someone who wanted to
understand "how do I write a test case for my code". The method
getName() should have appeared in part II, but I think I forgot to
mention it there, too.
> > 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.
OK. I have not looked at JUnit. Does anyone know whether JUnit originated
the term, or whether it has an antecedent?
I think the term is being used, as you suggest, as a synonym for "framework",
except that it is "smaller" than a framework or a sub-part of a framework.
Something like: "fixture" is to "framework" as "test case" is to "test suite"?
> > 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:
[ ... TestFixture ... ]
Yes, I think this would clarify the design.
To reiterate, TestFixture would contain setUp() and tearDown()
methods. [You keep using "tearOff", but I assume you really mean
"tearDown", yes?] You would subclass this, define void methods, and
use the latter as a parameter to TestCaller to create test cases.
With TestFixture, it seems that TestCase may be defined as
class TestCase : public Test, public TestFixture
and supply implementations of the Test methods, like run().
> Hope this help solve the confusing stuff. (Note that JUnit is even weirder in
> that aspect).
Oh dear! Maybe I *shouldn't* have a look at JUnit after all. I'm already
confused enough :-/
> > 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.
True. I was being sloppy in my description.
> 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".
Whatever the mechanism, it just seems to me that a test case defined
1. by subclassing TestCase, or
2. by subclassing TestFixture and invoking TestCaller
ought to have the same facilities, including testing for "expected
exceptions".
If you mean to make a new TestDecorator subclass for expecting
exceptions, akin to the existing RepeatedTest, then yes; to me, that
sounds like the way to procede. It has the added virtue of simpler
code in the common case, when the proposed decorator is NOT used.
> > 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.
I'm not sure I understand. Is the set up code to be run once at the beginning
of a test run? Or is it to be run just before each individual test case?
Do you have a real-life example use of TestSetUp that shows why it is
superior to other means (subclassing?) of achieving the same end?
-Steve
--
by Rocket to the Moon,
by Airplane to the Rocket,
by Taxi to the Airport,
by Frontdoor to the Taxi,
by throwing back the blanket and laying down the legs ...
- They Might Be Giants
|
|
From: Baptiste L. <bl...@cl...> - 2001-07-11 20:24:21
|
----- Original Message -----
From: "Steve M. Robbins" <ste...@vi...>
To: <cpp...@li...>
Sent: Wednesday, July 11, 2001 4:42 PM
Subject: Re: [Cppunit-devel] class overview I: defining tests
> Hi,
>
> Baptiste: thanks for your comments; they are helping clarify the
> CppUnit design in my mind.
>
> Everyone: other comments welcome!
> [...]
> > > 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?
> > >
> > [...]
> > 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.
>
> OK. I have not looked at JUnit. Does anyone know whether JUnit
originated
> the term, or whether it has an antecedent?
>
> I think the term is being used, as you suggest, as a synonym for
"framework",
> except that it is "smaller" than a framework or a sub-part of a framework.
> Something like: "fixture" is to "framework" as "test case" is to "test
suite"?
I don't really see where it relate to "framework", since the only reuse
there is for a fixture is in the few tests you write using that fixture.
Fixture aren't usally designed for extensibiliy... Oh well, as long as we
know what we are refering to when we are talking about Fixture, it will do.
That term might be specific to "testing". JUnit introduced a framework,
but the way testing is done is just like the "traditionnal" way with
traditionnal tool (I think): set up a context, run a test....
> > > class TestCaller
> > > ----------------
> > > [...]
> > To solve that problem, I would suggest introducing a new base class:
> [ ... TestFixture ... ]
>
> Yes, I think this would clarify the design.
>
> To reiterate, TestFixture would contain setUp() and tearDown()
> methods. [You keep using "tearOff", but I assume you really mean
> "tearDown", yes?] You would subclass this, define void methods, and
> use the latter as a parameter to TestCaller to create test cases.
Yes, that is exactly what I mean. As you may have already noted, I have
some memory trouble ;-). It is indeed tearDown (one of a reason I use
WorkspaceWhiz to generate my test case, otherwise I would never have them
running!).
> With TestFixture, it seems that TestCase may be defined as
>
> class TestCase : public Test, public TestFixture
>
> and supply implementations of the Test methods, like run().
I guess so. Hope it doesn't confuse people (TestCaller is a TestCase,
and therefore a TestFixture, and it takes another TestFixture in the
constructor!)
> > Hope this help solve the confusing stuff. (Note that JUnit is even
weirder in
> > that aspect).
>
> Oh dear! Maybe I *shouldn't* have a look at JUnit after all. I'm already
> confused enough :-/
Well, the weird stuff is that the TestCaller is the TestCase itself in
their case. They pass the name of the method to call in the constructor of
TestCase (that why you always need to specify such a constructor). On the
other hand, there are lots of good ideas, probably more documentations.
CppUnit is strongly based on JUnit.
> > [...]
> > 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".
>
> Whatever the mechanism, it just seems to me that a test case defined
>
> 1. by subclassing TestCase, or
> 2. by subclassing TestFixture and invoking TestCaller
>
> ought to have the same facilities, including testing for "expected
> exceptions".
Yes, that why I'd like to remove the "test" from the TestCaller, to keep
it "test free" like the TestCase.
> If you mean to make a new TestDecorator subclass for expecting
> exceptions, akin to the existing RepeatedTest, then yes; to me, that
> sounds like the way to procede. It has the added virtue of simpler
> code in the common case, when the proposed decorator is NOT used.
Yes that what I mean. It would also make it much easier to handle
specific case. For example, in many of the TestCases I'm writing, I also
need to check if the exception point out the correct "cause", then I need to
clean up the exception by calling a Destroy method. having a decorator that
could be subclassed would make life much easier.
> > > 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.
>
> I'm not sure I understand. Is the set up code to be run once at the
beginning
> of a test run? Or is it to be run just before each individual test case?
>
> Do you have a real-life example use of TestSetUp that shows why it is
> superior to other means (subclassing?) of achieving the same end?
Not yet. The example I give is what I plan to add some day to
TestRunner. I need to be able to set up the thread environment (the tests
are running in a separate thead).
TestSetUp is by no mean superior to subclassing. It just that sometime
you can not subclass (TestRunner does not publish the ActiveTest class). I
haven't find any use (at the current time) for TestSetUp when writing unit
test using TestFixture/TestCaller.
When you are using TestSetUp, the decorated TestCase become a "Strategy"
for TestSetUp. So the usual Strategy vs Template Method arguments enter in
play here (delegation vs subclassing). I guess it would be interesting to
use TestSetUp if you can reuse your TestSetUp for more than one test (for
example setting up some files, or setting up a thread context).
---
Baptiste Lepilleur <gai...@fr...> http://gaiacrtn.free.fr/index.html
Author of The Text Reformatter, a tool for fanfiction readers and writers.
Language: English, French (Well, I'm French).
|