Using Randomization in Java Unit Testing
The amazing thing about life is that it is consistent and random simultaneously. If you live in the northeast of the United States, you expect the winter to be cold and the summer to be hot. While temperatures will vary randomly from day to day, you expect a "cold" or "hot" pattern to be in play during those seasons.
Thus, when testing a temperature-dependent product (anti-freeze, for example), it's more accurate to test against random temperatures within a range than to test against a single constant value.
The same can be said of software. Usually, software is meant to run under conditions that are variable. Thus, when you write your unit tests, you will do well to use data that is random within a given range. Yet, for the most part, when it comes to writing basic unit tests, coders tend to hard-code testing data. The reasoning is that when it comes to basic unit tests, hard coding data is much less time consuming and not that risky.
I am here to tell you differently. Using randomization in your unit tests is easy and not at all time consuming, and it will increase the value of your tests in the short and long term. All you need to do is use a Java-based tool that I wrote called the QaRandomizer, which enables you to emit random values for common patterns and data structures quickly and easily.
You can use the QaRandomizer to generate:
- A random zip code with its associated city and state.
- First names, last names, full names, or email address.
- Strings of random characters, each of random length.
The QaRandomizer also has a helper method that extracts a random item from a list of similar type objects.
In this article, I will show you how to use QaRandomizer to provide random data in basic unit testing scenarios, as well as unit tests that use mock objects. Although the concepts I discuss in the article are general, the examples are in Java and the provided sample code is a Maven parent project with child projects. Thus, if you plan to work with the example code, you should know something about object-oriented programming, Java, Maven, and the TestNG testing framework.
Understanding the postal-service Project
The project that accompanies this article is called postal-service. (Click here to get the Maven project source code for postal-service and QaRandomizer.) Postal-service contains objects that represent items and services you'd find in a postal scenario (see Figure 1):
- You create Letter objects that you use with a PostOffice object to stamp and send mail.
- You buy a Stamp object from the PostOffice object.
- You define the sender and recipient of the Letter using Address objects.
- In addition to providing a service to send a Letter, the Post Office provides an Email transmission service.
- The PostOffice object uses a PostalDispatcher interface to facilitate the movement of
- Letter and Email objects.
Figure 1. The Postal Service Object Model: Postal-service contains objects that represent items and services in a postal scenario.
Avoiding the Usual Unit Testing Pitfall
OK, let's get into using randomization in unit testing. A fundamental principle of test-driven development (TDD) is that you write your tests before you write your code. Thus, let's start with the Address object. A pretty basic test is the getter/setter test. Typically in such a test, you need to make sure that the values that you set in the object "stick" and can be accessed through the getter. Listing 1 below shows a unit test for Address setter and getters.
Although pretty trivial, the unit test in Listing 1 illustrates a common pitfall. As you can see in Listing 1 and the code below, the programmer assigned hard-coded values to the Address object and wrote production code that got the test to pass.
//Create some test data String firstName="Bob"; String lastName="Reselman"; String address1="The Waldorff-Astoria Hotel"; String address2="301 Park Avenue"; String city="New York"; String state="NY"; String zip="10022"; //Apply the data to the Address object Address address = new Address(); address.setFirstName(firstName); address.setLastName(lastName); address.setAddress1(address1); address.setAddress2(address2); address.setCity(city); address.setState(state); address.setZip(zip);
Yet, in the final analysis, the test passes using the assigned values and only those values. Thus, the only guarantee is that the implementation code written to pass this test is applicable only to that very small sample of hard-coded data: "Bob", "Reselman", "The Waldorf-Astoria Hotel" "301 Park Avenue", "New York", "NY", "10022".
The developer is assuming that this test is an accurate behavioral model for the code to be writtena questionable assumption. What happens if another developer comes along a year from now and decides in the most fantastic of manner that names that are longer than five characters will not be supported by the Address object's setFirstName(String name) method? What then? In terms of regression testing, "Bob" would pass just fine despite the fact that failing conditions exist.
Now this is not say that you can write a unit test that covers every situation and behavior that may occur for years to come. Clearly, such an expectation is unrealistic. But for the same amount of labor that is required to write tests using hard-coded test data, you can use randomization to cover your testing expectations.
The QaRandomizer provides the ability to generate random first and last name data. It also generates a RandomAddress object that describes a typical postal address and contains random address data. (You'll read more about the RandomAddress object later on when I discuss the methods that make up the QaRandomizer API.)
Notice that in Listing 2 not only is first name, last name, and address data generated randomly, the testing is run over a number of iterations (10, in fact). Thus, the code is saying that you have generated 10 sets of random data against which this test has run and passed 10 times. Probability is on your side!
Listing 3 below shows the data generated in the randomSetterGetterAddressTest() demonstrated in Listing 2.
Listing 3: A Sampling of Name and Address Data Generated by QaRandomizer
Akira Morrisroe, 38 Roosevelt St. Suite 52 SAINT PAUL, KS 66771 Kaoru Sole, 798 Olympic Ave. Floor 87 WASHTA, IA 51061 Irwin Gugliotti, 372 Cherry Dr. Apt. 31 SMITHFIELD, ME 04978 Cheryl Edsall, 865 Elm Dr. Apt. 73 SHANDON, CA 93461 Sharayn Zitello, 103 Olympic Blvd. Room 15 ZEPHYR, TX 76890 Aleck Hermes, 794 Main Dr. MailStop 39 VERONA, WI 53593 Ossie Morrow, 250 Pine St. Room 86 AMA, LA 70031 Milos Calderone, 591 Spruce St. Desk 34 SAINT PETERSBURG, FL 33701 Laurie Brocklehurst, 898 Orange Ct. Apt. 68 GREENWICH, NY 12834 Joleen Jarvis, 538 Washington Dr. Apt. 40 PHOENIX, AZ 85010
If you use the code in Listing 2 as the "Test First test to pass" when writing your trivial Address object, you can rest more assured that your production programming will produce the outcome that you desire. There is no hard coding in play. Each test iteration uses distinct data. And, if you want greater reliability that our object can handle any data, all you need to do is increase the number of iterations in which to run the test.
Now that I've shown you a small benefit of the QaRandomizer, let's look at the services that it offers so that you can use the tool in a variety of situations.