How to refactor a method to make it easier to test
Below is a method that I'm having a hard time figuring out how to test using JUnit.
This method is difficult to test because it depends on the results of other methods (e.g. getClosestDcoumentCode).Based on my reading of JUnit, this suggests I should refactor the method. But how? And if refactoring is not necessary, how do you test a method that depends on other methods?
Thank you,
Elliott
private static String findPrincipal(List<DocumentKey> documentkeys_) {
Hashtable<String, Integer> codecounts = new Hashtable<String, Integer>();
for (DocumentKey document : documentkeys_) {
int x = 0;
String closestCode = getClosestDocumentCode(document.candidates);开发者_Go百科
if (closestCode == null) continue;
int thecount = 0;
if (codecounts.containsKey(closestCode))
thecount = codecounts.get(closestCode);
if (document.hasKey)
thecount += 2;
else
thecount++;
codecounts.put(closestCode, new Integer(thecount));
x++;
}
String closestCode = getClosestCode(codecounts);
return closestCode;
}
Well, first of all, I wonder if the method really needs to be static, and what that class is doing. It looks like it might be a GOD class, or at the very least it's violating the single responsibility principle. What does getClosestCode do? If it was a class, you could inject it with a stub in your tests into the test class.
EasyMock will let you mock the method response, but I'm not sure how you mock static methods.
In general, you probably need to
- Extract long functions into classes
- Make functionality non-static
- Maintain the single responsibility principal
It sounds to me like getClosestCode
and getClosestDocumentCode
belong to a different set of responsibilities than the findPrincipal
method. So you'll want to begin by separating these into two different classes. Create an interface for each class to implement. The class that implements the findPrincipal
method can then rely on the other interface as a constructor argument, like this:
public class PrincipalFinderImpl implements PrincipalFinder
{
private CodeFinder codeFinder;
public PrincipalFinderImpl(CodeFinder codeFinder) {
this.codeFinder = codeFinder;
}
public String findPrincipal(List<DocumentKey> documentkeys_) {
Hashtable<String, Integer> codecounts = new Hashtable<String, Integer>();
for (DocumentKey document : documentkeys_) {
int x = 0;
String closestCode = codeFinder.getClosestDocumentCode(document.candidates);
if (closestCode == null) continue;
int thecount = 0;
if (codecounts.containsKey(closestCode))
thecount = codecounts.get(closestCode);
if (document.hasKey)
thecount += 2;
else
thecount++;
codecounts.put(closestCode, new Integer(thecount));
x++;
}
String closestCode = codeFinder.getClosestCode(codecounts);
return closestCode;
}
}
Now it should be easy to create another class the implements the CodeFinder
interface, either manually or using a Mocking framework. You can then control the results of each call to getClosestCode
and getClosestDocumentCode
, and ensure that each of these methods gets called with exactly the arguments you expect it to be called with.
I don't read the method deeply. But if a private method needs to test, it indicates something wrong with your design. At least Kent Beck thinks so.
There is a chapter on stub calls on JUnit Second Edition, i recommend you have a look at that if you think your existing codes are not written to test-driven development standards.
精彩评论