Creating Test Data With the New Keyword: The Telescoping Constructor (Anti)Pattern
After we have finished this lesson, we
- Can identify the drawbacks of the telescoping constructor (anti)pattern.
- Understand when we shouldn’t use the
newkeyword in our test classes. - Know when we should use the
newkeyword in our test classes.
Using the new keyword is the only way to create new objects, and this lesson doesn’t criticize its existence. The point of this lesson is to show us that we must use it in the right place.
This lesson assumes that:
Get the source code from Github
Creating New Objects by Using the Telescoping Constructor (Anti)Pattern
As we remember
, the MailingAddress class uses the telescoping constructor (anti)pattern. Next, I will demonstrate how we can create new MailingAddress objects which contain Finnish, Swedish, and US addresses.
However, before we can create new MailingAddress objects, we have to create a new test class and add all required constants to our test class.
After we have created our test class, its source code looks as follows:
1public class MailingAddressTest {
2
3 private static final String CITY = "Test City";
4 private static final String COUNTRY_SWEDEN = "Sweden";
5 private static final String COUNTRY_UNITED_STATES = "USA";
6 private static final String POST_CODE = "11111";
7 private static final String POST_OFFICE_BOX = "PO Box 1";
8 private static final String RECEIVER = "Teddy Tester";
9 private static final String STATE = "TX";
10 private static final String STREET_ADDRESS = "Test Street 51";
11}
We are now ready to create new MailingAddress objects. Let’s start by creating MailingAddress objects which contain Finnish addresses.
Creating Finnish Addresses
If we want to create a new MailingAddress object that contains a Finnish address, we have to specify the following information:
- The name of the receiver
- Street address or post office box
- Post code
- City
Next, we will create two Finnish addresses:
First, we will provide street address to the constructor of the MailingAddress class.
After we have created a new Finnish address, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class MailingAddressTest {
4
5 private static final String CITY = "Test City";
6 private static final String COUNTRY_SWEDEN = "Sweden";
7 private static final String COUNTRY_UNITED_STATES = "USA";
8 private static final String POST_CODE = "11111";
9 private static final String POST_OFFICE_BOX = "PO Box 1";
10 private static final String RECEIVER = "Teddy Tester";
11 private static final String STATE = "TX";
12 private static final String STREET_ADDRESS = "Test Street 51";
13
14 @Test
15 public void createFinnishAddress() {
16 MailingAddress finnishAddress = new MailingAddress(RECEIVER,
17 STREET_ADDRESS,
18 null,
19 POST_CODE,
20 CITY
21 );
22 }
23}
This test method creates a Finnish address that looks as follows:
1Teddy Tester
2Test Street 51
311111 Test City
Second, we will provide post office box to the constructor of the MailingAddress class.
After we have created a new Finnish address, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class MailingAddressTest {
4
5 private static final String CITY = "Test City";
6 private static final String COUNTRY_SWEDEN = "Sweden";
7 private static final String COUNTRY_UNITED_STATES = "USA";
8 private static final String POST_CODE = "11111";
9 private static final String POST_OFFICE_BOX = "PO Box 1";
10 private static final String RECEIVER = "Teddy Tester";
11 private static final String STATE = "TX";
12 private static final String STREET_ADDRESS = "Test Street 51";
13
14 @Test
15 public void createFinnishAddress() {
16 MailingAddress finnishAddress = new MailingAddress(RECEIVER,
17 STREET_ADDRESS,
18 null,
19 POST_CODE,
20 CITY
21 );
22 }
23
24 @Test
25 public void createFinnishPoBoxAddress() {
26 MailingAddress finnishPoBoxAddress = new MailingAddress(RECEIVER,
27 null,
28 POST_OFFICE_BOX,
29 POST_CODE,
30 CITY
31 );
32 }
33}
This test method creates a Finnish address that looks as follows:
1Teddy Tester
2PO Box 1
311111 Test City
This constructor has two problems:
- It takes five
Stringobjects as constructor arguments. The problem is that if a constructor has many arguments of same type, it is hard to distinguish between different arguments. - The second or third constructor argument must always be
null. If we want to create an address that has a street address, the third argument must benull. On the other hand, if we want to create an address that has a post office box, the second argument must benull. This is a bit confusing and this rule can be hard to remember.
Let’s move on and create new MailingAddress objects which contain Swedish addresses.
Creating Swedish Addresses
If we want to create a new MailingAddress object that contains a Swedish address, we have to specify the following information:
- The name of the receiver
- Street address or post office box
- Post code
- City
- Country
Next, we will create two Swedish addresses:
First, we will provide street address to the constructor of the MailingAddress class.
After we have created a new Swedish address, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class MailingAddressTest {
4
5 private static final String CITY = "Test City";
6 private static final String COUNTRY_SWEDEN = "Sweden";
7 private static final String COUNTRY_UNITED_STATES = "USA";
8 private static final String POST_CODE = "11111";
9 private static final String POST_OFFICE_BOX = "PO Box 1";
10 private static final String RECEIVER = "Teddy Tester";
11 private static final String STATE = "TX";
12 private static final String STREET_ADDRESS = "Test Street 51";
13
14 @Test
15 public void createSwedishAddress() {
16 MailingAddress swedishAddress = new MailingAddress(RECEIVER,
17 STREET_ADDRESS,
18 null,
19 POST_CODE,
20 CITY,
21 COUNTRY_SWEDEN
22 );
23 }
24}
This test method creates a Swedish address that looks as follows:
1Teddy Tester
2Test Street 51
311111 Test City
4Sweden
Second, we will provide post office box to the constructor of the MailingAddress class.
After we have created a new Swedish address, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class MailingAddressTest {
4
5 private static final String CITY = "Test City";
6 private static final String COUNTRY_SWEDEN = "Sweden";
7 private static final String COUNTRY_UNITED_STATES = "USA";
8 private static final String POST_CODE = "11111";
9 private static final String POST_OFFICE_BOX = "PO Box 1";
10 private static final String RECEIVER = "Teddy Tester";
11 private static final String STATE = "TX";
12 private static final String STREET_ADDRESS = "Test Street 51";
13
14 @Test
15 public void createSwedishAddress() {
16 MailingAddress swedishAddress = new MailingAddress(RECEIVER,
17 STREET_ADDRESS,
18 null,
19 POST_CODE,
20 CITY,
21 COUNTRY_SWEDEN
22 );
23 }
24
25 @Test
26 public void createSwedishPoBoxAddress() {
27 MailingAddress swedishPoBoxAddress = new MailingAddress(RECEIVER,
28 null,
29 POST_OFFICE_BOX,
30 POST_CODE,
31 CITY,
32 COUNTRY_SWEDEN
33 );
34 }
35}
This test method creates a Swedish address that looks as follows:
1Teddy Tester
2PO Box 1
311111 Test City
4Sweden
This constructor has three problems:
- It takes six
Stringobjects as constructor arguments. If the created object wouldn’t contain the information of an address, it would be really hard to distinguish between different arguments. - The second or third argument must always be
null. This is the same problem which we noticed when we created Finnish addresses. - We can use this constructor for creating Finnish addresses. If we want to create a Finnish address, we have to set the value of the country argument to
null. This is a problem because now we have to remember to use the correct constructor.
Next, we will create new MailingAddress objects which contain US addresses.
Creating US addresses
If we want to create a new MailingAddress object that contains a US address, we have to specify the following information:
- The name of the receiver
- Street address or post office box
- City
- State
- Post code
- Country
Next, we will create two US addresses:
First, we will provide street address to the constructor of the MailingAddress class.
After we have created a new US address, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class MailingAddressTest {
4
5 private static final String CITY = "Test City";
6 private static final String COUNTRY_SWEDEN = "Sweden";
7 private static final String COUNTRY_UNITED_STATES = "USA";
8 private static final String POST_CODE = "11111";
9 private static final String POST_OFFICE_BOX = "PO Box 1";
10 private static final String RECEIVER = "Teddy Tester";
11 private static final String STATE = "TX";
12 private static final String STREET_ADDRESS = "Test Street 51";
13
14 @Test
15 public void createUSAddress() {
16 MailingAddress usAddress = new MailingAddress(RECEIVER,
17 STREET_ADDRESS,
18 null,
19 POST_CODE,
20 CITY,
21 STATE,
22 COUNTRY_UNITED_STATES
23 );
24 }
25}
This test method creates a US address that looks as follows:
1Teddy Tester
2Test Street 51
3Test City TX 11111
4USA
Second, we will provide post office box to the constructor of the MailingAddress class.
After we have created a new US address, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class MailingAddressTest {
4
5 private static final String CITY = "Test City";
6 private static final String COUNTRY_SWEDEN = "Sweden";
7 private static final String COUNTRY_UNITED_STATES = "USA";
8 private static final String POST_CODE = "11111";
9 private static final String POST_OFFICE_BOX = "PO Box 1";
10 private static final String RECEIVER = "Teddy Tester";
11 private static final String STATE = "TX";
12 private static final String STREET_ADDRESS = "Test Street 51";
13
14 @Test
15 public void createUSAddress() {
16 MailingAddress usAddress = new MailingAddress(RECEIVER,
17 STREET_ADDRESS,
18 null,
19 POST_CODE,
20 CITY,
21 STATE,
22 COUNTRY_UNITED_STATES
23 );
24 }
25
26 @Test
27 public void createUSPoBoxAddress() {
28 MailingAddress usPoBoxAddress = new MailingAddress(RECEIVER,
29 null,
30 POST_OFFICE_BOX,
31 POST_CODE,
32 CITY,
33 STATE,
34 COUNTRY_UNITED_STATES
35 );
36 }
37}
This test method creates a US address that looks as follows:
1Teddy Tester
2PO Box 1
3Test City TX 11111
4USA
This constructor has four problems:
- It takes seven
Stringobjects as constructor arguments. This is unacceptable. - The second or third argument must always be
null. Unfortunately there is no way to avoid this confusing problem. - We can use this constructor for creating Finnish and Swedish addresses as well. If we want to create a Finnish address, we have to set the values of the
stateandcountryarguments tonull. If we want to create a Swedish address, we have to set the value of thestateargument tonull. We have more options now, but this is not a good thing. - The order of the constructor arguments makes sense only if the developer is from Finland. If a US developer would use this constructor, I assume that he/she would hate it because he/she would expect that the arguments are in this order:
receiver,streetAddress,postOfficeBox,city,state,postCode, andcountry.
Let’s move on and find out when we shouldn’t use the new keyword if we have to use a constructor that sets the field values of the created object.
When We Shouldn’t Use the New Keyword
We shouldn’t use the new keyword in our test classes if any of the following conditions is true:
First, the constructor has “too many” constructor arguments. I won’t specify an exact number because this is a decision that you have to make yourself. However, when you start making mistakes and/or creating new objects feels a bit cumbersome, your constructor most likely has too many arguments.
Second, the class in question uses the telescoping constructor (anti)pattern or it has multiple constructors. We should encourage people to write tests that are easy to read and write. If we use the new keyword in this situation, everyone who reads and/or writes our test code must be familiar with the different constructors AND must use one constructor only for one purpose. This is confusing and doesn’t help us to meet our goal.
Third, the order of constructor arguments is logical only to people who belong to a specific group. This is important if the test code can be read and/or written by people who think that the order of constructor arguments is not logical. I think that if our tests can be confusing to some people, they are not easy to read and write.
Next, we will find out when we should use the new keyword if we have to use a constructor that sets the field values of the created object.
When We Should Use the New Keyword
We should use the new keyword in our test classes if any of the following conditions is true:
First, the constructor has only a few constructor arguments. If we can create a new object by using a constructor that takes only a few constructor arguments, we shouldn’t even consider other options because it makes no sense.
Second, the created objects are used by only one test class or a few test classes. Every decision is a trade-off and the other options have a cost. If we are in this situation, the cost of using other options is most likely higher than the benefits of using these options. That is why we should create our test data by using the new keyword.
We can now identify the drawbacks of the telescoping constructor (anti)pattern, and we know when we should use the new keyword in our test classes. Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us three things:
- The telescoping constructor (anti)pattern makes our code hard to read and write.
- If the
newkeyword makes our tests hard to read and write, we shouldn’t use it in our test classes. - We should use the
newkeyword in our test classes if its pros outweigh the cons.