I deal with a lot of code that has never had any unit tests for it at all. In fact, it was often written in such a way that creating unit tests for it seems pretty horrific because you would find yourself having to load data into multiple database tables, set up all manner of services and files, etc. just in order to test it properly. So what Iâm going to tell you is the simple, not-too-painful way I chose to make some of this code testable.
To me itâs all about removing all of the external dependencies from the code. If I can pull out all of the sections that are getting remote connections to EJBs, all of the data source lookups and queries, file reads, etc. and abstracting those out then I can test the code fairly easily because all Iâll be testing is the logic it had that surrounded all of those external calls to get data of one sort or another. So thatâs what I do, I remove all of the code that does those lookups and abstract it out.
Note: Some of the feedback Iâve gotten tells me that I should make it clear that what Iâm talking about below is only the refactoring part of getting your code to a unit testable state. After you perform some of the steps you might be well placed to look into either Spring or JSR-299 (for example Weld, which is their reference implementation) to do dependency injection. Or even into mock frameworks to do simpler implementations of your FooHelper for testing purposes. If youâve got one you like a lot, let me know about it. I havenât used any mock libraries for Java yet.
Step 1 – Use the refactoring capabilities in your IDE to extract some methods
Iâm using Eclipse in this example, but Iâm sure you could do much the same thing in Netbeans or any other major IDE.
Eclipse has a function on the context sensitive menu to extract a method. Just select a section of the code that goes and looks up something in a file or gets a JDBC connection and executes a query (or calls some code that does) and click the right mouse button to get the context menu, then select Refactor > Extract Methodâ¦ and fill out the info in the dialog box where it asks you what to call your new method, etc. The refactoring will create an all new function which takes a set of parameters passed in and replace the code in the original spot with a call to that function. Repeat as needed on the offending section of code until you donât have any code within it that isnât actual logic that calls these new methods that use external resources.
Step 2 â Pull the new methods out into an external class
I donât doubt that I could probably get Eclipse to do all of this too but at the moment I perform the next step manually.
Letâs say that the code that I was pulling methods out of was called foo(). Then I would create a new class called ProductionFooHelper at this point. I would pull all of the methods I extracted out of the class foo() was part of and put them into ProductionFooHelper(). That immediately breaks the foo() code because it no longer knows where the functions it used to call are anymore; but it leaves us with a version of foo that doesnât require any exterior resources. That brings us toâ¦
Step 3 â Extract an interface from the ProductionFooHelper class
Here Eclipse will do the tedious work for us again. Go to the ProductionFooHelper class and right click. Select Refactor > Extract Interfaceâ¦ from the context menu that pops up.
You can just select all of the methods to be in the interface and give it a name like FooHelper. Click OK and it will be created. Note that ProductionFooHelper should be marked as implementing the FooHelper interface.
Step 4 â Fix the failing calls within foo()
Go back to the class containing the foo() code and add a new parameter to it. The new parameter would be something like âFooHelper helperâ. Then all the function calls within foo() that are currently showing as errors within the IDE can have âhelper.â put in front of them. That will indicate that weâre calling that function on the passed in class. At this point everything in the code should resolve again except for the spot you originally called foo(). It will need to have an instance of ProductionFooHelper created and passed into it before it compiles successfully.
At this point weâve extracted all the code which had external dependencies into another helper class and we have a production version of that class that should give us the exact same behavior weâve always had. But if we were to produce a MockFooHelper that implemented FooHelper and produced faked results for unit tests, then we could test foo() at our hearts content knowing that we never have to do database setup or anything else unless we want to. The specific implementation of the functions in the FooHelper is up to us for testing purposes.
This may seem a convoluted way to get to testable code and maybe youâve got much better ways to achieve this same thing. If you do, Iâd love to know about it so I can improve my own coding skills. But if not, I have to say that it actually works. Iâve used it recently in a hierarchical fashion where innermost code had a helper interface extracted, then another close to it, and then yet a third. Then I wanted code that called all of them to be unit testable so I extracted a new interface that implemented the other three interfaces and added all new functions as well. The production helper had all of the methods for the innermost stuff as well as the outermost. The result is that I now have four different test points in the code where I can control everything that goes in and out and I was able to whip off a dozen new tests for code that once had none due to its complexity and the difficulty of setting something up.