Unit testing has gotten a lot of focus in the last few years, primarily from the Agile/XP practice of Test-Driven Development. Arguably, TDD is now mainstream practice, not some crazy XP idea that only those mad Agile evangelists espouse on their blogs. In terms of tool support, we have JUnit, TestNG, EasyMock, JMock, and a lot of other tools to help us write and run tests.
While thats all great, there is still the issue of test data. I've seen a lot of unit tests contain a lot of setup code just to create suitable test data.
Consider a reasonably simple JUnit4/EasyMock test for a service, where we want our service to call out to a DAO which returns a particular object:
public class MyServiceUnitTest {Now there's nothing actually wrong with this test, but taking TDD to the letter (fake it till you make it), the implementation of the class would actually be wrong. It would look like:
private MyDao myDao;
private MyService myService;;
@Before
private void setup() {
myDao = EasyMock.createMock(MyDao.class);
myService = new MyService(myDao);
}
@Test
public void canGetDataFromDaoAndReturn() {
Serializable primaryKey = "Don't care";
MyDomainObject myDomainObject = new MyDomainObject();
EasyMock.expect(myDao.load(primaryKey)).andReturn(myDomainObject);
EasyMock.replay(myDao);
MyDomainObject result = myService.findById(primaryKey);
Assert.assertSame(myDomainObject, resut);
EasyMock.verify(myService);
}
}
public class MyService {The only way to force the correct implementation is to triangulate. That is, to perform the same test but pass in a different ID. That sounds like duplicated effort to me!
...
public MyObject findById(Serializable id) {
return myDao.load("Don't Care");
}
}
Whilst the example above is trivial and somewhat contrived, there is one other key point I'd like to make: I had to set up some test data to get the tests to work. There are two pieces of data that are used.
The first is the value of "primary key" that is an instance of
Serializable
. Actually, that's only partly true, it's a String as far as the test is concerned (the value is "Don't Care"). I could have chosen an Integer, or Float, or any type that implements Serializable. That's a fatal flaw in my test because I risk getting a ClassCastException if somewhere, someone assumes that ID's are String's and tries to cast it as such.The second piece of test data is the instance of
MyDomainObject
. Lucky in our case it was easy to create, but what if MyDomainObject
had several arguments on the constructor? Just for fun, let's put on the constructor a javax.xml.transform.Transformer
, org.hibernate.Session
and javax.mail.Session
. And since the domain object is in a JAR that you don't have the source to, you can't just go and change the API :-)....Not quite so easy to create one for the purpose of testing, is it? You could spend quite a bit of time messing around with this, just to set up your test data. Not only is that tedious, but when the next person looks at the test they'll need to interpret all that setup code, which makes the test harder to understand.
@Test
public void canGetDataFromDaoAndReturn() {
Serializable primaryKey = "Don't care";
Transformer transformer = TransformerFactory.newTransformer();
org.hibernate.Session hibernateSession = SessionFactory.openSession(); // uh oh, this needs a hibernate config or it will throw an exception!
javax.mail.Session mailSession = ...// umm, how do I get one of these things without JavaEE?
MyDomainObject myDomainObject = new MyDomainObject(transformer, hibernateSession, umm...);
// continue testing ...
}
Enter Ipsedixit. Ipsedixit is a tool that takes care of thinking up that test data for you. It even knows how to "instantiate" interfaces and classes, and provides them to your test, so in the example above it doesn't matter what
MyDomainObject
needs on the constructor.Using Ipsedixit, lets have a look at the unit test again...
@RunWith(JUnit4IpsedixitTestRunner.class)This has the advantage of the previous test in a number of ways:
public class MyServiceUnitTest {
private MyService myService;
@Mock private MyDao myDao;
@Arbitrary private MyDomainObject myDomainObject;
@Arbitrary private Serializable primaryKey;
@Before
private void setup() {
myService = new MyService(myDao);
}
@Test
public void canGetDataFromDaoAndReturn() {
EasyMock.expect(myDao.load(primaryKey)).andReturn(myDomainObject);
EasyMock.replay(myDao);
MyDomainObject result = myService.findById(primaryKey);
Assert.assertSame(myDomainObject, resut);
EasyMock.verify(myService);
}
}
- I don't have to construct any test data at all. The @Arbitrary annotation will make Ipsedixit provide an instance for you.
- I don't need to set up my mock. Ipsedixit can create one automatically using the @Mock annotation. You may also use Atunit for automocking, if you prefer.
- My test code is reduced, meaning the intent of the test is clearer
int
field will make Ipsedixit populate that field with a random number. String
fields get random Strings. You can even customise how the random value is provided (ie, a string of a particular length, or a number in a certain range).For other types, such as the ones in our example, Ipsedixit will provide a dynamic proxy. So, for the
Serializable
we get a JDK Proxy, and for MyDomainObject
we get a CGLIB proxy. It doesn't actually matter what they are, but the point is that they are objects of the types we require.Ipsedixit does not require any particular testing framework to run, but there are integration points for JUnit 3, JUnit 4, and Spring-Test. It would not be a hard ask to integrate TestNG and other frameworks either.
You might be surprised at how seemingly complicated tests can be simplified by introducing Ipsedixit. Give it a go today!
Great Article
ReplyDeleteJava Online Training India
Java Training in Chennai
LE-MERIDIAN FUNDING SERVICES. We are directly into pure loan and project(s) financing in terms of investment. We provide financing solutions to private/companies seeking access to funds in the capital markets i.e. oil and gas, real estate, renewable energy, Pharmaceuticals, Health Care, transportation, construction, hotels and etc. We can finance up to the amount of $900,000,000.000 (Nine Hundred Million Dollars) in any region of the world as long as our 1.9% ROI can be guaranteed on the projects.
ReplyDeleteLe-Meridian Funding Service -Email info@lemeridianfds.com.
lfdsloans@outlook.com
(WhatsApp...+1-989-3943-740 Or Call +1-913-9518-145)
Thanks for sharing this.,
ReplyDeleteLeanpitch provides crash course in Servant Leadership ,everyone can use it wisely.
scrum master servant leadership
What is servant leadership