- Last edited November 12, 2000 |
I have a third party class library (Position, Request) that accesses a remote server. I have class Client that uses Position and Request. I want to develop a unit-test for Client. Naturally, I thought "Mock Objects". So, I want to develop MockPosition? and MockRequest?.
Client looks like this:
public Client { public Client(); public String doThing(String arg) { Request req = new Request("server"); Result res = req.send();I see two possible appraoches:
return res.getAnswer(); }
1) Build my own interface (or abstract base class) that "looks like Request". Derive MockRequest? and RequestProxy?. Use RequestFactory? to construct the correct object.
2) create MockRequest?, extending Request. Change the new to use RequestFactory? which creates the right type of Request object (real or imagined).
Now for the hard part. I turns out the the constructor for Response takes another object from the ClassLibrary?, and that in turn is very hard to create and so and on. Now, on path 2, I don't think I have a choice. I have to construct a Response the hard way. On path 1, I could have AbstractRequest? return an AbstractResponse? with two different implementations, MockResponse? and ResponseProxy?. I'm not fond of this because it involves a fair amount of work to build the testing framework, AND I'm making significantly more changes to the code ONLY for the purposes of testing.
Questions: A) Can anyone suggest another alternative approach? B) Am I wrong to be concerned about path 2? C) Given the two choices, which would you do? D) This class library is conceptually a device driver, and we expect to have additional such devices that are designed to provide similar information in an entirely different API. Naturally, when we get there, I'll have a small class heirarchy that abstract the device driver. But for now YAGNI and DTSTTCPW discourage me from going that route, and I'm not sure it eliminates the testing problems even when I do. Am I correct in this approach?
If you really can't get to the Request and Result classes, and the Client does something significant, how about inserting a mediating object? Say,
Client { public String doThing(String arg, Dialogue dialogue) { ... return dialogue.sendAndReceive(someArgumentOrOther); }Where Dialogue is an interface that wraps up your interactions with the Request and Response. You can test the real implementation of a Dialogue functionally, and use a mock implementation, with hardcoded responses, to test your client. It's weird, but we've found that, as we move towards passing more behaviour around, our code gets cleaner and more flexible.
One of the tenets of XP is that you should change your code to make it testable, because without testing we can't rely on refactoring or shared ownership. Furthermore, if you're writing your code test-first, then you can only write testable code. The question then is the cost of building your test harness, and how ruthlessly you can mock up real classes.
In the example above, if you had a factory for Request, your mock implementations would only need to implement two methods: Request.send() and Response.getAnswer(). Can you develop a different constructor for a subclass of Response that doesn't trigger anything else?
DavidCorbin?
If you really can't get to the Request and Result classes, and the Client does something significant, how about inserting a mediating object?
I can't. I could re-implement what it does, because ultimately it is making an HTTP request and parsing the response, but I'd be parsing based on empirical evidence, and who knows what would happen when "v2" of the library comes out....
As for the mediating object, that's what I considered my "path #2" was with ResponseProxy?, so it can be done, I'm just trying to find a better solution.
I just "worry" that I will have a whole in my testing by not testing the way I use the third party library.
Can you develop a different constructor for a subclass of Response that doesn't trigger anything else?
Believe me, I've tried. There is only the one constructor, and in my oppinion it's a bad one. The object takes what is conceputally a hash-tree, but instead of using java.util. they cobbled together a really wierd set of classes to deal with it (I only know this much by decompiling the .class files).
There's a couple of things to test here: that your client will send the right requests and can handle the right responses, and that the library conforms to that protocol. You have to know what is supposed to happen when code you client, so you might as well write that knowledge into functional tests for the library. Then you know what's broken when version 2 comes out. OK now you've got some tests on the library.
Now subclass the Response and Request objects to produce Mock versions and override everything''. All the methods except the ones you use throw a ''NotImplemented? runtime exception. Implement the methods you need very simply so that they just return canned values, hardcoded if you like. For a bonus point, you could check input parameters against preset expected values.
Now use these Mocks to exercise the code in the client. You could pass a factory object to the client, so you can substitute a test one which generates Mocks. Again, these Mocks have no behaviour at all, except to return the responses that you preset, and the data you use to seed them might well be the same as for the library functional tests. Refactor!
You might also consider wrapping up the construction of the weird input parameter and unit testing that separately. One more thing to rely on when you have problems.
Finally, rather than fully implementing a layer at a time, do a simplistic slice through the whole system and grow it one interaction at a time.
How does this sound?
---
DavidCorbin?
I like (very much) the idea of "implement everything", and "throw NotImplemented?".
Glad to help
Rather than pass a factory, what I've done is for THE Factory object to key off of a static variable. The test code sets the static variable to control what type of Request gets made. Works well.
If it works for you, that's fine, but please consider passing it through. That way, you don't need to include test stuff in your production code when you ship -- and I've gone right off singletons.
If I implement an interface and mediating/proxy object, I'm not sure I have to fight with the wierd input parameter at all (which of course, is a plus).
Aha!
As for your last paragraph, I'm a tad confused by it. What do you consider a layer vs. a slice? And for that matter, an interaction (in this case)?
I meant, don't implement all the functional tests (layer), then all the mock stuff, then all the client stuff, etc. Rather, pick a single request/response combination (slice) that you know about and implement all the pieces for that as simply as you can. Repeat for all the requests/responses you're interested in and refactor.
- Last edited November 12, 2000 |