I need to write a JUnit test for the following method (simplified):
/** Return name of previous entry or name of given entry if no previous entry. */
public String getPreviousName(TreeEntry entry) {
if (entry.getPrevious() != null) {
return entry.getPrevious().getName();
} else {
return entry.getName();
}
}
I just don't know how I should test this. I could either
a) Use Mocks to mock out the parameter and its previous entry or
b) Create a real TreeEntry and it's real previous entry
The problem with using mocks is, that I need to say in the test case which method the actual implementation needs to use, e.g. here I say the correct name is obtained via getPrevious() of TreeEntry and then via getName() of that previous entry (not considering the if null case, because that's basically not the problem):
TreeEntry mockEntry = mock(TreeEntry.class);
TreeEntry mockPreviousEntry = mock(TreeEntry.class);
when(mockEntry.getPrevious()).thenReturn(mockPreviousEntry);
when(mockPreviousEntry.getName()).thenReturn("testName");
assertEquals("testName", classUnderTest.getPreviousName(mockEntry));
But an implementer could for example also implement it like that:
return NameService.getName(entry.getPrevious());
It just doesn't matter for the test.
However, if I use classical unit testing without mocks I have another problem. The constructor of TreeEntry is 开发者_如何学Cvery complex. It has 5 parameters that need valid objects. So it's very complex to create something like:
TreeEntry entry = new TreeEntry(...);
TreeEntry previousEntry = new TreeEntry(...);
entry.setPrevious(previousEntry);
previousEntry.setName("testName");
assertEquals("testName", classUnderTest.getPreviousName(entry));
The code would be much longer than this, because the constructors take complex parameters. Another thing is that the constructor accesses the file system to load it's contents from there, so it adds to making the unit tests slower.
Using the classical approach it also ties the test a lot more to the implementation of TreeEntry. Because if something is changed in TreeEntry with the constructors or the setting of the previous entry it will also need to be changed in the test. And if it's forgotten it will cause the test to fail even tough the class under test is not TreeEntry.
What would you say is the correct way to unit test this?
I personally tend to like the mocking approach more. Is it maybe okay to say that in a test case I already specify which are the collaborators of the class under test because that in some way also belongs more to the design rather than the implementation?
Best regards, Alex
I think your mocking approach looks just fine (and I agree that if TreeEntry
is as complex as you say, you shouldn't mix them into these tests (but I hope you do test TreeEntry
elsewhere)). If you are writing a lot of tests like these, you should consider employing some Builder pattern (with a fluent syntax if you have the patience to make it), so that you can construct the mock e.g. like this:
BuildANew.TreeEntry().WithPreviousEntryName("testName");
or you could create an ITreeEntry
interface and write your tree-manipulating code in terms of that interface, and create a simple stub implementation to pass into your tests, so that you won't need to mess around with the mock setup code. Or, for an even cleaner approach, create the interface and mock it.
I think your best option is to extract an interface from TreeEntry, implement it with s basic stub in your test code, and test against that.
If you only want to test the
public String getPreviousName(TreeEntry entry)
method I would mock it since you are only unittesting this method. And it is good practice to keep your unittests fast.
But if you are interested in testing the whole file acces and creation of your TreeEntry I would create them.
On my daily work i try to write the unittests first and design the function afterwards, because it helps to design easy to use/test interfaces.
http://en.wikipedia.org/wiki/Test-driven_development
I think the mock is OK.
On the other hand: JUnit test is not just a simple test, it also drives design.
If you find difficult to write test case, for instance constructor takes complex parameters, you may re-think your design.
btw, I don’t think method getPreviousName()
is necessary, getPreviousEntry()
is enough.
精彩评论