Re: [Cppunit-devel] A new architecture ?
Brought to you by:
blep
From: King D. <Ki...@tc...> - 2001-10-22 19:35:52
|
> Message: 3 > From: Duane Murphy <dua...@ma...> > To: CppUnit Developers <cpp...@li...> > Subject: Re: [Cppunit-devel] A new architecture ? > Date: Sun, 21 Oct 2001 23:30:32 -0700 > > I'll try and chime in with some thoughts of my own about what you have > written and my experiences with CppUnit. > > I agree with all of the points you make in your discussion > concerning the > architecture of CppUnit. I spent a number of years writing > Java and was a > bit disappointed to discover that CppUnit appeared to be, for > lack of a > better term, a port of JUnit. I dont know whether that's good > or bad. I > like C++ a lot and I feel that there are many things that can be done > better in C++. My opinion. Well CppUnit is in fact a port of JUnit. JUnit is a wonderful testing framework, but what works in Java does not necessarily work in C++ and vice versa. > I would like to hope that we can address a few items that make me > uncomfortable using CppUnit. I would be happy to work towards solving > some of these problems. > > (1) Too many pointers. I have found many places in the code > where objects > are constructed for no good reason. It almost struck me that > the author > somehow believed that the only way to get an object was to > use new(). In Java, of course, that is true. Since CppUnit is a port of Junit you will see lots of news. > (2) Const correctness. Most of the changes and additions I > have made to > my working copy of CppUnit fix const correctness. Just as I dont like > new() I dont like non-const objects. If an object is > non-const that means > I might change it without meaning to. If I want to change it > then I will > use a non-const object. There are several methods that are > clearly const > that are not labeled as such. Again a port of Java which does not have const. > (3) Use of references. While pointers are evil, references are your > friend. Const references are your best friend. They take up minimum > space, they have maximum reliability (they cant be NULL), you > cant modify > them without meaning to, and they are polymorphic. Excellent! Java has no references. > (4) Counted pointers. Sometimes you have to use pointers. If I have to > use pointers, I almost always use a counted pointer. I have a > class that > I use for my own work but that was because I wrote it before Boost. I > highly recommend using the boost class libraries > <http://www.boost.org> > especially the counted pointer library. Many of the other libraries > should also be examined for possible use. Why write it, when > you can just > use it. In general, I do not favor counted pointers, but in this case things are usually arranged in a tree, so loops are not an issue. > (5) C++ is not Java. There are many java'isms in the code. I probably > cant name them all two that I most recall are using new() > (see above) and > returning containers. If a class is modeling a container then > it should > model the container, not simply return its contents. Once again the Java'isms are expected. Of course that doesn't mean they should remain. > --- At Mon, 22 Oct 2001 00:02:55 -0400, Steve M. Robbins wrote: > > >Hello, > > > >So I spent some time yesterday thinking about the architecture of > >CppUnit, basically the same issues that I raised previously in two > >messages: > > > > http://www.geocrawler.com/archives/3/6780/2001/7/0/6126116/ > > http://www.geocrawler.com/archives/3/6780/2001/7/0/6126118/ > > > >In those messages, I tried to outline my understanding of the various > >classes in the current CppUnit. In the end, I concluded > that a lot of > >my difficulty in understanding CppUnit came from a couple of rather > >odd bits of system architecture. As one example, there is the class > >TestCase that is used in two different ways: it can be used to > >implement a single test case, or it can be used as a "fixture" to > >implement several test cases. In addition, there is the fact that a > >test case can be programmed to check for an "expected" exception if > >the case is constructed with TestCaller, but not if it is a direct > >subclass of TestCase. And there is the truly baroque > TestResult class > >that is very inflexible and heavy. I agree. > >I spent some time considering what a clean design would look > like, and > >then some more time actually coding it. There are two large > groups of > >classes needed for unit testing: classes to set up the tests, and > >classes to run them and capture the output. I thought about putting > >these two classes into separate namespaces, which I will tentatively > >call CppUnit::Test and CppUnit::Result. > > > > > > Test Construction Classes (CppUnit::Test) > > ----------------------------------------- > > > >I'll leave aside the assertions and the exception classes for > >the moment. Whic really need a rework as well. I really prefer JUnit where all the assert methods are defined as superclasses of the base Test class. The problem as always is how to get the file and line number info to those methods, so you end up creating nasty macros and to get lots of flexibility with them requires creating many macros with different names since you can't overload macros. > >The current design includes single test cases and suites of test > >cases, both of which implement a common interface. As Baptiste > >pointed out these are, respectively, the leaf, composite, and > >component classes of a "Composite Pattern". I propose to have three > >classes that serve the same purposes, all in CppUnit::Test > >namespace: > > > >Base - a pure virtual class that defines the interface > (i.e. the Component) > >Case - the leaf class that implements a *single* test case; > subclass of Base > >Suite - the composite component (also a subclass of Base), > consisting > > of a set of tests where a test could be any subclass of Base > > > >To these basic three -- each of which are fairly simple and > clean -- \ > >I also added the following. > > > >CaseWithException: a single test that requires that a specified > >exception is thrown. The class is templated on the exception > >type. > > > >FunctionAdaptor: takes a pointer to a "void foo(void)" function > >and turns it into a CppUnit::Test::Case object. I see no need to cater to someone not using OO. > >MethodAdaptor: takes a pointer to a "void foo(void)" class method > >and turns it into a CppUnit::Test::Case object. Which is the TestCaller class now. > >These three are all subclasses of CppUnit::Test::Case, so very little > >extra code is required. > > > > > >Using the MethodAdaptor and CaseWithException, I can easily implement > >the CPPUNIT_TEST_SUITE(), CPPUNIT_TEST(), CPPUNIT_TEST_EXCEPTION(), > >and CPPUNIT_TEST_SUITE_END() macros. The beauty of this scheme is > >that *any* class can contain a test suite. In the current scheme, I > >think that you can only use these macros in a subclass of > >CppUnit::TestCase. And with the FunctionAdaptor you can make test > >suites out of arbitrary global functions. One of the weaknesses of the macros right now is the difficulty in applying a TestDecorator to a test. I'd like to see that addressed. What makes things ugly is the use of macros everywhere. Unfortunately that seems hard to avoid because of the need to get file and line number passed around to tell where the error occurred. It would sure be nice to be able to generate a stack trace like in Java. > >So the test-definition side seems sufficiently flexible, at least > >to me :-). Have I missed something out? > > Test Result Classes (CppUnit::Result) > > ------------------------------------- > > > >To keep the code clean and flexible, I opted here to use the > composite > >pattern again. I used CppUnit::Result::Base for the interface class, > > > >class Base > >{ > >public: > > virtual ~Base() {}; > > > > //! Callback to indicate a test is about to start. > > virtual void startTest( CppUnit::Test::Base* test ) = 0; > > > > //! Callback to indicate that a failure has occurred. > > virtual void addFailure( CppUnit::Failure* failure ) = 0; > > > > //! Callback to indicate a test has just ended. > > virtual void endTest( CppUnit::Test::Base* test ) = 0; > >}; Some reason you want to drop the addError method? > >and CppUnit::Result::Group for the composite class. The latter is > >basically identical to the test suite class in that it contains > >a vector of pointers to result classes, and the callbacks are > >passed along to each result object in the group. > > > >There are a number of possible leaf classes. The simplest ones > >will just print the test progress to the screen. For example, > >here is one in the same vein as the CompilerOutputter classes > >recently added. This one is for emacs-compatible output. > > > > > >class EmacsOutput : public CppUnit::Result::Base > >{ > >public: > > void startTest( CppUnit::Test::Base* test ) {} > > > > void addFailure( CppUnit::Failure* failure ) > > { > > if ( failure->getSourceLine().isValid() ) { > > std::cerr << failure->getSourceLine().fileName() > > << ":" > > << failure->getSourceLine().lineNumber() > > << ":"; > > } else { > > std::cerr << "Error at unknown location:"; > > } > > > > std::cerr << failure->getMessage() > > << std::endl; > > }; > > > > void endTest( CppUnit::Test::Base* test ) > > {} > >}; > > > >It cannot get any simpler than this! Output for other IDEs will be > >similarly trivial. If you have a GUI running, then you can implement > >a leaf leaf class that updates the progress meter. Agreed on simpler architecture, except I don't see the need for the Group composite class. Basically the TestRunner is the composite class that has 0 or more TestListeners registered to listen for events. > >If you want to save the failures and generate a report at the > >end, then you just implement another leaf class that stores > >a copy of the failures received. > > > > > > What's Next > > ----------- > > > >I believe that the current functionality is available using > the simple > >classes outlined above. Those, plus the current assertion functions, > >exception class, and test registry classes. I haven't looked too > >carefully at the latter, however; perhaps there is room to simplify > >those also. > > > >There are two questions to consider. First, is it worth > reworking the > >foundations of CppUnit? Certainly, the gain in clarity is persuasive > >to me; how about the rest of you? Second, if we decide to go ahead > >with this, how much compability must be maintained? I agree that it should be done. Compatibility is not as big a concern as doing it right. -- Dale King |