Re: [Cppunit-devel] A new architecture ?
Brought to you by:
blep
From: Duane M. <dua...@ma...> - 2001-10-22 06:30:30
|
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. One question about your architecture that doesnt seem obvious to me is where is the test fixture? So far I have found the current architecture to be very heavy with test fixture. I have worked around it in ways that I believe conserve time and memory. However, the basic concept of setup() and tearDown() appear sound to me. How would one setup a test fixture with the new architecture. As might be hinted from my statements above, I think a rewrite should be considered. Backwards compatibility should be examined, but if this is taken as a major revision, we might be able to side step that requirement by simply making a version of 1.x available. Truthfully the architecture is not that complicated that converting tests shouldnt that much of a burden although we should get some user feedback on this. 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(). I hate new(). new is my enemy. In my years of experience 90+% of programming errors are caused by new. I avoid new at all costs. Using new should be the final decision not the first. (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. (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! (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. (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. I dont mean for these items to be heavy handed. This is my experience writing and architecting C++ and then looking at CppUnit. These are just my opinions and I certainly am not running the show. I just wanted to express them. I will work toward the goals that are set for the group. I look forward to building a new and better CppUnit! ..Duane --- 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 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. > >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. > >MethodAdaptor: takes a pointer to a "void foo(void)" class method >and turns it into a CppUnit::Test::Case object. > > >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. > >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; >}; > >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. > > >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 have implemented the classes mentioned above along with the >CPPUNIT_TEST_SUITE() macros, and was able to use the macro-based test >suites previously written with no modifications. Since none of the >assertion macros had changed, my custom assertion_traits worked okay. >I did have to change my custom TestResult class, but since it was >customized precisely to put the output in emacs format, I can happily >change to the EmacsOutput class. ;-) > >My initial ambition was to reimplement completely the current Test, >TestCase, TestCaller, TestResult classes using the new classes. I >think that is a lot work and I'm not sure whether the gain is worth >it. It is straightforward to replace the TEST_SUITE macros with >equivalent ones using the new classes. So any tests that use the >macros alone will continue to function. What other techniques are in >use? Derivation of TestCase? Direct use of TestCaller and TestCase >subclass methods? How many are worth supporting? > > >At the moment, none of the code is in CVS. There are two reasons for >this. First, I'd like to see how people feel about pursuing this. >Second, I made a mistake in choosing the namespace CppUnit::Test. >Using that namespace will prevent it from coexisting with the old >CppUnit::Test class. Since I'd like, as much as possible, to >reimplement the old classes in terms of the new I'm going to change >the new classes to a different namespace. Perhaps CppUnit::Setup ? >Suggestions welcomed. |