SOLVED! HTTP request to the remote WebDriver server for URL http://localhost:52847/….. timed out after 60 seconds.

I can finally sleep again! Just kidding …

For a few weeks, when running Selenium tests in Teamcity, once in a while, I would see tests failing with the following exception:

The HTTP request to the remote WebDriver server for URL http://localhost:52847/session/a56bcb818246209e5d359626409731ac/element/13961238-cb66-437e-9154-1c150eebaf70/value timed out after 60 seconds.

stack trace: at OpenQA.Selenium.Remote.HttpCommandExecutor.MakeHttpRequest(HttpRequestInfo requestInfo) at OpenQA.Selenium.Remote.HttpCommandExecutor.Execute(Command commandToExecute) at OpenQA.Selenium.Remote.DriverServiceCommandExecutor.Execute(Command commandToExecute) at

OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters) at OpenQA.Selenium.Remote.RemoteWebElement.Execute(String commandToExecute, Dictionary`2 parameters) at OpenQA.Selenium.Remote.RemoteWebElement.SendKeys(String text)

This exception would happen sometimes when executing the SendKeys() method, other times when using Click() or Text() or Displayed(). Or any other IWebElement interface  method (C#).

I do not think that it was caused by the C# code because when running the same code locally, the exception would never happen.

It only happened when running the Selenium tests in Teamcity, on virtual machines hosted in Azure.

The Chrome driver was created in a very straightforward way:

 ChromeOptions options = new ChromeOptions();
 options.EnableMobileEmulation(deviceName);
 ChromeDriver drv = new ChromeDriver(options);

OK, what now?

My suspicion is that the exception is happening because

  • either the Teamcity agent (where the UI tests run) is very slow or
  • the site is slow

 

I have searched for a solution for days.

Some people suggested adding the no-sandbox option:

options.AddArgument("no-sandbox");

It did not help.

Others advised that this may be caused by conflicting versions of software (chrome driver, selenium, etc).

Or that the code is causing it and I should use explicit waits (already doing it) instead of implicit waits.

Or that drivers or browsers were not closed correctly.

Or that I should clean temporary files.

Or that Teamcity is causing it.

None of these really helped.

What helped was creating the Chrome driver differently and specifying a timeout for it.

Also, increasing the page load timeout as follows:

 ChromeOptions options = new ChromeOptions();
 options.EnableMobileEmulation(deviceName);
 options.AddArgument("no-sandbox");

 ChromeDriver drv = new ChromeDriver(ChromeDriverService.CreateDefaultService(), options, TimeSpan.FromMinutes(3));
 drv.Manage().Timeouts().PageLoad.Add(System.TimeSpan.FromSeconds(30));  

 

I can sleep again since doing this.

I still get this exception when running UI tests in Teamcity.

But with 95% reduced frequency.

And yes, I still believe that it is somehow related to the site being very slow, occasionally.

 

Advertisements

How difficult is to rewrite a Java Selenium test in C#?

not so bad

I recently wrote an automated test that

  • opens a site
  • searches for a keyword
  • selects a result on results page
  • enables an alert on details page
  • gets api calls executed by details page when enabling the alert
  • gets data from the response of the api calls
BrowserMob is ideal for getting the api calls but i could not get it to work in C# with https requests.

So i wrote the code in Java which worked very well.

The development team insisted though that the automation code is in C# since this is the development language.

So, after figuring out a way of not using BrowserMob (I did not need to get those API calls because the information I was after was also in a cookie), I started to convert the Java code to C#.

How difficult can it be to convert Java code to C#?

It turned out that it was very easy.

 

These were the major differences for my test so it runs in Visual Studio, C# instead of Eclipse and Java:

 

1. Selenium interfaces are named differently (WebDriver is IWebDriver in C#, WebElement is IWebElement)

 

2. There is no CamelCase notation

 

3. String formatting is slightly different and uses {} instead of %

 

4. Writing text to files is a bit different

 

5. const is used instead of final static for constants

 

6. the collection returned by driver.FindElements() is not a list but a readonly collection

 

7. the equivalent of IllegalArgumentException is ArgumentException

 

8. all Selenium methods are minimally different

 

9. there are no expected conditions but the alternative is better and more flexible

 

10. Collection methods have different names

 

11. String in Java is string in C#

 

12. packages in Java are namespaces in C#

 

13. nUnit is similar to jUnit with minor differences

C# and Java are so similar

That it makes me believe that coding in C# after coding in Java is like driving a Honda after driving a Ford.

There are differences, some important but most minimal.

Knowing one well should make learning the other one very easy.

And having 2 languages in your toolbox just makes you more employable.

How to simulate network conditions

It is useful to be able to run Selenium tests on different types of networks, having different download and upload throughput.

This was not possible until recently when the needed support was added to the ChromeDriver class.

See below the code that opens the Google home page on multiple download and upload speeds:

import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.Response;
import org.testng.annotations.DataProvider;
public class TestClass {
   //data provider for the connection's download and upload throughput    
   @DataProvider(name = "test1")
    public Object[][] networkConditions() {
        return new Object[][] {
            { 5000 , 5000 },
            { 10000, 7000 },
            { 15000, 9000 },
            { 20000, 10000 },
            { 23000, 11000 },
            { 30000, 15000 },
            { 40000, 20000 },
            { 50000, 20000 },
            { 75000, 20000 },
            { 100000, 20000 },
            { 0, 0 },
        };
    }
    
    @Test(dataProvider = "networkConditions")
    public void test(int downloadThroughput, int uploadThroughput)
throws IOException {
        System.setProperty("webdriver.chrome.driver",
"c:/selenium/chromedriver.exe");
        ChromeDriver driver = new ChromeDriver();
        
        if (downloadThroughput > 0 && uploadThroughput > 0) {
            CommandExecutor executor = driver.getCommandExecutor();
                   
            Map map = new HashMap();
            map.put("offline", false);
            map.put("latency", 5);
                
            map.put("download_throughput", downloadThroughput);
            map.put("upload_throughput", uploadThroughput);
            Response response = executor.execute(
new Command(driver.getSessionId(), 
"setNetworkConditions", 
 ImmutableMap.of("network_conditions", ImmutableMap.copyOf(map))));
        }
            
        driver.get("http://google.com");
        
        driver.quit();
    }
    
}

 

The code should be easy to follow.

 

A hashmap is created with the network conditions to be used for the browser.

 

The driver gets a command executor and uses it to execute a command on the current session to set the network conditions based on the hash map.

 

These are the results of executing the code:

 

download: 5000   , upload: 5000  , finished in 87221 ms
download: 10000  , upload: 7000  , finished in 46126 ms
download: 15000  , upload: 9000  , finished in 32165 ms
download: 20000  , upload: 10000 , finished in 25703 ms
download: 23000  , upload: 11000 , finished in 22966 ms
download: 30000  , upload: 15000 , finished in 19065 ms
download: 40000  , upload: 20000 , finished in 15008 ms
download: 50000  , upload: 20000 , finished in 13116 ms
download: 75000  , upload: 20000 , finished in 11031 ms
download: 100000 , upload: 20000 , finished in 9463 ms
download: 0      , upload: 0     , finished in 5874 ms

 

If the download and upload are 0, the code is executed with no network conditions specified.

 

In all other cases, a download and upload throughput is being used.

How to make better decisions when buying Selenium courses

where should i go

A few days ago, a manual tester reached out on LinkedIn asking for help on making a change from manual testing to test automation.

He wanted to know what good materials to buy so that he can get started on this transition.

I recommended 2 paid resources of Alan Richardson, none of them cheap, but worth the money:

Java For Tests (book)

Selenium WebDriver Basics With Java (online course)

None of them are free. The book is about $15 and the online course $225.

How do I know that they are good?

I used both of them a while ago and was satisfied with what I learned.

I try not to make recommendations to others for books, courses, blogs, unless I reviewed these materials by myself.

The materials are not free and especially the online course expensive. $225 is a lot of money compared to most (or all) Udemy courses, for example.

How can anyone be sure that they are worth buying?

Both the book and the online course allow you to preview them for free.

About 70 pages of Java For Testers can be read for free. A lot of lessons of the online course can also be previewed.

So anyone interested in either the book or the course should preview the free stuff to get a feeling of the author’s learning style, how clear his English is, how useful is the content, how good is the organization of the material, how fast or slow is the pace.

All these contribute to a good experience of reading the book or going through the course.

Let’s say that you like what you see.

The next question is …

Who is Alan Richardson?

He has a personal blog where you can read everything he blogged on in the last few years.

He also has a business blog where more information about him is available:

4 published books

4 published online courses

Frequent speaker at conferences

From all these, he seems to be very engaged in the test automation field and actually pretty good at it.

It is difficult not to start trusting him after going through all these materials.

So, his materials are good quality, he knows what he writes about, he seems to be an important name in test automation (and testing).

But why are these materials so expensive?

The price can be an issue. Or maybe not.

$225 is expensive compared to $10 on Udemy, $5 on Amazon, free on Youtube.

But for $225, you get content that you verified as valuable, from an author who is credible, knowledgeable and skilled, content that will give you everything you need, content that will keep you engaged and satisfied with what you learn.

You will not need to check other courses or books.

You will not get demoralized because the content is poor or you don’t understand what the author says.

You will not waste any time or money.

You will start on the learning track and stay on it until you get where you wanted and that is to writing automation code.

$225 is the price for not wasting time, not checking online courses randomly, being motivated, knowing that you go the proper direction.

It is difficult to get wrong with these 2 materials for all the reasons already mentioned.

So, before you buy more Selenium materials, try doing a bit of homework about

  • reading the author’s blog
  • going through his free online materials, webinars
  • reading about the author’s activity
  • going through any previews of the paid material that you plan on buying

If after this investigation, you are still interested, do not focus so much on the price but on what you get for it.

As soon as you have your new skills, you will make $225 back in a week.

By the way,

I chose Alan Richardson as an example.

I could have written the same things about other great authors from the Selenium space such as Dave Haeffner, John Sonmez and others.

In summary, learning more about what you want to buy and who made it helps a lot with getting a product worth of your time and money.

 

 

 

 

 

How to create self-documenting reports in a Selenium project (ebook)

A few months ago, if you asked me what I believe about adding reporting to an automation project, my answer was “Why do it?”

 

I thought that reports that show the test execution status are useless because Jenkins has plugins that generate them. Why put any effort in creating my own if they are available out-of-the-box?

 

But recently I realized that there are other types of reports that could be very useful.

 

All Selenium tests are created for a QA team so its testers can run them attended or unattended for new builds with bug fixes or new features. Most of the QA people do not know enough coding so they can just read it to find out what the code does. How can they know the steps that a Selenium test goes through without looking in the code?

 

How can they see what the test does, step by step?

 

The answer is by using self-documenting reports for the Selenium test. This is a report that shows all steps that a Selenium test goes through.

 

There is nothing out-of-the-box in Jenkins that does this so I started looking into possible ways of doing it.

 

The first one creates a simple console reporter that writes to the console all steps of a Selenium test and screenshots taken after each step.

console report

The second does the same thing but with the log4j framework so all information is logged in an HTML file.

log4j report

 The layout of the HTML file is not great which leads to the third solution which is to extend the TestNG HTML reports.

testng report

This third attempt is probably good enough but it requires adding code to each page class method to be included in the final report. This is code duplication which we should try to stay away from.

 

Is there a solution that can generate the self-documenting reports with no code changes?

 

There is one called the Allure framework.

This framework does it all for you without adding code to the page methods. You just add annotations to all methods to be added to the final report. The screenshots are added to the same report through a listener.

allure report

There are a lot of possible solutions with the last one being the best.

I had a lot of fun (ok, honestly, the Allure framework is well documented but if you ask a lot of it, good luck finding help) working on these different ways of reporting.

So I documented everything in a reporting ebook which you can buy if you want to save time and maybe learn one thing or two.

 

A few words about the 4 reporting solutions:

  • all solutions are built with Java, Selenium WebDriver, TestNG and Maven.
  • all code is structured using page object model.
  • the page classes use a base page class for common page information
  • the test class uses a base test class for the driver variable and test fixtures.

 

All solutions implement a simple test case for the Vancouver Public Library site:

  1. open the site (http://www.vpl.ca)
  2. on the home page, type a keyword in the search box
  3. execute the search by clicking the search button
  4. on the results page, click the next page to go to page 2
  5. on the results page, click the next page to go to page 3
  6. on the results page, click the next page to go to page 4
  7. on the results page, click the previous page to go to page 3
  8. assert that the current page number is 3

 

The goal for each solution is to generate a report such as

  • open the site
  • type a keyword in the search box
  • execute the search
  • click the next page
  • click the next page
  • click the next page
  • click the previous page
  • assert that the current page number is 3

 

You can see the first solution here. It will give you an idea of the process followed for the other 3.

The last solution is different from the first 3 because it relies not on my own code but on another library. The last solution, the one based on the Allure framework, is the best.

 

The first solution is free for you. The other 3 are not.

If you want all of them (and the full code for each), please make a PAYPAL transfer of USD 9 to alex@alexsiminiuc.com including your email address. An Amazon gift card works as well.

 

Use Test Factories for Dynamic Tests

Most of the time, we need normal unit tests for test automation:

@Test
public void testSearch() {
}

A normal unit test is a public method that does not return a value, it does not have parameters and uses the @Test annotation.

The unit test can have a parameter though if it is used with a data provider:

@DataProvider(name = "searchKeywords")
public Object[][] searchKeywords() {
return new Object[][] {
{ "iphone"}, { "ipad"}, { "iwatch"}  };
}

@Test(dataProvider = "searchKeywords")
public void testSearch(String keyword) {
}

What is a dynamic test then?

A dynamic test is a unit test of a test class that has a constructor.

The constructor may have a parameter or more and saves them as class members:

import org.testng.annotations.Test;

public class SampleTest {

private int n;

public SampleTest(int n) {
  this.n = n;
}

@Test
public void testScript() {
  System.out.println("test script: " + n);
}

}

The test class gets an integer parameter in the constructor and saves it in a member of the class.

The testScript() method displays the value of the n class member.

testScript() is a dynamic unit test because it belongs to a test class that uses a constructor with a parameter (or more).

How do you use such a unit test?

A test factory creates objects of the test class.

These objects are then executed in the background:

import org.testng.annotations.Factory;

public class FactoryTest {

  @Factory
  public Object[] createTests() {
    System.out.println("create the tests dynamically!");
    Object[] tests = new Object[10];

    for (int i = 0; i < 10; i++)
      tests[i] = new SampleTest(i);

    return tests;

}

}

The createTests method creates first an array to store of test objects of the SampleTest class.

It then creates multiple SampleTest objects and saves them in the array.

Finally, it returns the array of objects.

This is the result of executing the FactoryTest class:

[RemoteTestNG] detected TestNG version 6.14.2
create the tests dynamically!
test script: 0
test script: 1
test script: 5
test script: 3
test script: 9
test script: 2
test script: 7
test script: 6
test script: 8
test script: 4
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript

===============================================
Default test
Tests run: 10, Failures: 0, Skips: 0
===============================================

We see in these results 10 tests being executed and passing.

The interesting part is that they were not executed in the order they were created.

When would dynamic tests be needed in Selenium test automation?

Let’s assume that we want to automate a test case for the https://www.thebay.com/ site.

If you mouse over the accessories option of the menu, multiple categories are displayed.

bay

We want to verify that when clicking on any of these categories, results are displayed.

We could get started with a test as follows:

import static org.testng.Assert.assertTrue;

import java.util.List;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class CategoriesTest {

@DataProvider(name = "accessoriesCategories")
public Object[][] createData1() {
  return new Object[][] {
    { "New Arrivals"},
    { "Best Sellers"},
    { "Online Only"},
    { "Watches"}
  };
}

@Test(dataProvider = "accessoriesCategories")
public void testCategoriesUsingDataProvider(String category) {

  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  ResultsPage resultsPage = menu.selectCategory(category);
  assertTrue(resultsPage.hasResults(), "no results for " + category);

}

}

The categories are defined in a data provider and the test has a parameter for a category.

The test will be executed once for each category.

The test uses 3 classes.

HomePage.java

public class HomePage {

public void open() {
  System.out.println("open home page");
}

public Menu openAccessoriesMenu() {
  System.out.println("open accessories menu");
  return new Menu();
}

}

Menu.java

import java.util.Arrays;
import java.util.List;

public class Menu {

public ResultsPage selectCategory(String category) {
  System.out.println("category selected: " + category);
  return new ResultsPage();
}

public List getCategories() {
  System.out.println("get categories");

  return Arrays.asList( "New Arrivals",
                        "Best Sellers",
                        "Online Only",
                        "Watches");
  }

}

ResultsPage.java

public class ResultsPage {

public boolean hasResults() {
  return true;
}

public HomePage goBack() {
  System.out.println("go back to home page");
  return new HomePage();
}
}

The result of executing the test is below:

[RemoteTestNG] detected TestNG version 6.14.2
open home page
open accessories menu
category selected: New Arrivals
open home page
open accessories menu
category selected: Best Sellers
open home page
open accessories menu
category selected: Online Only
open home page
open accessories menu
category selected: Watches
PASSED: testCategoriesUsingDataProvider("New Arrivals")
PASSED: testCategoriesUsingDataProvider("Best Sellers")
PASSED: testCategoriesUsingDataProvider("Online Only")
PASSED: testCategoriesUsingDataProvider("Watches")

===============================================
Default test
Tests run: 4, Failures: 0, Skips: 0
===============================================

The solution works if the categories for the Accessories option do not change.

But what if they change all the time?

Obviously, we will not be able to use the data provider any longer so the test will change:

@Test
public void testCategoriesWithoutDataProvider() {
  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  List < String > categories = menu.getCategories();

  for (String category: categories) {
    ResultsPage resultsPage = menu.selectCategory(category);
    assertTrue(resultsPage.hasResults(), "no results for " + category);

    homePage = resultsPage.goBack();
    menu = homePage.openAccessoriesMenu();
  }

}

The test is more complicated than before.

It gets all categories of the menu and then iterates through them. For each category, the test selects the category, verifies that there are results and then goes back to HomePage.

The result of executing the test is below:

[RemoteTestNG] detected TestNG version 6.14.2
open home page
open accessories menu
get categories
category selected: New Arrivals
go back to home page
open accessories menu
category selected: Best Sellers
go back to home page
open accessories menu
category selected: Online Only
go back to home page
open accessories menu
category selected: Watches
go back to home page
open accessories menu
PASSED: testCategoriesWithoutDataProvider

===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================

The test does pretty much the same thing as before.

But instead of having one test for each category, we have 1 single test for all categories.

If the test fails, it will not be clear which category has a problem.

So this is what we want to fix.

We will simplify the test class so it does only the selection of the category:

import static org.testng.Assert.assertTrue;

import java.util.List;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class SelectCategory {

private String category;

public SelectCategory(String category) {
this.category = category;
}

@Test
public void selectCategory() {
  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  ResultsPage resultsPage = menu.selectCategory(category);
  assertTrue(resultsPage.hasResults(), "no results for " + category);

}
}

And we will use a test factory class that gets all categories and creates multiple objects for the SelectCategory class:

import java.util.List;

import org.testng.annotations.Factory;

public class SelectCategoryFactory {

@Factory
public Object[] testCategories() {
  System.out.println("start test factory");

  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  List < String > categories = menu.getCategories();

  closeBrowser();

  Object[] tests = new Object[categories.size()];

  for (int i = 0; i < categories.size(); i++)
    tests[i] = new SelectCategory(categories.get(i));

  System.out.println("end of test factory");

  return tests;
}

private void closeBrowser() {
  System.out.println("close site");
}

}

The result of executing the test factory shows again 4 tests passing:

[RemoteTestNG] detected TestNG version 6.14.2
start test factory
open home page
open accessories menu
get categories
close site
end of test factory
open home page
open accessories menu
category selected: Watches
open home page
open accessories menu
category selected: Best Sellers
open home page
open accessories menu
category selected: New Arrivals
open home page
open accessories menu
category selected: Online Only
PASSED: selectCategory
PASSED: selectCategory
PASSED: selectCategory
PASSED: selectCategory

===============================================
Default test
Tests run: 4, Failures: 0, Skips: 0
===============================================

The test factory allows the initial test to be changed so that it works for dynamic categories. It also allows executing a test for each category.

How good is your Selenium Java code?

code-review

 

You probably know who Martin Fowler is.

He coined the term Page Object Model sometime in 2013:
https://martinfowler.com/bliki/PageObject.html

Yes, the page object model that you use in your Selenium test automation projects.

His contribution to software development goes way beyond the page object model since he wrote many useful books on topics such as code refactoring and design patterns.

He also said that

Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.

This is what I want to focus on today, on the code written by good programmers.

Good programmers write code that other programmers can understand but also can change and maintain.

This is what we should aim at in our Selenium projects.

Our code should also be easy to understand, change and maintain.

 

How do you know if your code does all these?

What are the rules that your code should follow so that it is good?

Let’s see.

 

Write Clean Code

The code should be clean.

This means a lot of things.

1. The code should use clear names

Variables should have names that explain their purpose.
Methods should have names that either explain what the method does or the result returned by the method.

2. Use problem domain names

If you do automation for an e-commerce site, make sure that you have classes for concepts such as

  • product
  • user
  • basket
  • cart
  • order
  • invoice

3. Classes should be small

A class should contain an average of less than 30 methods.

4. Methods should be small

Methods should not have more than an average of 30 code lines.

5. Do one Thing

This applies to both classes and methods.
If a method does more than one thing, consider splitting it in 2.
If a class has more than one responsibility, consider breaking it in more classes.

6. Don’t Repeat Yourself 

Any duplication, inside a page object class, a page method, a test class or a test method should be avoided.

7. Explain yourself in code

Write code that is self-explanatory, that is so easy to understand so no comments are needed.

8. Make sure the code formatting is applied

Code formatted correctly is easier to read by other developers.

9. Use Exceptions rather than Return codes

If a method cannot fulfill its purpose, instead of returning an obscure error code,
throw an exception since the code is in an abnormal state.

10. Don’t return Null

There are many ways of avoiding returning null.
You can use Optional introduced in Java 8.
Or you can return an empty list.

Write Secure Code

1. Make class final if not being used for inheritance

Making the class final ensures that it is not extended.

All page classes and page element classes should be final.

2. Avoid duplication of code

3. Limit the accessibility of packages,classes, interfaces, methods, and fields

Parent classes (base page class, base test class) should be abstract.

All methods of a parent class should be declared as protected since they should only be used in the child classes.

Only a small number of methods of any class should be public.

If a class should be used only by other classes in the same package, use the default access modifier.

4. Validate inputs (for valid data, size, range, boundary conditions, etc)

Any public method should have its parameters checked for validity.

5. Avoid excessive logs

If you are logging tracing information for all page classes and their methods, when you run the whole suite of tests, you will get excessive logs that are very difficult to read.

This becomes even worse if the automated tests are run in parallel on different virtual machines.

6. Release resources (Streams, Connections, etc) in all cases

Consider using try/catch with releasing resources.

7. Purge sensitive information from exceptions (exposing file path, internals of the system, configuration)

8. Do not log highly sensitive information

9. Make public static fields final (to avoid caller changing the value)

10. Avoid exposing constructors of sensitive classes

Use a factory method instead of the constructor to create objects.

General Rules

1. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

2. Favor the use of standard exceptions

3. Don’t ignore exceptions

4. Check parameters for validity

5. Return empty arrays or collections, not nulls

6. Minimize the accessibility of classes and members

7. In public classes, use accessor methods, not public fields

All fields of a class should be private so that objects cannot be changed (stay imutable).
Set methods are bad for the same reason since they make an object mutable.

Get methods should be used to expose fields outside of the class.

8. Minimize the scope of local variables

Declare the variable just before you need it and not at the beginning of the method.

9. Refer to objects by their interfaces

Also called program to an interface.

10. Adhere to generally accepted naming conventions

11. Use enums instead of int constants

SortOrder.PriceAscending is always more clear than 1.

12. Beware the performance of string concatenation

Use StringBuilder class instead.

13. Avoid creating unnecessary objects

 

This is obviously an incomplete list.

But it should get you started on focusing more on the quality of your code.

Because, good programmers write good code and you want to be a good programmer.

Don’t you?

Don’t Do It Yourself

DDIY

DIY or Do it yourself.

I started paying attention to the DIY concept after seeing the following method that waits until a page is loaded in the browser:

public void waitForPageToLoad(String string){
  String url = browserDriver.getCurrentUrl();
  Boolean stringExists = url.contains(string);
  int Count = 0;
 
  while (Boolean.FALSE.equals(stringExists) && Count < 100){
     url = browserDriver.getCurrentUrl();
     stringExists = url.contains(string);
     Count = Count + 1;
     try {
        Thread.sleep(500);
     } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
     }
  }
 
   //if(stringExists==false)
   if(Boolean.FALSE.equals(stringExists))
      throw new IllegalStateException(
        “Page not loaded. Current page” + “is: “
         + browserDriver.getCurrentUrl()); 
   }
}

 

The  method seems useful at first sight. But reading the code with more attention, one realizes that it is just a bad implementation of concepts that already exist in the Selenium WebDriver library: WebDriverWait and ExpectedConditions.

 

The same result is provided by 2 lines of code:

WebDriverWait wait = new WebDriverWait(driver, 30);
wait.until(ExpectedConditions.urlContains(keyword));

 

 

 

When writing code, this is one of the things that are always so tempting.

  1. You need to convert an array into a list so you put together a method that does it.
  2. You need a  way to wait until a page is loaded so you write quickly some code for it.
  3. You need random strings and numbers in your project so you create your class that generates them.

 

After doing it yourself, the result is always the same. You are satisfied about being capable of completing the task. You are capable of writing code that is useful in your project. You can show it to a colleague with pride.

 

But then, you discuss your new classes with someone a bit more senior.

 

Who listens to you carefully and then replies with 3 words:

Don’t do it yourself!

(There are actually 4 words here, hmmm, my counting is not what it used to be.)

 

What did you say? What do you mean by “don’t do it yourself”? What is wrong with my code? It works very well. It has no bugs. It is well designed.

 

That’s what you think now. It may be working for now but will it continue to do so in the future for all your other needs?

Are you sure that it will be easy to extend and maintain?

And actually, why are you doing it? Don’t you have other more important code to write?

 

Yes, I have more code to write. But what about all these things? Generating a random number and string, for example?

 

 

Before starting to write any common-purpose code, check if anyone else did it before you. Chances are very high that there is already a library out there that does what you need and much more. This library was created to work in a variety of situations. It is well designed and probably extensible. It is well documented and well tested.

 

 

Let’s take generating a random string and number.

Your code may look like this:

String text = "";
char[] symbols = new char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};

int size = 20;

for (int i = 0; i < size; i++) {
Random random = new Random();
int randomIndex = random.nextInt(symbols.length);
text = text + symbols[randomIndex];
}

System.out.println(text);

 

Easy enough, isn’t it?

 

Yes, but very limited too.

 

How about using instead the RandomStringGenerator class from the commons apache library?

Look how easy it is to

  • generate random string values from letters only
  • generate random string values from digits only
  • generate random string values from letters and digits and with variable length
char[] letters = new char[] {'a', 'z'};
char[] numbers = new char[] {'0', '9'};

RandomStringGenerator generator;

generator = new RandomStringGenerator
.Builder().withinRange(letters).build();

System.out.println(generator.generate(20));

generator = new RandomStringGenerator
.Builder().withinRange(numbers).build();

System.out.println(generator.generate(20));

generator = new RandomStringGenerator
.Builder().withinRange(letters, numbers).build();

for (int i = 1; i <=10; i++)
System.out.println(generator.generate(10, 20));

 

In conclusion …

 

There is no need to re-invent the wheel.

Unless you have so many ideas and intuitions that your new wheel is compared to the old one what the first iphone was compared to a blackberry.

 

Otherwise, spend your time to write code that implements a good problem for the company you work for.

And, every time you can, don’t do it yourself.

Use third party libraries instead such as apache commons.

Program to an interface with Selenium WebDriver (eBook)

pexels-photo-755834.jpeg

Be Flexible With Java Interfaces – Pexels.com

 

Anyone can write code that a computer can understand.
Good programmers write code that humans can understand.

Martin Fowler

 

Testers and developers use the Selenium WebDriver library for implementing test automation for various websites.

They use Selenium WebDriver for its major purpose: to interact with a site in the browser.

 

But I believe that there is something else that we can use this library for.

We can use it to learn how to write good code.

Code that is easy to maintain.

Easy to extend, easy to use and understand.

 

What can you learn from the Selenium WebDriver code?

 

You can learn how to apply programming concepts such as factories.

Or how to extend code instead of modifying it with decorators.

Or how program to an interface.

 

Programming to an interface is the focus here.

 

What does it mean?

 

It means two things:

1. each class that you create should implement an interface

2. every time an object of the class is created, its type should be the interface that the class implements

 

This sounds pretty abstract so lets see if an example makes things simpler.

 

This is the example provided for interfaces in the java documentation.

In its most common form, an interface is a group of related methods with empty bodies.

A bicycle’s behavior, if specified as an interface, might appear as follows:

 

interface Bicycle {

  void changeCadence(int newValue);

  void changeGear(int newValue);

  void speedUp(int increment);

  void applyBrakes(int decrement);
}

 

To implement this interface, the name of the class would change to a particular brand of bicycle and you’d use the implements keyword in the class declaration.

The class would also have to provide an implementation for each method defined by the interface.
class TrekBicycle implements Bicycle {

  int cadence = 0;
  int speed = 0;
  int gear = 1;

  void changeCadence(int newValue) {
    cadence = newValue;
  }

  void changeGear(int newValue) {
    gear = newValue;
  }

  void speedUp(int increment) {
    speed = speed + increment;
  }

  void applyBrakes(int decrement) {
    speed = speed - decrement;
  }

  void printStates() {
    System.out.println("cadence:" +
      cadence + " speed:" +speed + " gear:" + gear);
 }

}

 

Implementing an interface allows a class to become more formal about its behavior.

If your class claims to implement an interface, all methods defined by that interface must appear in its source code before the class will successfully compile.

 

What is so special about a class implementing an interface?

 

When you create an object of the class, you can do it in 2 different ways:

TrekBicycle trekBike = new TrekBicycle();
Bicycle trekBike = new TrekBicycle();

 

The second way of creating the object provides a lot of flexibility in dealing with all bicycle objects .

If you have an additional Bicycle class, for example

class GTBicycle implements Bicycle {

  int cadence = 0;
  int speed = 0;
  int gear = 1.25;

  void changeCadence(int newValue) {
    cadence = newValue;
  }

  void changeGear(int newValue) {
    gear = newValue * 1.1;
  }

  void speedUp(int increment) {
    speed = speed + increment + 1.2;
  }

  void applyBrakes(int decrement) {
    speed = speed - decrement;
  }

  void printStates() {
    System.out.println("cadence:" +cadence + " speed:" +
   speed + " gear:" + gear);
  }

}


an object of the GTBicycle class can also be saved in a Bicycle variable:

Bicycle gtBike = new TrekBicycle();

 

Any object of a class than implements the Bicycle interface can be saved in a Bicycle variable.

 

Where have we seen this with Selenium WebDriver?

 

You can see interfaces everywhere:

 

1. When creating a driver object

A driver object for the Chrome browser is created as follows

WebDriver driver = new ChromeDriver();

 

WebDriver is an interface while ChromeDriver is a class that extends RemoteDriver which implement WebDriver.

 

To create a driver for Firefox, we just change the class name:

WebDriver driver = new FirefoxDriver();

Same for the driver for Edge:

WebDriver driver = new EdgeDriver();

 

2. When finding a web element

WebElement button = driver.findElement(locator);

 

WebElement is an interface.

For this reason, you can save in a WebElement variable any type of html element: button, link, label, image, list, textbox, etc.

 

3. When typing a keyword in a textbox

All of the following ways of typing a keyword in a textbox work:

a. type a String value

WebElement searchBox = driver.findElement(textBoxId);
String keyword = "symphony";
searchBox.sendKeys(keyword);

 

b. type a StringBuilder value

WebElement searchBox = driver.findElement(textBoxId);
StringBuilder author = new StringBuilder()
                          .append("wolfgang")
                          .append(" ")
                          .append("amadeus")
                          .append(" ")
                          .append("mozart");

searchBox.sendKeys(author);

 

c. type a StringBuffer value

WebElement searchBox = driver.findElement(textBoxId);
StringBuffer topic = new StringBuffer().append("classical")
                                       .append(" ")
                                       .append("music");

searchBox.sendKeys(author);

 

d. type a String, a StringBuilder, a StringBuffer and a key

WebElement searchBox = driver.findElement(searchBoxId);
StringBuilder author = new StringBuilder()
                         .append("wolfgang")
                         .append(" ")
                         .append("amadeus")
                         .append(" ")
                         .append("mozart");

String space = " ";
StringBuffer topic = new StringBuffer()
                         .append("classical")
                         .append(" ")
                         .append("music");

String subTopic = "symphony";

searchBox.sendKeys(author, space, topic, space, subTopic, Keys.TAB);

 

Why do all these different ways of typing a keyword work?

 

It looks as if sendKeys may have one or more parameters and they can have different types: String, StringBuilder, StringBuffer, Keys.

The sendKeys signature explains it all:
void sendKeys(CharSequence... keysToSend)

 

The … explains why you can have one or more parameters.
… means that sendKeys can have a variable number of parameters of the same type.
All parameters must have the CharSequence type.

 

CharSequence explains why we can have so many classes as parameter types for sendKeys.

 

CharSequence is an interface in Java that is implemented by classes such as String, StringBuilder and StringBuffer.

This means that you can use any object of the String, StringBuffer and StringBuilder classes as a CharSequence variable.

 

What about Keys?

Keys is a Selenium enumeration which implements as well the CharSequence interface.

What should we learn from all this?

A few things …

1. Each class that we create should implement an interface.

2. When creating an object of the class, use the interface as its type.

 

How can you use interfaces in a Selenium project?

 

If you are interested in seeing how “programming to an interface” is done in a Selenium WebDriver project step by step, I have an ebook for you.

 

It will show you how to change code that does not use interfaces so that it follows the “programming to an interface” principle.

 

The result of applying this principle is that we will be able to replace 10 automated scripts that search for different categories (car, minivan, boat, rv, etc) with one script.

So, instead of having a search test for each category

public class TestClass {

   CarInfo carInfo = new CarInfo("audi", "a4");

   MinivanInfo minivanInfo = new MinivanInfo("nissan","navara", "pickup");

   BikesInfo bikesInfo = new BikesInfo(
                             "kawasaki", "gpz1000", "sports tourer");

   @Test
   public void canSearchForCars() {

     CarHome homePage = new CarHome();
     homePage.open();

     CarResults resultsPage = homePage.searchFor(carInfo);
     assertTrue(resultsPage.count() > 0);

   }

   @Test
   public void canSearchForMinivans() {

     MinivanHome homePage = new MinivanHome();
     homePage.open();

     MinivansResults resultsPage = homePage.searchFor(minivanInfo);
     assertTrue(resultsPage.count() > 0);

  }

  @Test
  public void canSearchForBikes() {

     BikesHome homePage = new BikesHome();
     homePage.open();

     BikesResults resultsPage = homePage.searchFor(bikesInfo);
     assertTrue(resultsPage.count() > 0);

  }


}

 

you can have

public class TestClass {

  String postalCode = "w1f7tu";

  @DataProvider(name = "vehicleInfo")
  public Object[][] vehicleInfo() {
    return new Object[][] {
      { "cars" , new CarInfo("BMW", "3 SERIES") },
      { "minivans", new MinivanInfo("NISSAN", "NAVARA", "Pickup")},
      { "bikes" , new MotorHomesInfo("AUTO-SLEEPERS", "2")},
    };
  }

  @Test(dataProvider = "vehicleInfo")
  public void canSearchForCategory(String category,
                                   VehicleInfo vehicleInfo) {

     HomePage homePage = openHomePage(category);
     homePage.open();

     ResultsPage resultsPage = homePage.searchFor(
                                     vehicleInfo, postalCode);
     assertTrue(resultsPage.count() > 0);
  }

}

 

Interested?

 

Send USD 15 to alex@alexsiminiuc.com through Paypal.
I will email you the book and answer all your questions about it, also by email.

 

Extend, Dont Change

Keep-calm-and-don-t-change-7

The test automation code should be as professional as the application code.

It should be built using the same principles.

Such as “program to an interface” and “extend, dont change”.

 

How do you build it so that it follows such principles?

Let’s imagine that we have a class that formats a phone number with spaces between the area code, prefix and the line number.

For a phone number like 6143567891, the PhoneNumber class displays it as 614 356 7891.

import java.util.ArrayList;
import java.util.List;

public class PhoneNumber {
 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  }

  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  public String getValue() { 
    return String.format("%s %s %s", 
                         areaCode(), 
                         prefix(), 
                         lineNumber()); 
  } 

  public String areaCode() { 
    return String.format("%d%d%d", 
                         digits.get(0), 
                         digits.get(1), 
                         digits.get(2)); 
  } 

  public String prefix() { 
    return String.format("%d%d%d",   
                         digits.get(3), 
                         digits.get(4), 
                         digits.get(5)); 
  }

  public String lineNumber() { 
    return String.format("%d%d%d%d",   
                         digits.get(6), 
                         digits.get(7), 
                         digits.get(8), 
                         digits.get(9)); 
  } 

  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 

}

The following test displays a phone number formatted properly:

import java.util.Arrays;
import java.util.List;
import org.testng.annotations.Test;

public class TestClass {
  private final static List<Integer> TEN_DIGITS = 
          Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testNormalPhone() { 
    PhoneNumber phone = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone.getValue()); 
  } 
}

 

For a while, this phone template is the only template needed for the project.

Multiple classes use objects of the PhoneNumber class.

But things change and various other formats become useful such as:

(614) 356 7891 : the area code is in ()

614-356-7891 : the area code, prefix and line number are separated by –

604 356 7891 : the 604 prefix is used

778 356 7891 : the 778 prefix is used

 

How can the additional templates be implemented?

You may choose to make changes to the PhoneNumber class for the additional templates.

Since there is already a getValue() method used in many other classes, you leave it as is and create additional methods, one for each additional template:

import java.util.ArrayList;
import java.util.List;
 public class PhoneNumber { 

  private List<Integer> digits = new ArrayList<>(); 
  
  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 

  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  public String getValue() { 
    return String.format("%s %s %s", 
                         areaCode(), 
                         prefix(), 
                         lineNumber()); 
  }
  public String getValueWithParanthesis() { 
   return String.format("(%s) %s %s", 
                        areaCode(), 
                        prefix(), 
                        lineNumber()); 
  } 

  public String getValueWithHyphens() { 
    return String.format("%s-%s-%s", 
                         areaCode(), 
                         prefix(), 
                         lineNumber()); 
  } 

  public String getValueWith604Prefix() { 
    return String.format("604 %s %s", 
                         prefix(), 
                         lineNumber()); 
  }
  public String getValueWith778Prefix() { 
    return String.format("778 %s %s", 
                         prefix(), 
                         lineNumber()); 
  } 

  public String areaCode() { 
    return String.format("%d%d%d", 
                         digits.get(0), 
                         digits.get(1), 
                         digits.get(2)); 
  } 

  public String prefix() { 
    return String.format("%d%d%d", 
                         digits.get(3), 
                         digits.get(4), 
                         digits.get(5)); 
  } 

  public String lineNumber() { 
    return String.format("%d%d%d%d", 
                        digits.get(6), 
                        digits.get(7), 
                        digits.get(8),  
                        digits.get(9)); 
  }
 
  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 
}

 

The test displays all possible formats:

import java.util.Arrays;
import java.util.List; import org.testng.annotations.Test; 
public class TestClass {
  private final static List<Integer> TEN_DIGITS = 
                Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testPhoneTemplates() { 

    PhoneNumber phone1 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone1.getValue()); 

    PhoneNumber phone2 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone2.getValueWithParanthesis()); 

    PhoneNumber phone3 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone3.getValueWithHyphens()); 

    PhoneNumber phone4 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone4.getValueWith604Prefix()); 

    PhoneNumber phone5 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone5.getValueWith778Prefix()); 

  } 
}

 

This works.

But we just violated many principles that should be followed when writing code:

  • program to an interface

When creating a phone number object, it should be saved in a variable that uses an interface as type:

IPhoneNumber phoneNumber = new PhoneNumber();

Instead, we save the phone number object in a variable with the PhoneNumber type:

PhoneNumber phoneNumber = new PhoneNumber();
  • extend, not change

We changed the PhoneNumber class by adding 4 additional methods.

A class should not be changed since this may create issues in all other classes that use its objects.

  • single responsibility

The PhoneNumber class started with having one responsibility, to format a phone number.

Now, the class has 5 responsibilities by providing 5 types of formatting.

 


 

What do we do?

How do we change the code so all these 3 principles are followed?

 


 

  1. Create a IPhoneNumber interface

import java.util.List;
 public interface IPhoneNumber { 

   public String getValue(); 

   public List<Integer> getDigits(); 

   public String areaCode(); 

   public String prefix();
   public String lineNumber(); 

}

 

2. Create a class for each phone number template and  implement the IPhoneNumber interface

 

PhoneNumber class

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumber implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 

  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  @Override
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()  ); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0), 
                         digits.get(1),  
                         digits.get(2)); 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3), 
                         digits.get(4), 
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6), 
                         digits.get(7),
                         digits.get(8),
                         digits.get(9)); 
  } 

  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 

}

 

PhoneNumberWithParanthesis class

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumberWithParanthesis implements IPhoneNumber { 

  private final String phoneTemplate = "(%s) %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberWithParanthesis(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0),               
                         digits.get(1),               
                         digits.get(2)); 
  } 

  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 

}

 

PhoneNumberWithHyphens class

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumberWithHyphens implements IPhoneNumber { 

  private final String phoneTemplate = "%s-%s-%s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberWithHyphens(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  @Override
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0),               
                         digits.get(1),               
                         digits.get(2)); 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } }

 

PhoneNumberStartingWith604 class

import java.util.ArrayList;
import java.util.List;
 public class PhoneNumberStartingWith604 implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberStartingWith604(List<Integer> digits) { 

    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 
  
    this.digits = digits; 
  }

  @Override   public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return "604"; 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 

}

 

PhoneNumberStartingWith778 class

import java.util.ArrayList;
import java.util.List;
 public class PhoneNumberStartingWith778 implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberStartingWith778(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  @Override
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return "778"; 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 

}

 

The test displays all possible templates for a phone number:

import java.util.Arrays;
import java.util.List; import org.testng.annotations.Test; 
public class TestClass {
  private final static List<Integer> TEN_DIGITS = 
              Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testPhoneTemplates() { 

    IPhoneNumber phone1 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone1.getValue()); 

    IPhoneNumber phone2 = new PhoneNumberWithParanthesis(TEN_DIGITS); 
    System.out.println(phone2.getValue()); 

    IPhoneNumber phone3 = new PhoneNumberWithHyphens(TEN_DIGITS); 
    System.out.println(phone3.getValue()); 

    IPhoneNumber phone4 = new PhoneNumberStartingWith604(TEN_DIGITS); 
    System.out.println(phone4.getValue()); 

    IPhoneNumber phone5 = new PhoneNumberStartingWith778(TEN_DIGITS); 
    System.out.println(phone5.getValue()); 
  } 
}

 

The code is better because

  • each class has one responsibility only (for a single phone template)
  • each class implements an interface; each phone object, even if created with a class, is saved in a variable with the IPhoneNumber interface

 

But, looking at all classes, it is easy to see that have so much duplicated code.

 


 

What do we do next?

A different approach is needed.

We started with a PhoneNumber class that formats a phone number using a template.

We would like not to modify this class but extend it so that we can format the phone number with other templates.

Decorators provide this behaviour.

A decorator allows extending the functionality of a class without making any changes to the class.

We need a few steps for the new implementation:

  1. We keep the same IPhoneNumber interface

import java.util.List; 
public interface IPhoneNumber { 

  public String getValue(); 

  public List<Integer> getDigits(); 

  public String areaCode(); 

  public String prefix(); 

  public String lineNumber(); 
}

 

2. We keep the PhoneNumber class in its original shape.

The PhoneNumber class implements the IPhoneNumber interface:

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumber implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String getValue() { 
    return String.format(phoneTemplate,  
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 
  @Override
  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0),               
                         digits.get(1),               
                         digits.get(2)); 
  } 

  @Override 
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override 
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

}

 

3.  Create an abstract class named PhoneDecorator:

import java.util.List; 
public abstract class PhoneDecorator implements IPhoneNumber { 
  private IPhoneNumber decoratedPhone; 

  public PhoneDecorator(IPhoneNumber phone) { 
    decoratedPhone = phone; 
  } 

  public String getValue() { 
    return decoratedPhone.getValue(); 
  } 

  public List<Integer> getDigits() { 
    return decoratedPhone.getDigits(); 
  } 

  public String areaCode() { 
    return decoratedPhone.areaCode(); 
  } 

  public String prefix() { 
    return decoratedPhone.prefix(); 
  } 

  public String lineNumber() { 
    return decoratedPhone.lineNumber(); 
  }

}

The abstract class PhoneDecorator will be used as parent for all specific decorator classes.

We will have one decorator class for each new template:

  • Phone With Paranthesis
  • Phone With Hyphens
  • Phone With 604 Prefix
  • Phone With 778 Prefix

 

4. Create the specific decorators

 

PhoneWithParanthesisDecorator class

public class PhoneWithParanthesisDecorator extends PhoneDecorator {    public PhoneWithParanthesisDecorator(IPhoneNumber phone) {      super(phone);    }    public String getValue() {      String phoneTemplate = "(%s) %s %s";      return String.format(phoneTemplate,                                         super.areaCode(),                           super.prefix(),                                        super.lineNumber());    } }

 

PhoneWithHyphensDecorator class

public class PhoneWithHyphensDecorator extends PhoneDecorator { 
  
  public PhoneWithHyphensDecorator(IPhoneNumber phone) { 
    super(phone); 
  } 

  public String getValue() { 
    String phoneTemplate = "%s-%s-%s"; 
    
    return String.format(phoneTemplate,               
                         super.areaCode(),  
                         super.prefix(),   
                         super.lineNumber()); 
  } 

}

 

PhoneWith604PrefixDecorator class

public class PhoneWith604PrefixDecorator extends PhoneDecorator { 

  public PhoneWith604PrefixDecorator(IPhoneNumber phone) { 
    super(phone); 
  } 

  public String getValue() { 
    String phoneTemplate = "%s %s %s"; 

    return String.format(phoneTemplate,               
                         "604", 
                         super.prefix(),   
                         super.lineNumber()); 
  }

}

 

PhoneWith778PrefixDecorator class

public class PhoneWith778PrefixDecorator extends PhoneDecorator { 

  public PhoneWith778PrefixDecorator(IPhoneNumber phone) { 
    super(phone); 
  } 

  public String getValue() { 
    String phoneTemplate = "%s %s %s"; 

    return String.format(phoneTemplate, 
                         "778",  
                         super.prefix(),  
                         super.lineNumber()); 
  }

}

 

Each decorator only implements the change about a specific template.

It does this by encapsulating an IPhoneNumber object and then overriding the getValue() method of the PhoneDecorator class.

There is no code duplication between the phone decorator classes.

They do not have code for anything else than the getValue() method.

 

5. Wrap the PhoneNumber objects with specific decorators to use other phone templates

The test method puts everything together:

import java.util.Arrays;import java.util.List; import org.testng.annotations.Test; 
public class TestClass {
   private final static List<Integer> TEN_DIGITS = 
          Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testPhoneTemplates() { 

    IPhoneNumber phone1 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone1.getValue()); 

    IPhoneNumber phone2 = new PhoneWithParanthesisDecorator( 
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone2.getValue()); 

    IPhoneNumber phone3 = new PhoneWithHyphensDecorator(
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone3.getValue()); 

    IPhoneNumber phone4 = new PhoneWith604PrefixDecorator(
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone4.getValue()); 

    IPhoneNumber phone5 = new PhoneWith778PrefixDecorator(
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone5.getValue()); 

  }

}

 


 

What did we get when using decorators?

  1. No changes are made to the original class
  2. We program to an interface; all phone number objects are saved in IPhoneNumber variables
  3. Every time we need a different phone template than the one implemented in the PhoneNumber class, we create a decorator for it.
  4. To use the new template, we “decorate” the PhoneNumber object by wrapping it with a decorator object