When writing an end-to-end Selenium test for an e-commerce site, one of the last steps is about filling in the user info such as address.
An object of an Address class is created as follows:
Address address = new Address(100,
"Broadway street",
"Langley",
"A1A 2K3",
"Alberta",
"Canada");
The Address class uses the following fields and constructor:
public class Address {
private int streetNumber;
private String streetName;
private String city;
private String postalCode;
private String state;
private String country;
public Address(int streetNumber,
String streetName,
String city,
String postalCode,
String state,
String country) {
this.streetNumber = streetNumber;
this.streetName = streetName;
this.city = city;
this.postalCode = postalCode;
this.state = state;
this.country = country;
}
//getters
//equals(), hashCode(), toString()
}
The constructor has 1 integer and 5 String parameters.
Because of this, the constructor and any Address objects are not too expressive.
But there is another issue here.
It is very easy to create Address objects that are invalid by simply specifying the String values out of order:
Address address = new Address(100,
"Langley",
"Broadway street",
"A1A 2K3",
"Canada",
"Alberta");
If this invalid address is used for any site feature that requires a valid address (example: selecting the address from a list of addresses), the site will work incorrectly and you may assume that you found a bug. Where, in reality, your automated test uses invalid data.
How can we prevent something like this from happening?
We avoid using primitive data types such as int and String.
Instead, we create a tiny class for each field:
StreetNumber class
StreetName class
City class
PostalCode class
State class
Country class
Each of these classes is very small:
public class StreetName {
private String name;
public StreetName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
//equals, hashCode, toString
}
Using the tiny classes for all parameters, the Address class changes to:
public class Address {
private StreetNumber streetNumber;
private StreetName streetName;
private City city;
private PostalCode postalCode;
private State state;
private Country country;
public Address(StreetNumber streetNumber,
StreetName streetName,
City city,
PostalCode postalCode,
State state,
Country country) {
this.streetNumber = streetNumber;
this.streetName = streetName;
this.city = city;
this.postalCode = postalCode;
this.state = state;
this.country = country;
}
//getters
//equals(), hashCode(), toString()
}
Creating an Address object becomes:
Address address = new Address(new StreetNumber(100),
new Streetname("Broadway street"),
new City("Langley"),
new PostalCode("A1A 2K3"),
new State("Alberta"),
new Country("Canada"));
What do we get from creating so many tiny classes?
it is not easy any longer to pass invalid values to parameters (example: passing the postal code as state)
the code becomes much more expressive
we can add validations in each of the tiny classes (example: postal code validation)
Also,
the Selenium library uses as well tiny classes:
We dislike Stringly-typed code, and we like tiny types.
Why do we like them?
Because they allow our code to express intent as clearly as possible, and we can do things like “hang behavior” off them as the need arises.
PS:
If you are interested in seeing a full Selenium project created from A to Z, go here.