Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Is CppUnit un-C++

Reply
Thread Tools

Is CppUnit un-C++

 
 
Steven T. Hatton
Guest
Posts: n/a
 
      09-04-2004
I finally got this thing to build. There's something to be said for using
the release of the cvs image sometimes. :-/

I started reading the docs, and this example struck me as a fundamentally
bad design for C++. Perhaps it's not bad design in the sense that it will
fail, or that it can't be maintained. But there seems to be something
fundamentally un-C++ about this. Does anybody else see what I'm talking
about here?

class ComplexNumberTest : public CppUnit::TestFixture {
private:
Complex *m_10_1, *m_1_1, *m_11_2;
public:
void setUp()
{
m_10_1 = new Complex( 10, 1 );
m_1_1 = new Complex( 1, 1 );
m_11_2 = new Complex( 11, 2 );
}

void tearDown()
{
delete m_10_1;
delete m_1_1;
delete m_11_2;
}

void testEquality()
{
CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
}

void testAddition()
{
CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
}
};
--
"[M]y dislike for the preprocessor is well known. Cpp is essential in C
programming, and still important in conventional C++ implementations, but
it is a hack, and so are most of the techniques that rely on it. ...I think
the time has come to be serious about macro-free C++ programming." - B. S.

 
Reply With Quote
 
 
 
 
David Hilsee
Guest
Posts: n/a
 
      09-04-2004
"Steven T. Hatton" <(E-Mail Removed)> wrote in message
news:(E-Mail Removed)...
> I finally got this thing to build. There's something to be said for using
> the release of the cvs image sometimes. :-/
>
> I started reading the docs, and this example struck me as a fundamentally
> bad design for C++. Perhaps it's not bad design in the sense that it will
> fail, or that it can't be maintained. But there seems to be something
> fundamentally un-C++ about this. Does anybody else see what I'm talking
> about here?

<snip>

Yes, it looks like Java. The giveaway is the use of plain pointers and new
instead of using local Complex objects or, at the very least, smart
pointers. That's not surprising, because (IIRC) CppUnit was derived from
JUnit. IMHO, it's not an indication of a bad design; it's just a bad
example that isn't using some of the better features of C++. Is there
something else that you don't like about it, besides the fact that it's
overusing new and ignoring potential memory leaks?

--
David Hilsee


 
Reply With Quote
 
 
 
 
Steven T. Hatton
Guest
Posts: n/a
 
      09-04-2004
David Hilsee wrote:

> Yes, it looks like Java. The giveaway is the use of plain pointers and
> new instead of using local Complex objects or, at the very least, smart
> pointers.


I agree that using local variables would make a lot of sense. There are
conflicting objectives I've run into in this regard. If I do use local
variables for class types, they require that I #include the headers
containing their definitions in the header containing the class I'm
defining. That is, instead of simply forward declaration. It also means I
may introduce more compiler dependencies. I'd have to think a bit to give
an example, but I know there can be problems caused by having class types
as members, rather than pointers to them.

OTOH, it makes resource management much simpler.

> That's not surprising, because (IIRC) CppUnit was derived from
> JUnit.


That's what the documentation says.

> IMHO, it's not an indication of a bad design; it's just a bad
> example that isn't using some of the better features of C++. Is there
> something else that you don't like about it, besides the fact that it's
> overusing new and ignoring potential memory leaks?


I was thinking about something that would not be an issue if the Complex
objects were member variables. But if there were a compelling argument for
using pointers, the way things are organized seems to neglect something
fundamental in C++. It's a four letter acronym.

--
"[M]y dislike for the preprocessor is well known. Cpp is essential in C
programming, and still important in conventional C++ implementations, but
it is a hack, and so are most of the techniques that rely on it. ...I think
the time has come to be serious about macro-free C++ programming." - B. S.

 
Reply With Quote
 
Derrick Coetzee
Guest
Posts: n/a
 
      09-04-2004
Steven T. Hatton wrote:
> If I do use local variables for class types, they require that I #include
> the headers containing their definitions in the header containing the class I'm
> defining. That is, instead of simply forward declaration.


Typically, nothing in a header file contains local variables anyway,
except inline functions. And if you have an inline function using a
class, presumedly you'd want to call methods on it - which would require
the class's header anyway. This argument isn't very strong.

> It also means I may introduce more compiler dependencies.


Be happy if the compiler is keeping track of your dependencies instead
of you. See the make manual for an example of code to automatically
generate makefiles describing header dependencies. Other, more
sophisticated build tools do this automatically.

> ...but I know there can be problems caused by having class types
> as members, rather than pointers to them.


Actually I think it's preferable to include objects rather than pointers
to them, where possible. It's often more efficient, since it avoids an
indirection during calls, and it's less error-prone, since you don't
have to allocate or deallocate the object (although using auto_ptr also
suffices). The main problem is that without a pointer or reference you
can't invoke polymorphic behaviour on data members, and every object can
have only one place where it's stored (redundancy and its update issues
aside).
--
Derrick Coetzee
I grant this newsgroup posting into the public domain. I disclaim all
express or implied warranty and all liability. I am not a professional.
 
Reply With Quote
 
Steven T. Hatton
Guest
Posts: n/a
 
      09-05-2004
Derrick Coetzee wrote:

> Steven T. Hatton wrote:
>> If I do use local variables for class types, they require that I #include
>> the headers containing their definitions in the header containing the
>> class I'm
>> defining. That is, instead of simply forward declaration.

>
> Typically, nothing in a header file contains local variables anyway,
> except inline functions.


In this context it seems clear that /local/ was meant to signify 'class
local'. That is to say class type used as members of the class being
defined. I would not have introduced the word 'local' to describe such a
member, but I believe it is technically correct. Add to the following that
a class definition is a class specifier declaration:

"So that several statements can be used where one is expected, the compound
statement (also, and equiva-
lently, called "block") is provided.
compound-statement:
{ statement-seqopt }
statement-seq:
statement
statement-seq statement
A compound statement defines a local scope (3.3). [Note: a declaration is a
statement (6.7). ]" - ISO/IEC 14882:20036.3

>> It also means I may introduce more compiler dependencies.

>
> Be happy if the compiler is keeping track of your dependencies instead
> of you. See the make manual for an example of code to automatically
> generate makefiles describing header dependencies. Other, more
> sophisticated build tools do this automatically.


"C++ allows the programmer to expose the representation of a class as part
of the interface. This representation may be hidden (using /private/
or /protected/), but it is available to the compiler to allow allocation of
automatic variables, to allow inline substitution of functions, etc. The
negative effect of this is that the use of class types in the
representation of a class may introduce undesirable dependencies" -
TC++PL(SE)24.4.2

One example that comes to mind is when you have some kind of recursive
reference. You end up with ODR violations.

>> ...but I know there can be problems caused by having class types
>> as members, rather than pointers to them.

>
> Actually I think it's preferable to include objects rather than pointers
> to them, where possible. It's often more efficient, since it avoids an
> indirection during calls, and it's less error-prone, since you don't
> have to allocate or deallocate the object (although using auto_ptr also
> suffices).


Yes, that is what I meant in my previous post when I said it makes resource
management much simpler.

> The main problem is that without a pointer or reference you
> can't invoke polymorphic behaviour on data members, and every object can
> have only one place where it's stored (redundancy and its update issues
> aside).


You still have polymorphism as is demonstrated by the code below. I don't
understand your comment about an object having only one location. That
will always be true.

#include <iostream>
#include <string>

class Printable{
public:
Printable()
{}
virtual ~Printable()
{}
virtual std:stream& print(std:stream& out) const = 0;
};

std:stream& operator<<(std:stream& out, const Printable& p)
{
return p.print(out);
}

class A : public Printable {

public:
A(const std::string& data="Class A")
: m_data(data)
{}

virtual ~A()
{}

virtual std:stream& print(std:stream& out) const
{
out << m_data << ". Invoked on A.\n";
}
protected:
std::string m_data;
};

class B ublic A{
public:
B()
: A("Class B")
{}

virtual ~B()
{}

virtual std:stream& print(std:stream& out) const
{
out << m_data << ". Invoked on B.\n";
}
};

class C{
A a;
B b;
public:
A* getA_ptr()
{
return &a;
}

A* getB_ptr()
{
return &b;
}
};


int main(){
C c;
A* a_ptr = c.getA_ptr();

std::cout << "c.getA_ptr() " << *a_ptr;

a_ptr = c.getB_ptr();
std::cout << "c.getB_ptr() " << *a_ptr;
}

--
"[M]y dislike for the preprocessor is well known. Cpp is essential in C
programming, and still important in conventional C++ implementations, but
it is a hack, and so are most of the techniques that rely on it. ...I think
the time has come to be serious about macro-free C++ programming." - B. S.

 
Reply With Quote
 
David Hilsee
Guest
Posts: n/a
 
      09-06-2004
"Steven T. Hatton" <(E-Mail Removed)> wrote in message
news:(E-Mail Removed)...
> Derrick Coetzee wrote:
>
> > Steven T. Hatton wrote:
> >> If I do use local variables for class types, they require that I

#include
> >> the headers containing their definitions in the header containing the
> >> class I'm
> >> defining. That is, instead of simply forward declaration.

> >
> > Typically, nothing in a header file contains local variables anyway,
> > except inline functions.

>
> In this context it seems clear that /local/ was meant to signify 'class
> local'. That is to say class type used as members of the class being
> defined. I would not have introduced the word 'local' to describe such a
> member, but I believe it is technically correct. Add to the following

that
> a class definition is a class specifier declaration:

<snip>

In my post, I meant that they could be local to testEquality and
testAddition. I think my wording was a bit confusing. At any rate, it
doesn't matter much, because it would also make more sense to have them as
members instead of pointers. IMHO, either way is better. While it is true
that members can cause unnecessary recompilation in certain cases, I doubt
that this will be a big problem for test code. When I want to avoid those
problems, I usually just use the pimpl idiom.

--
David Hilsee


 
Reply With Quote
 
Daniel T.
Guest
Posts: n/a
 
      09-06-2004
In article <(E-Mail Removed)>,
"Steven T. Hatton" <(E-Mail Removed)> wrote:

> I finally got this thing to build. There's something to be said for using
> the release of the cvs image sometimes. :-/
>
> I started reading the docs, and this example struck me as a fundamentally
> bad design for C++. Perhaps it's not bad design in the sense that it will
> fail, or that it can't be maintained. But there seems to be something
> fundamentally un-C++ about this. Does anybody else see what I'm talking
> about here?
>
> class ComplexNumberTest : public CppUnit::TestFixture {
> private:
> Complex *m_10_1, *m_1_1, *m_11_2;
> public:
> void setUp()
> {
> m_10_1 = new Complex( 10, 1 );
> m_1_1 = new Complex( 1, 1 );
> m_11_2 = new Complex( 11, 2 );
> }
>
> void tearDown()
> {
> delete m_10_1;
> delete m_1_1;
> delete m_11_2;
> }
>
> void testEquality()
> {
> CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
> CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
> }
>
> void testAddition()
> {
> CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
> }
> };


Yes, this is poor C++ design. what happens if the second 'new Complex'
throws? Will tearDown be called, and if it is the 'delete m_11_2' call
will probably be undefined because nowhere are the pointers initialized.

Would using members rather than pointers help? Not really, the whole
point of the pattern is so testEquality and testAddition are isolated.

The three variables are really ment to be local to the test functions. I
would rather see something like:

struct Numbers {
Complex m_10_1, m_1_1, m_11_2;
numbers(): m_10_1( 10, 1 ), m_1_1( 1, 1 ), m_11_2( 11, 2 ) { }
};

struct ComplexNumberTest: public CppUnit::TestFixture {
void testEquality() {
Numbers n;
CPPUNIT_ASSERT( n.m_10_1 == n.m_10_1 );
CPPUNIT_ASSERT( !(n.m_10_1 == n.m_11_2 ) );
}

void testAddition() {
CPPUNIT_ASSERT( n.m_10_1 + n.m_1_1 == n.m_11_2 );
}
};
 
Reply With Quote
 
Steven T. Hatton
Guest
Posts: n/a
 
      09-06-2004
David Hilsee wrote:

> In my post, I meant that they could be local to testEquality and
> testAddition. I think my wording was a bit confusing. At any rate, it
> doesn't matter much, because it would also make more sense to have them as
> members instead of pointers. IMHO, either way is better. While it is
> true that members can cause unnecessary recompilation in certain cases, I
> doubt
> that this will be a big problem for test code. When I want to avoid those
> problems, I usually just use the pimpl idiom.


I was certainly speaking in generalities regarding the problems of using
class type members as opposed to using pointers to class types as members.
Here is the case where I believe it is completely impossible to accomplish
what is suggested by the code without introducing at least one pointer (or
perhaps a reference):

class B;
class A{ B b; };

class B{ A a; };

The issue of header files is really irrelevant in this case. I just
happened to encounter it while working on something involving headers, and
found the obvious solution was to use pointers rather than classes as
members. It was kind of two birds with one stone. I removed the compiler
dependencies resulting from the definition of the class type member
variables that applies to any header file, as well as the specific problem
of indirect recursion. They both result from the same need for the
compiler to know how to allocate the memory for the defined class type
member.

What I really found bothersome about the sample I presented was that setUp()
should be a constructor and tearDown() should be a destructor. That is
exactly what these special member functions are intended to do. That is the
core concept behind RAII. A constructor establishes the class invariant or
state of the class, and the destructor reverses that process, freeing any
resources obtained in the construction of the class.

In some Java subcultures constructors are considered evil because they don't
work well in conjunction with serialization. It's more or less the same
kind of restriction you meet when attempting to create a finite sized
container of class type. You either have to feed the template a prototype
constructed object, or you need to have a default constructor which takes
no arguments.

As for the pimpl idiom I haven't revisited that in a while, but I have to
say, from what I recall about it, it supports my conviction that the single
most important change C++ needs is a better way of accessing resources.
One that doesn't require header files, or at least formalizes the notion of
header files as declaration files and not simply #include paperclip and
tape hacks.
--
"[M]y dislike for the preprocessor is well known. Cpp is essential in C
programming, and still important in conventional C++ implementations, but
it is a hack, and so are most of the techniques that rely on it. ...I think
the time has come to be serious about macro-free C++ programming." - B. S.

 
Reply With Quote
 
Steven T. Hatton
Guest
Posts: n/a
 
      09-06-2004
Daniel T. wrote:

> In article <(E-Mail Removed)>,
> "Steven T. Hatton" <(E-Mail Removed)> wrote:
>
>> class ComplexNumberTest : public CppUnit::TestFixture {
>> private:
>> Complex *m_10_1, *m_1_1, *m_11_2;
>> public:
>> void setUp()
>> {
>> m_10_1 = new Complex( 10, 1 );
>> m_1_1 = new Complex( 1, 1 );
>> m_11_2 = new Complex( 11, 2 );
>> }
>>
>> void tearDown()
>> {
>> delete m_10_1;
>> delete m_1_1;
>> delete m_11_2;
>> }
>>
>> void testEquality()
>> {
>> CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
>> CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
>> }
>>
>> void testAddition()
>> {
>> CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
>> }
>> };

>
> Yes, this is poor C++ design. what happens if the second 'new Complex'
> throws? Will tearDown be called, and if it is the 'delete m_11_2' call
> will probably be undefined because nowhere are the pointers initialized.


I didn't even think about the fact the pointers were un-initialize. Good
observation.

> Would using members rather than pointers help? Not really, the whole
> point of the pattern is so testEquality and testAddition are isolated.
>
> The three variables are really ment to be local to the test functions. I
> would rather see something like:
>
> struct Numbers {
> Complex m_10_1, m_1_1, m_11_2;
> numbers(): m_10_1( 10, 1 ), m_1_1( 1, 1 ), m_11_2( 11, 2 ) { }
> };
>
> struct ComplexNumberTest: public CppUnit::TestFixture {
> void testEquality() {
> Numbers n;
> CPPUNIT_ASSERT( n.m_10_1 == n.m_10_1 );
> CPPUNIT_ASSERT( !(n.m_10_1 == n.m_11_2 ) );
> }
>
> void testAddition() {
> CPPUNIT_ASSERT( n.m_10_1 + n.m_1_1 == n.m_11_2 );
> }
> };

Can I assume you intended

void testAddition() {
Numbers n;
CPPUNIT_ASSERT( n.m_10_1 + n.m_1_1 == n.m_11_2 );
}

as the second function?

That probably is a better approach for this case. I do believe there are
instances where it makes sense to actually construct a test environment
that exists for the duration of the TestFixture's lifetime. But I can't
say I have an example of any. I've been trying to devise appropriate tests
for a modestly complicated vector class I wrote with half a dozen operator
overloads, some static member instances, and the obligatory access and
mutate functions.

I believe your approach makes more sense than creating instances to be used
for every test. unless I have a compelling reason to want to test the
side-effect on one test with another, I see no reason to allow a test to
influence the data used for a subsequent test.

One issue I'm unsure of is how detailed I need to be to satisfy 'generally
accepted' expectations of thoroughness. As an example, suppose I have


inline Vector3d& Vector3d:perator+=(const Vector3d& v)
{
m_x += v.m_x;
m_y += v.m_y;
m_z += v.m_z;
return *this;
}

inline Vector3d operator+(const Vector3d& v1, const Vector3d& v2)
{
Vector3d t(v1);
return t += v2;
}

It seems to me, testing the second operator (+) implicitly verifies the
first operator (+=).
--
"[M]y dislike for the preprocessor is well known. Cpp is essential in C
programming, and still important in conventional C++ implementations, but
it is a hack, and so are most of the techniques that rely on it. ...I think
the time has come to be serious about macro-free C++ programming." - B. S.

 
Reply With Quote
 
Daniel T.
Guest
Posts: n/a
 
      09-06-2004
In article <(E-Mail Removed)>,
"Steven T. Hatton" <(E-Mail Removed)> wrote:

> Daniel T. wrote:
>
> > In article <(E-Mail Removed)>,
> > "Steven T. Hatton" <(E-Mail Removed)> wrote:
> >
> >> class ComplexNumberTest : public CppUnit::TestFixture {
> >> private:
> >> Complex *m_10_1, *m_1_1, *m_11_2;
> >> public:
> >> void setUp()
> >> {
> >> m_10_1 = new Complex( 10, 1 );
> >> m_1_1 = new Complex( 1, 1 );
> >> m_11_2 = new Complex( 11, 2 );
> >> }
> >>
> >> void tearDown()
> >> {
> >> delete m_10_1;
> >> delete m_1_1;
> >> delete m_11_2;
> >> }
> >>
> >> void testEquality()
> >> {
> >> CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
> >> CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
> >> }
> >>
> >> void testAddition()
> >> {
> >> CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
> >> }
> >> };

> >
> > Yes, this is poor C++ design. what happens if the second 'new Complex'
> > throws? Will tearDown be called, and if it is the 'delete m_11_2' call
> > will probably be undefined because nowhere are the pointers initialized.

>
> I didn't even think about the fact the pointers were un-initialize. Good
> observation.
>
> > Would using members rather than pointers help? Not really, the whole
> > point of the pattern is so testEquality and testAddition are isolated.
> >
> > The three variables are really ment to be local to the test functions. I
> > would rather see something like:
> >
> > struct Numbers {
> > Complex m_10_1, m_1_1, m_11_2;
> > numbers(): m_10_1( 10, 1 ), m_1_1( 1, 1 ), m_11_2( 11, 2 ) { }
> > };
> >
> > struct ComplexNumberTest: public CppUnit::TestFixture {
> > void testEquality() {
> > Numbers n;
> > CPPUNIT_ASSERT( n.m_10_1 == n.m_10_1 );
> > CPPUNIT_ASSERT( !(n.m_10_1 == n.m_11_2 ) );
> > }
> >
> > void testAddition() {
> > CPPUNIT_ASSERT( n.m_10_1 + n.m_1_1 == n.m_11_2 );
> > }
> > };

> Can I assume you intended
>
> void testAddition() {
> Numbers n;
> CPPUNIT_ASSERT( n.m_10_1 + n.m_1_1 == n.m_11_2 );
> }
>
> as the second function?


Yes, sorry.


> That probably is a better approach for this case. I do believe there are
> instances where it makes sense to actually construct a test environment
> that exists for the duration of the TestFixture's lifetime. But I can't
> say I have an example of any. I've been trying to devise appropriate tests
> for a modestly complicated vector class I wrote with half a dozen operator
> overloads, some static member instances, and the obligatory access and
> mutate functions.
>
> I believe your approach makes more sense than creating instances to be used
> for every test. unless I have a compelling reason to want to test the
> side-effect on one test with another, I see no reason to allow a test to
> influence the data used for a subsequent test.


The last time I created a testing framework, I didn't have setUp and
tearDown methods. Instead, the c_tor was the 'setUp' and the d_tor was
the 'tearDown'. Something like this:

class TestFixture {
public:
virtual void run() = 0;
};

struct ComplexTest: TestFixture {
Complex m_10_1, m_1_1, m_11_2;
ComplexTest(): m_10_1( 10, 1 ), m_1_1( 1, 1 ), m_11_2( 11, 2 ) { }
};

struct ComplexEqualityTest: ComplexTest {
void run() {
ASSERT( m_10_1 == m_10_1 );
}
};

struct ComplexAdditionTest: ComplexTest {
void run() {
ASSERT( m_10_1 + m_1_1 == m_11_2 );
}
};


> One issue I'm unsure of is how detailed I need to be to satisfy 'generally
> accepted' expectations of thoroughness. As an example, suppose I have
>
>
> inline Vector3d& Vector3d:perator+=(const Vector3d& v)
> {
> m_x += v.m_x;
> m_y += v.m_y;
> m_z += v.m_z;
> return *this;
> }
>
> inline Vector3d operator+(const Vector3d& v1, const Vector3d& v2)
> {
> Vector3d t(v1);
> return t += v2;
> }
>
> It seems to me, testing the second operator (+) implicitly verifies the
> first operator (+=).


In "test-first design", the idea is to write tests that will fail but
should succeed, then write code to cause the test to succeed. So, if you
can't conceve of a test that will fail, then you are done; and if you
write a test that doesn't fail on the first try, you need to investigate
what is going on because it should have failed...
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Reusing tests in cppUnit Belebele C++ 3 01-22-2007 09:53 PM
How to use CPPUnit effectively? Hooyoo C++ 59 11-03-2006 09:17 PM
CPPUNIT To Forum C++ 1 01-14-2005 03:40 AM
Xcode and cppunit Scott C++ 2 04-27-2004 10:09 PM
Forking sub-process in CppUnit? Roy Smith C++ 0 07-04-2003 12:58 AM



Advertisments