| Register | FAQ | Calendar | Search | Today's Posts | Mark Forums Read |
|
#1
| |||
| |||
| Hi everyone, I've just finished reading the article on mock objects at http://www.mockobjects.com/files/endotesting.pdf. There was one section I was confused with: ************************************************** ************* Thirdly, developing with Mock Objects tends to push behaviour towards Visitor-like objects [Gamma 1994] that are passed around; we call these Smart Handlers. For example, rather than having code that queries attributes from an object and writes each one to a stream, a first step would be to pass a stream to the object which then writes out its attributes. This preserves the encapsulation of the object. Thus, the code changes from: public void printPersonReport(Person person, PrintWriter writer) { writer.println(person.getName()); writer.println(person.getAge()); writer.println(person.getTelephone()); } to: public void printPersonReport(Person person, PrintWriter writer) { person.printDetails(writer); } public class Person { public void printDetails(PrintWriter writer) { writer.println(myName); writer.println(myAge); writer.println(myTelephone); } .... } As this code becomes more complex however, it becomes difficult to test cleanly because the generic println method used in printDetails loses information about our understanding of the domain. Instead, we can write a handler object to reify this dialogue between a stream and a Person: public void handleDetails(PersonHandler handler) { handler.name(myName); handler.age(myAge); handler.telephone(myTelephone); } This separates the input and output aspects of rendering a Person on a stream. We can test both that we have the inputs that we expect, and that a given set of values is rendered correctly. The unit test for the handler inputs would then be: void testPersonHandling() { myMockHandler.setExpectedName(NAME); myMockHandler.setExpectedAge(AGE); myMockHandler.setExpectedTelephone(TELEPHONE); myPerson.handleDetails(myMockHandler); myMockHandler.verify(); } followed by a separate unit test to check that the domain code for PersonHandler outputs itself correctly: void testPersonHandler() { myMockPrintWriter.setExpectedOutputPattern( ".*" + NAME + ".*" + AGE + ".*" + TELEPHONE + ".*"); myHandler.name(NAME); myHandler.age(AGE); myHandler.telephone(TELEPHONE); myHandler.writeTo(myMockPrintWriter); myMockPrintWriter.verify(); } These three effects mean that code developed with Mock Objects tends to conform to the Law of Demeter [Lieberherr 1989], as an emergent property. The unit tests push us towards writing domain code that refers only to local objects and parameters, without an explicit policy to do so. ************************************************** ************* Now, I understand the first switch. The very first example requires printPersonReport to know about the interface of person.. this is removed in the second example. However, I am unsure of the switch to the handler: "As this code becomes more complex however, it becomes difficult to test cleanly because the generic println method used in printDetails loses information about our understanding of the domain. Instead, we can write a handler object to reify this dialogue between a stream and a Person:" I don't really understand what is being said. Can anyone shed some light on this? Cheers Taras |
|
#2
| |||
| |||
| Taras_96 wrote: > I've just finished reading the article on mock objects at > http://www.mockobjects.com/files/endotesting.pdf. There was one > section I was confused with: > ************************************************** ************* > Thirdly, developing with Mock Objects tends to push behaviour towards > Visitor-like objects > [Gamma 1994] that are passed around; we call these Smart Handlers. The Visitor pattern is used in OO prog langs that are difficient wrt supporting the capability known as "multiple dispatch" . The example does not exhibit the characteristics of multiple dispatch, so the claim is incorrect. If anything, the "Smart Handler" is more akin to the Strategy pattern. > For example, rather > than having code that queries attributes from an object and writes > each one to a stream, a first > step would be to pass a stream to the object which then writes out its > attributes. This > preserves the encapsulation of the object. Thus, the code changes > from: > public void printPersonReport(Person person, PrintWriter writer) { > writer.println(person.getName()); > writer.println(person.getAge()); > writer.println(person.getTelephone()); > } > to: > public void printPersonReport(Person person, PrintWriter writer) { > person.printDetails(writer); > } > public class Person { > public void printDetails(PrintWriter writer) { > writer.println(myName); > writer.println(myAge); > writer.println(myTelephone); > } > ... > } > As this code becomes more complex however, it becomes difficult to > test cleanly because the > generic println method used in printDetails loses information about > our understanding of the > domain. Instead, we can write a handler object to reify this dialogue > between a stream and a > Person: > public void handleDetails(PersonHandler handler) { > handler.name(myName); > handler.age(myAge); > handler.telephone(myTelephone); > } > This separates the input and output aspects of rendering a Person on a > stream. We can test > both that we have the inputs that we expect, and that a given set of > values is rendered > correctly. The unit test for the handler inputs would then be: > void testPersonHandling() { > myMockHandler.setExpectedName(NAME); > myMockHandler.setExpectedAge(AGE); > myMockHandler.setExpectedTelephone(TELEPHONE); > myPerson.handleDetails(myMockHandler); > myMockHandler.verify(); > } > followed by a separate unit test to check that the domain code for > PersonHandler outputs > itself correctly: > void testPersonHandler() { > myMockPrintWriter.setExpectedOutputPattern( > ".*" + NAME + ".*" + AGE + ".*" + TELEPHONE + ".*"); > myHandler.name(NAME); > myHandler.age(AGE); > myHandler.telephone(TELEPHONE); > myHandler.writeTo(myMockPrintWriter); > myMockPrintWriter.verify(); > } > These three effects mean that code developed with Mock Objects tends > to conform to the Law > of Demeter [Lieberherr 1989], as an emergent property. The unit tests > push us towards > writing domain code that refers only to local objects and parameters, > without an explicit policy to do so. The Demeter principles are applied in the example *before* any mention of a test stub. > Now, I understand the first switch. The very first example requires > printPersonReport to know about the interface of person.. this is > removed in the second example. > However, I am unsure of the switch to the handler: > "As this code becomes more complex however, it becomes difficult to > test cleanly because the generic println method used in printDetails > loses information about our understanding of the domain. Instead, we > can write a handler object to reify this dialogue between a stream and > a Person:" > I don't really understand what is being said. Can anyone shed some > light on this? All the claims made for the given example are fallacy. Which is why you are probably having trouble understanding. Regards, Steven Perryman |
|
#3
| |||
| |||
| Responding to Taras_96... This is a Hot Button for me because I see huge amounts of testing effort wasted because the application is poorly formed in an OO sense... > The unit tests push us towards > writing domain code that refers only to local objects and parameters, > without an explicit policy > to do so. Obviously they aren't doing a very good job of pushing. This is an example of what happens when one *doesn't* do DFT. Look at what is happening here. The customized code for the mock objects and their infrastructure is probably going to be integer factors more that the code in the UUT itself! (It may be so repetitive compared to other UUT tests that the tester can write it while drunk, but there shouldn't be any need to write it in the first place.) Any time one has to resort to something this complex and customized to unit test a specific UUT there is something seriously wrong with the problem space abstraction of the UUT itself because it is bleeding cohesion all over the application. Fix that and you won't need mock objects, much less this sort of convoluted supporting infrastructure. <aside> FYI, the commercial translation IDEs usually provide a generic test harness OTB that can be used for unit testing. All the developer provides is specific test case data for the UUT in a text file. That's possible because the translation methodologies have to support full code generators for OOA models. Full code generators are only viable if the OOA model was constructed in a very disciplined OO fashion where methods are self-contained, cohesive, and properly encapsulated while collaborations separate message from method in an asynchronous communication model. Thus there are never any behavioral dependencies between methods so testing benefits because one doesn't need customized mock objects to emulate such dependencies. </aside> -- There is nothing wrong with me that could not be cured by a capful of Drano. H. S. Lahman hsl@pathfindermda.com Pathfinder Solutions http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman "Model-Based Translation: The Next Step in Agile Development". Email info@pathfindermda.com for your copy. Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php. (888)OOA-PATH |
|
#4
| |||
| |||
| > Thirdly, developing with Mock Objects tends to push behaviour towards > Visitor-like objects > [Gamma 1994] that are passed around; we call these Smart Handlers. I suspect that unit testing - especially test-first - forces the dependencies between objects to grow very narrow. Test-free code might possibly indulge in multiple channels between objects, because programmers have no incentive (besides a vague sense of guilt) to shrink these channels down. Expensive setup is a design smell. That means if you need to create too many objects just to test one of them, your objects are too coupled. Test cases should construct the fewest possible objects before testing one of them. So simple laziness in the tests translates into a direct force to decouple your code objects. But now we come to mock objects. I'm aware that a blog with "mockobjects" in its URI should be expected to indulge in promotion. But, in my own code, I only mock to avoid an expensive side-effect in lower-level code. I do not mock to make expensive setup easier, because that would only perfume the code smell. Mocks might possibly make objects easier to couple. So I'm curious (but I don't know enough Java to verify) if these guys could have used TDD to emerge a good design - a design that encourages the vaunted Visitor Pattern - without mock abuse! > "As this code becomes more complex however, it becomes difficult to > test cleanly because the generic println method used in printDetails > loses information about our understanding of the domain. Instead, we > can write a handler object to reify this dialogue between a stream and > a Person:" I think it's saying that the payload of println(x), the x part, will fall out of the system, and that a mock can capture and test it. -- Phlip |
|
#5
| |||
| |||
| phlip wrote: >> Thirdly, developing with Mock Objects tends to push behaviour towards >> Visitor-like objects >> [Gamma 1994] that are passed around; we call these Smart Handlers. otherthread: > Why have you posted something relating to a posting of mine, in a > posting to a different person ... ?? |
|
#6
| |||
| |||
| > > > "As this code becomes more complex however, it becomes difficult to > > test cleanly because the generic println method used in printDetails > > loses information about our understanding of the domain. Instead, we > > can write a handler object to reify this dialogue between a stream and > > a Person:" > > I think it's saying that the payload of println(x), the x part, will fall out of > the system, and that a mock can capture and test it. > > -- > Phlip As in you can now mock the handler object? |
|
#7
| |||
| |||
| On Jul 16, 5:28 pm, "H. S. Lahman" <h...@pathfindermda.com> wrote: > Responding to Taras_96... > > This is a Hot Button for me because I see huge amounts of testing effort > wasted because the application is poorly formed in an OO sense... Not that I'm defending the design, but how would you design the functionality? > > > The unit tests push us towards > > writing domain code that refers only to local objects and parameters, > > without an explicit policy > > to do so. > > Obviously they aren't doing a very good job of pushing. This is an > example of what happens when one *doesn't* do DFT. Look at what is > happening here. The customized code for the mock objects and their > infrastructure is probably going to be integer factors more that the > code in the UUT itself! (It may be so repetitive compared to other UUT > tests that the tester can write it while drunk, but there shouldn't be > any need to write it in the first place.) > Any time one has to resort to something this complex and customized to > unit test a specific UUT there is something seriously wrong with the > problem space abstraction of the UUT itself because it is bleeding > cohesion all over the application. Fix that and you won't need mock > objects, much less this sort of convoluted supporting infrastructure. From what I gathered in the example, the UUT wouldn't be hard to test in the very first instance, just pass in a mock person and a mock writer. So I'm not quite sure the reasonings for redesigning in the first place.... or why exactly the final solution is better than the initial one, maybe someone can shed some light on this. IE: the reason for redesign wasn't to make testing possible/easier (although somehow considering testing the code did motivate the designer to redesign) > > <aside> > FYI, the commercial translation IDEs usually provide a generic test > harness OTB that can be used for unit testing. All the developer > provides is specific test case data for the UUT in a text file. That's > possible because the translation methodologies have to support full code > generators for OOA models. Full code generators are only viable if the > OOA model was constructed in a very disciplined OO fashion where methods > are self-contained, cohesive, and properly encapsulated while > collaborations separate message from method in an asynchronous > communication model. Thus there are never any behavioral dependencies > between methods so testing benefits because one doesn't need customized > mock objects to emulate such dependencies. > </aside> Could you explain the aside a bit further? I didn't really understand it (you have a few years on me in software design )Thanks Taras |
|
#8
| |||
| |||
| Responding to Taras_96... > Not that I'm defending the design, but how would you design the > functionality? So far the UUT, Person, seems rather uninteresting since the example only identified 3 knowledge attributes because they are easy to manipulate in an example. But testing those would not require mock objects. I would need to see what behaviors Person has that would require mock objects for testing before I can speculate on how it should have been designed. However, the example below may be sufficient. >> <aside> >> FYI, the commercial translation IDEs usually provide a generic test >> harness OTB that can be used for unit testing. All the developer >> provides is specific test case data for the UUT in a text file. That's >> possible because the translation methodologies have to support full code >> generators for OOA models. Full code generators are only viable if the >> OOA model was constructed in a very disciplined OO fashion where methods >> are self-contained, cohesive, and properly encapsulated while >> collaborations separate message from method in an asynchronous >> communication model. Thus there are never any behavioral dependencies >> between methods so testing benefits because one doesn't need customized >> mock objects to emulate such dependencies. >> </aside> > > Could you explain the aside a bit further? I didn't really understand > it (you have a few years on me in software design )I think it is probably that I am used to test automation in translation IDEs. Let me describe how that would work first and then draw some parallels to how it would work in an elaboration environment. In a translation IDE there is already infrastructure for full code generation and model level execution. The IDE would make use of that to automate construction of the tests. Consider Person so far: Person { private: Name myName; Age myAge; Telephone myTelephone; public: Name getName(); Age getAge(); Telephone getTelephone(); void setName(Name); void setAge(Age); void setTelephone(Telephone); Person(Name, Age, Telephone); } To unit test this object we need a test infrastructure for each attribute, something like this <simplistic> example: PersonTester::testName(Person myPerson) { for (i=0; i < myNameTestCount; i++) { myPerson.setName (myNameTestNames[i]); name = myPerson.getName(); if (name != myNameTestNames[i]) myNameTestResults[i] = FALSE; else myNameTestResults[i] = TRUE; } } where myNameTestCount and myNameTestNames contain test case data initialized from an external file. IOW, we have test data consisting of N candidate names for a Person with various lengths, embedded punctuation, etc. and we put the results of each test case in myNameTestResults for later analysis. In this case PersonTester is the test harness. You could manually write this for every to test, but that would be kind of tedious. The simplest way the IDE could automate this is to just generate this code automatically since has a code generator handy and it already has a model that describes Person and its responsibilities. In effect, the IDE would read a file that might look <very roughly> like: Object:Person // tells IDE what the UUT is Responsibility:Name // tells the IDE which responsibility to test Test Count:3 // number of test cases for responsibiliyt Irving // test case data Clyde Frumpkin (Hint: note that this looks a lot like XML.) The IDE will then create a PersonTester object with method for each responsibility from the file and boilerplate to execute the individual responsibility tests (invoking testName) and for interpreting the results, which will look pretty much the same for all tests except for names of attributes. It will write that test harness code for all the responsibilities identified in the input file for all objects in the input file. When that has been done, the user tells the harness to build the test application and then execute it. However, some IDEs are more sophisticated than this and they will use the model simulator as a sort of interpreter. That interpreter will essentially eat the same test case data file but it will interpret it in the simulator with some special infrastructure for recording results and whatnot. It will then "walk" each test case in debug mode; in some simulators you can actually step through the test cases one line at a time. (That is more complicated so I won't get into detail about how that happens.) So far there are no mock objects. Suppose we add a behavior: Person::birthday() { myAge = myAge + 1; } To test this we need to initialize Person's age, invoke birthday(), and check Person's age for a variety of possible initial ages. I won't take up space describing it, but TestPerson::testBirthday will be created with roughly the same structure and it will read the same sort of input file for the test case data. The test case data will have to include the expected results so that the appropriate attribute getters can be invoked. So far, so good. This is all fairly straight forward (albeit tedious, which is why we automate) and we have no mock objects. [TestPerson is a pure test harness artifact to stimulate Person and record/analyse results. In practice one can make this much more generic because knowledge and behavior tests all look pretty much the same at this level (and behaviors are self-contained, for the reasons below). The only problem we would have is getting around the 3GL type system if we generated the code. Even that would not be a problem in scripting-based OOPLs where we can process ASCII strings to define types dynamically.] But let's change the method slightly: Person::birthday(x) { if (myAnotherObject.doIt(x)) myAge = myAge + 1; } Now we need a mock object because the specification of myAge after 'x' has been processed depends upon AnotherObject.doIt() doing the right thing. IOW, for any given value of 'x' we must stub AnotherObject.doIt() and provide the right boolean return for the value of 'x' in the test case. Thus our test harness must emulate the behavior of AnotherObject.doIt(). (If we don't stub, then a correct implementation must be linked in and we are doing an in situ functional test rather than a unit test.) The problem here is that Person.birthday() is not properly self-contained in an OO sense and it has a hierarchical implementation dependence on AnotherObject.doIt(). That dependence is what requires the use of mock objects. We would eliminate that dependence in the design in this trivial case by using the original definition of Person.birthday() while connecting the flow of control dots differently. The client who is currently invoking Person.birthday() would invoke AnotherObject.doIt() instead. Then AnotherObject.doIt() would invoke Person.birthday -- provided the result of its operation was TRUE. [There is a rigorous DbC technique for getting the flow of control right, but that's a different story.] Now both responsibilities can be unit tested in complete isolation; we just need to validate that AnotehrObject.doIt() invokes a no-op stub for Person.birthday when and only when its operation is TRUE. For full test automation one also needs to specify what messages the behavior responsibility is expected to generate in the test case data file. The tricky part of that is ensuring it gets to the right object, but the OOA model already has the relationships defined that would be navigated so that can be validated as well. [The translation IDEs use a neat trick for logging messages from behaviors. Since all translation methodologies insist in using object state machines, any outgoing messages will be events. So one just provides a switch on the event queue manager so that it only pops and processes the first event, which is the stimulus event for the state action. Then it just logs any event placed on the queue by the UUT for analysis by the test harness. The event includes the address of the receiving object in memory, so that can be compared to the addresses when objects were instantiated.] So how would one do this if one did not have a model simulator or full code generator handy? You could use a scripting language like perl to construct the test harness code based on the same sort of input file with somewhat more complicated lexical replacement. It would be fairly complex but it would only have to be done once. If one extracts the invariants of unit testing, much of the testing looks very similar. As I indicated above, in practice one can write quite generic code for the test harness, especially in one of the scripting-based OOPLs. I don't want to understate the complexity of building such an automated test harness. There are a lot of nasty little issues to be resolved (e.g., access to private attributes) and one needs to be clever about how the invariants of unit testing are abstracted. But it is a one-time effort because the same harness can be applied to any application once one gets around the type system issues for mapping ASCII identity to run time instances. One just has to provide a different test case data file. -- There is nothing wrong with me that could not be cured by a capful of Drano. H. S. Lahman hsl@pathfindermda.com Pathfinder Solutions http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman "Model-Based Translation: The Next Step in Agile Development". Email info@pathfindermda.com for your copy. Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php. (888)OOA-PATH |
|
#9
| |||
| |||
| Taras_96 wrote: >> I think it's saying that the payload of println(x), the x part, will fall out of >> the system, and that a mock can capture and test it. > As in you can now mock the handler object? There are two kinds of mocks in the world - call them "dynamic" and "static". A dynamic mock, such as Mocha for Ruby, looks like this: object.expects(:method).returns(42) result = object.foo() # calls method and adds 1 assert{ result == 43 } The Mocha library wrapped a real object and replaced only one of its methods. A static mock, in a Java-like language, might look like this: class MockMyClass: inherits MyClass { int method() { return 42; } }; MockMyClass object = new(MockMyClass); int result = object.foo(); // calls method and adds 1 assert_equal 43, result Never mind the static version is several times more verbose and redundant than the dynamic one. I think modern Java also supports dynamic mocks. Now suppose that our target line (our Activate line in the Assemble-Activate-Assert Pattern) cannot take a mocked MyClass directly. Mocha dynamically solves that problem like this: MyClass.any_instance.expects(:method).returns(42) activate(); # calls object.foo() for us That means that if anyone uses any MyClass, its .method() will do nothing but return 42, and the test case will fail if nobody calls .method(). To use a static mock like that, I must insert the MockMyClass into the code at the right juncture for the Activate line to then use it. This implies that an activate() method, in Java, cannot simply call new(MyObject) inside itself. This topic is "Construction Encapsulation". Nobody constructs anything they need; they always receive everything from callers. I suspect the authors you cited discovered that, in their situation, Construction Encapsulation lead to the Visitor Pattern. -- Phlip |
![]() |
| Thread Tools | |
| Display Modes | |
In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.