I'm finding out first-hand just how difficult it can be to test-drive development of a web-based application. I've read about techniques for working around dependencies on web containers and EJB containers and their services in a few articles and books and much of the advice has to do with mock objects. I've had very limited success with using these approaches. Here's why.
The application I'm working on has a lot of code generated by an MDA (model-driven architecture) tool. The tool generates a lot of fine-grained facade objects which developers currently call directly from Struts action classes. With this approach, it's difficult to write vanilla JUnittest cases since the generated facade directly accesses EJBs. CactusStrutsTestCase does help but using it slows down the TDD cycle considerably.
The tool generated facades are also concrete classes with many different methods and are a bit difficult to mock up because they keep track of the state of the objects that it returns from its methods (yeah, my reaction was "What the h...?!" too). For example:
This is a complete snippet: there are no statements between the calls to setXXX() and facade.store(). It is evident from the above code that the facade keeps a reference to the FooUpdateObject that it returned and to the composite objects as well. This makes it difficult to mock up a facade. Again, the facade and the update objects are concrete classes; it would have been easier to mock them up with EasyMock if they were interfaces, but they are not. Refactoring the facade and update objects is not an option either since they are tool-generated code.
So, my question is, is there some way for me to test-drive this stuff with just plain JUnit classes and POJOs or am I stuck with having to write in-container test cases?
Can you perhaps change the way the MDA tool generates the code (isn't that what MDA is about)?
The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
Originally posted by Ilja Preuss: Uh, this sounds - well, less than optimal...
For a moment there I thought you'd somehow figured out what tool we were using... But you're right, it's far from optimal (ironically).
I'm sure there's a way to change the transformation patterns but we don't have that kind of expertise in-house. We're using the transformation patterns that come out of the box.
My initial efforts went something like this:
1. Created a coarse-grained facade (a POJO) so I could move a lot of processing logic out of the Struts action classes. 2. Created an Interface to abstract away the data input source.
The above refactoring allowed me to create a vanilla JUnit test:
The cast to (IFooCreateInput) is redundant because we made the form implement the IFooCreateInput, I'm just using it to show what the helper.create() method expects.
I suppose I could just bite the bullet and manually create an interface that reflects all the methods in FooFacade that I want to fake out. There's still the UpdateObjects though
One of the comments (complaints?) I hear most often is that having to mock up so many things just seems like a lot of hard work that has very little payoff and that we're having to write way too much test code for a very little amount of production code. This does not help ease the concern that the test-first approach will take away from the time we have to develop the production code. Despite my belief that the up-front effort to write test code will pay for itself down the road, I can't help feeling the same way sometimes. There has to be an easier way.
(BTW, I have found reflective articulation to be very helpful at times and part of the reason I'm writing all this is that I hope that by just doing so, it might help me to find an alternative solution. The main reason, of course, being that I hope to get some good advice from Ilja, Lasse and the other regulars. TIA )
Junilu, How much of that code snippet is generated? We use the approach that if something is completely generated, it isn't necessary to write unit tests. After all, if you can't change the code, you can't break it. (There are some generated tests, but that's another story.)
Does the generator let you put your code in methods? If so, you can put your custom code in one testable method and let the generator do its thing.
The Facade and UpdateObjects are the generated classes. The tool does allow custom code to be added to the generated classes but it was decided by the team, due to some bad experiences and perceived flakiness of the tool, to just stick to generated code exclusively.
We're not writing tests for the generated code either, which is why we're trying to mock up the classes that are generated.
Junilu, Yuck! One way to get around it is to write/generate a testable wrapper. Our code generator creates only static methods. One person wrote a wrapper that wraps each of those static methods with an instance method (and an interface) to make it testable.