473,396 Members | 1,804 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,396 software developers and data experts.

Is CppUnit un-C++

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.

Jul 22 '05 #1
9 1961
"Steven T. Hatton" <su******@setidava.kushan.aa> wrote in message
news:34********************@speakeasy.net...
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
Jul 22 '05 #2
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.

Jul 22 '05 #3
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.
Jul 22 '05 #4
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:2003§6.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::ostream& print(std::ostream& out) const = 0;
};

std::ostream& operator<<(std::ostream& 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::ostream& print(std::ostream& out) const
{
out << m_data << ". Invoked on A.\n";
}
protected:
std::string m_data;
};

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

virtual ~B()
{}

virtual std::ostream& print(std::ostream& 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.

Jul 22 '05 #5
"Steven T. Hatton" <su******@setidava.kushan.aa> wrote in message
news:2p********************@speakeasy.net...
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
Jul 22 '05 #6
In article <34********************@speakeasy.net>,
"Steven T. Hatton" <su******@setidava.kushan.aa> 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 );
}
};
Jul 22 '05 #7
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.

Jul 22 '05 #8
Daniel T. wrote:
In article <34********************@speakeasy.net>,
"Steven T. Hatton" <su******@setidava.kushan.aa> 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::operator+=(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.

Jul 22 '05 #9
In article <nu********************@speakeasy.net>,
"Steven T. Hatton" <su******@setidava.kushan.aa> wrote:
Daniel T. wrote:
In article <34********************@speakeasy.net>,
"Steven T. Hatton" <su******@setidava.kushan.aa> 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::operator+=(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...
Jul 22 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
by: Scott | last post by:
I'm trying to run cppunit on my system under Mac OS X 10.3.3 with Xcode, and I'm getting the following error when I try to run the program: ZeroLink: unknown symbol...
1
by: To Forum | last post by:
hi, After searching around with google, I have not reach a final answer for my problem with installation with CPPUNIT. 1/ how can I register the dll file in VC7, please tell me in detail 2/ have...
59
by: Hooyoo | last post by:
How to use CPPUnit effectively? When I want to use CPPUnit, I always fint it useless and waste my time. I think that's mainly because I don't know how to use CPPUnit effectively. So if somebody...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.