RE: [Cppunit-devel] Mock C++
Brought to you by:
blep
From: Philippe F. <P.FREMY@OBERTHURCS.com> - 2002-08-26 11:46:59
|
> > If I understood right, your framework does not look very generic to me, and > > requires many manual calls. I think we can do better. > It is fairly generic: you don't have much restriction in writing your expectation > and you can easily add new one. What you probably mean is that > it does not automatize the creation of such class. Yes, I find it not generic in the sense that you have to write everything yourself. I would like mock to be very easy to use. CppUnit takes something like half an hour to set-up. And it is complicated. Mock is going to be even more difficult. > > > To record the called made to a function, we could add a simple macro just > > after every function calls. Compilers usually have a __FUNCTION__ that > > contains the name of the function, so this could be automatic. It would > gcc does, but I don't remember VC6 providing that. Just checked, it does not. __LINE__ + __FILE__ will do. > > > record the function being called. Using a dictionnary behind that, you would > > be able to store and retrieve specific information about each function call. > I could be done, but I have an hard time seing how you would > fill that map: you are not in the function There would be a record mode and a check mode. In the record mode, all calls are recorded for future reference. In check mode, an exception is raised if the function is not called properly and in the right order. In both case, I am in the function. This is how easymock works I think. TestTree::testVisitTree() { Tree tree; tree.load( "Foo1Foo2Bar1" ); // prepare a tree with two Foo1, Foo2 and Bar1 mock = new MockVisitor(); mock.setRecordMode(); mock.visitFoo( tree.foo1 ); // is recorded for future comparison mock.visitFoo( tree.foo2 ); // is recorded for future comparison mock.visitFoo( tree.bar1 ); mock.setCheckMode(); try { tree.visitTree( mock ); } catch (MockError me ) { // display mock error } } class MockVisitor : Visitor, CppMock { // [...] void visitFoo( Foo * foo ) { NOTIFY_FUNCTION_CALLED; NOTIFY_ARG( foo->toString() ); END_NOTIFY; } void visitBar( Bar * bar ) { NOTIFY_FUNCTION_CALLED; NOTIFY_ARG( bar->toString() ); END_NOTIFY; } }; #define NOTIFY_FUNCTION_CALLED notifyFunctionCalled( __FILE__, __LINE__ ) #define NOTIFY_ARG( foo ) notifyArg( foo ) #define END_NOTIFY() endNotify() class FunctionCall { public: std::string file; long line; std::vector< std::string > args; } class Mock { public: // [...] enum MockMode { RecordMode, CheckMode }; void notifyFunctionCalled( std::string file, long line ) { if (_mode == RecordMode) { _currentCall = new FunctionCall; _currentCall->line = line; _currentCall->file = file; } else { // retrieve FunctionCall object _expectedCall = getCurrentExpectedCall(); // check that this function was expected to be called if ( _currentCall->file != _expectedCall->file || _currentCall->line != _currentCall->line ) { throw MockException( "Unexpected method call" ); } } } void notifyArg( std::string arg ) { if (_mode == RecordMode) { _currentCall->args.add( arg ); // I don't know the syntax but you get the idea } else { if ( _currentCall->getCurrentArg() != arg ) { throw MockException( "Unexpected arg" ); } } } void endNotify() { if (_mode == RecordMode) { // store _currentCall _functionCalls->add( _currentCall ); // I don't know the syntax but you get the idea } else { // increment index so that next call to getCurrentExpectedCall() returns the next FunctionCall } } protected: MockMode _mode; FunctionCall * _currentCall; FunctionCall * _expectedCall; std::map< std::string, int > _functionCallIndex; std::vector< FunctionCall > _functionCalls; }; Of course, the syntax is probably not correct and there are more checks to add. But you get the idea. The advantages with this framework is that it is very easy to set-up, in comparison with yours. One don't need to add one store per function. One could add more methods to tell that a function can be called any number of times, and stuff like that. The drawbacks: - does not handle return value - compare objects with their string representation. I agree it is weak, but I tend to prefer simple implementable stuff than very good but very difficult to implement solutions. This is in the spirit of XP. :-) If the developer is aware that string representation is going to be used, he can provide a sensible one. > Also notes that in the provided example, I was tracking the > call sequence for all methods. Sometime you are only > interested on some specific method call count. You implement this by using a specific > expectation call count for each methods. This would be easy to provide. > > Also, notes by doing that you're being a lot less generic: you force the > user to write mock object in a specific way. Yes, I force the user into a simple mock framework. The problem I have with your framework is that the developer must set-up everything by himself. I already have trouble converting my colleagues to test units. If it is too difficult to use a mock object, they simply won't. Of course, if you do everything yourself, you have more control. > > > If we assume all objects can be compared using their string representation, > > we can store the expected and actual arguments of every function. > > Urk. Never do that. String representation should only be used for diagnostic > purpose, never for 'business' stuff. I actually do that by providing functor > (with default) for equality and string conversion. Equality is a very > volatile things when writing unit test. Sometime you need strict identity > (an object can only be equal to itself), sometime you only need a similarity > comparison (the objects have the same caracteristic). I agree but storing an object of any type for further comparison can't be done in an automated way, as far as I can tell. I want to avoid the user declaring each store for each argument function. string looks to me like a nice way to work this around. I wanted to implement that for my next project but it turns out that I won't need it. So I'll implement it some other day, when I'll need it. By the time, anyone is free and encouraged to pick the idea and code it. Philippe |