As announced in my previous article I am starting a new project to provide an easy API to fill in POJOs with dummy data.
The goal of this article is to collect a first draft of requirements for PODAM. Ok, so what's needed by a tool to fill POJOs with dummy data?
- The tool should provide a black box through a simple function: given as argument a POJO class, the function MUST return an instance of the POJO class with all its instance variables filled with data, unless otherwise stated by a custom annotation (see next point).
- The possibility, through annotations, to customise the behaviour of such tool. For instance one might want to skip the auto-generation of data for some POJO attributes; or one might want to pilot the values assigned to a POJO: for instance on a numeric attribute the value assigned by the tool should be between 5 and 100. Other customisation could go in the direction of fine grained value assignments to properties (boolean, Date, etc)
- The possibility to be integrated with tools such as JUnit, although PODAM should not extend JUnit but rather JUnit could make use of PODAM through composition. In this scenario, instead of invoking a Factory function which returns an instance of a POJO, one could declared a @Podam annotation on a (instance) local variable in a JUnit (or other testing framework) test and the factory method would then be invoked.
- For PODAM to work the POJO being set with dummy data must have a non-argument constructor
Let's try to capture the above requirements in steps. Let's start with the simplest scenario, e.g. I want a POJO with all its attributes (including its graph objects) set with some dummy values. Given the following Domain Model:
A Client has got an Address and one or more BankAccounts. If I am writing a JUnit test for a service which uses the Client POJO, I'd like PODAM to offer something like this:
public class ClientServiceUnitTest {
@JUnit
public void testClientService() throws Exception {
ClientService service = EasyMock.createMock(ClientService.class);
Client clientDto = PodamFactory.createDummy(Client.class);
service.persistClient(clientDto);
EasyMock.expectLastCall();
//Etc. Replay and Mock validation
}
}
PodamFactory should return a Client POJO whose primitive types attributes are filled with values but also whose graphed objects are filled with data. So the client will have an address object filled with dummy data and a collection of one or more bank accounts.
Now let's look at some more interesting scenarios. Let's say that I wanted a Client POJO filled with data but I wanted the address attribute to be skipped. I could write something like the following:
//Imports omitted
public class Client implements Serializable {
private String firstName;
private String lastName;
@Podam(skip=true) private Address address;
private List<BankAccount> bankAccounts;
//Constructors, getters/setters, equals/hashcode and toString methods omitted for brevity
}
I could use a @Podam annotation with boolean attribute skip = true to indicate that a certain attribute should not be filled by PODAM. Of course for graphed objects to be filled with dummy data, these should be PODAM-compliant (e.g. have a no-arguments constructor).
Another scenario could be that I wanted to specify exactly how many bank accounts I wanted. using the same class as before I could have:
//Imports omitted
public class Client implements Serializable {
private String firstName;
private String lastName;
private Address address;
@Podam(nbrElements=5)
private List<BankAccount> bankAccounts;
//Constructors, getters/setters, equals/hashcode and toString methods omitted for brevity
}
By using the nbrElements attribute I could instruct PODAM on how many elements in the list I'd like to be filled with dummy data. So it seems that a @Podam annotation is emerging from the initial requirements. The question here is whether such annotation (which belongs mainly to the testing world) would be acceptable in a production POJO code. My view is that annotations in general are innocuous and that the benefits of having fine grained control over auto-generated dummy data during testing outweighs the cons of having a "test related" import in our code.
Let's see if I can define some attributes of a @Podam annotation.
- skip. (Boolean). It instructs the tool to skip the auto-generation of dummy data for the annotated attribute. The default is false.
- value. (String). It asks the tool to assign exactly the value specified in the attribute. The default is a blank String.
- minValue. (Numeric). It requires the tool to assign a numeric value to the annotated attribute which should not be less than the value defined in the annotation.
- maxValue. (Numeric). Similar to minValue except that the value assigned to the annotated attribute should not be greater than the value indicated by the annotation. The default is the maximum value for the type of the annotated attribute.
- nbrElements. In case of a Collection, the number of elements the tool should create with dummy values. The default is 1.
- length. (Numeric). The length of the dummy string value assigned to the annotated attribute. The default can be an arbitrary number, such as 5, 10, 20, etc.
So far these are the attributes I could think of. What do you think folks?
Happy technology.
Not sure of it would be a complete fit for your requirements, but have you considered using http://code.google.com/p/make-it-easy/ , its a micro framework for building test data in a functional/fluid style
Posted by: Ross Duncan | Friday, 01 April 2011 at 09:47
Hi Ross, Thank you for the pointer. The code in make-it-easy and the suggestions by Nat and Steve (whom both I know personally since from time to time we talk at the XTC Tuesday in London) are more about writing more readable tests. The idea on which many Agile practitioners are working hard it to write unit tests using a "natural" language. Although I don't share Nat's and Steve's views I do recognise that many players in the community are going towards this direction. The reasons I don't like the approach are mainly that too much time and effort has to be applied in writing unit tests whereas I like the approach which writes comprehensive suites but never forgetting that the real focus is business application logic.
As far as PODAM is concerned, the above frameworks still require one to know the structure of a POJO in order to create the builders. PODAM, on the contrary, wants to relieve developers from knowing such structure and simply obtain a POJO filled with dummy data in one line of code, typically (names have not been decided yet):
POJO pojo = PodamFactory.createDummyPojo(POJO.class);
Eventually developers might customise the behaviour of PODAM by using a @Podam annotation (explained in my article).
However your post gave me an idea for a further requirement: instead of requesting that PODAM compatible POJO have a no-arguments constructor, in case such constructor didn't exist, PODAM might be able to figure out the first minimal constructor and use that to create a new instance. I'll add this requirement as a nice to have.
Thank you.
Marco
Posted by: Marco Tedone | Friday, 01 April 2011 at 10:16
Hi Marco,
I think I understand a bit better now what you are trying to build. A couple of things Im not clear on, if you could help me?
Firstly, when PODEM creates a test objects, should all created instances be equal, or would they simply be equivalent with respect to the @Podam instructions. In other words if you have @minValue = 5 and @maxValue on some field, would all instances of that class contain that field with the same value (between 5 and 100), or would they be various values between 5 and 100
Secondly, more on the design side, do you envisage annotations being the main way to instruct PODEM, or would you also provide an external mechanism? Reading through your above examle with Clients and Addresses I could imagine wanting to have both testing scenarios in the same suite of tests, the first where Address was required, and secondly where Address should be 'skipped'. Annotations would seem to disallow this control.
Sounds really interesting!
Ross
Posted by: Ross Duncan | Friday, 01 April 2011 at 14:42
Hi Ross,
Thank you for your post. To answer to your first question: values would be randomly assigned. So if an attribute had a @Podam annotation with both minValue=5 and maxValue=20 two instances might have the same value although this would not be certain, since the value of the two instances could be anything in the range 5-20. Strings would also be assigned randomly, unless a "value" attribute was specified in the annotation with a well defined String of characters...
Now to your second question...This is an interesting point and one to which I gave quite a thought. On one side it is true that by specifying a @Podam annotation on the POJO this would enforce all tests to be subject to the same behaviour and this is, of course, a limitation. On the other side I really wanted to give developers something easy to setup their POJOs with minimal effort. So in case one wanted different scenarios, in a test one could write:
POJO pojo = PodamFactory.createDummyPojo(POJO.class);
pojo.setAddress(null); //or pojo.setBankAccounts(new ArrayList());
The alternative would be to have the @Podam annotation on the POJO declaration in the test. This would be the best of the two worlds....However...How to get JUnit to process the @Podam annotation? I thought that in order to do so, PODAM should extend JUnit and add capability to process @Podam annotations. The problem is that PODAM *is-not-a-JUnit*; the only possible collaboration I could see would be to use composition (e.g. JUnit "uses" PODAM) but without having PODAM as part of the JUnit code base I see that as a bit difficult.
Have you got any ideas on how we could use a @Podam annotation in JUnit tests? If so I'd be more than happy to hear it and implement it.
Thanks for your posts, they are being really helpful.
Marco
Posted by: Marco Tedone | Friday, 01 April 2011 at 15:10
Hey guys, I made it. PODAM is ready. You can find it out at http://www.jemos.eu/projects/podam/index.html
Posted by: Marco Tedone | Monday, 25 April 2011 at 15:51