Why do static variables and methods suck?

I believe they are very full at times.

I got this comment on a LinkedIn post for the differences between Selenium testers and Selenium developers.

It is worth providing an answer.

Now, I am not a Java expert, far from it.

But others are.

For example, Simon Stewart.

Yes, that Simon Stewart who created Seleniumn WebDriver.

This is from his blog:

“Singletons? Static Methods? Also No.
Singletons (in the traditional “implemented as a static field in a class” sense, not in the “ideally we’d only have one of these” sense) destroy our ability to have fun and write tests that can run in parallel, slashing our potential productivity. Also, it leads people to start using the Service Locator pattern instead of Dependency Injection, and we take DI as an article of faith (see above), mainly because it facilitates TDD by making collaborators clear, like we (also) said above.”

So, static methods? Also no.

Do you need more reasons against static variables and methods?

They promote code that is not object oriented.

 

Object oriented code is about objects.

 

Every method is used on an object.

 

Since static variables or methods are for the class but not the class’s objects, you are writing code that does not use objects.

 

Have a look at the following articles for more details:

Still not convinced?

Read this topic from Stack Overflow.

 

Still in doubt?

Continue to use them.
Advertisements

How to reduce code duplication using predicates and lambda expressions

Many sites allow their clients to search for information and then browse through the results.

Each result has multiple attributes such as

  • title
  • price
  • status

 

results attributes

 

In test automation projects, it is important to have the ability of filtering the results by different attributes.

For example, filter the results with price greater than 0.

Or filter the results that are available only online.

Or filter the results that have a keyword included in the title.

 

How can this filtering be implemented?

public class Result {

  private String title;
  private int price;
  private String status;

  public Result(String title, int price, String status) {
    this.title = title;
    this.price = price;
    this.status = status;
  }

  public int price() {
    return this.price;
  }

  public String title() {
    return this.title;
  }

  public String status() {
    return this.status;
  }

}

Result class is used for creating an object for each result returned for the user search.

The results filtering happens in the test class:

 

public class TestClass {

List<Result> results;

List<Result> withPriceGreaterThan0(List<Result> list) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.price() > 0)
     newResults.add(r);
  return newResults;
}

List<Result> withPriceEqualTo0(List<Result> list) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.price() == 0)
     newResults.add(r);
  return newResults;
}

List<Result> byStatus(List<Result> list, String status) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.status().toLowerCase().contains(status.toLowerCase()))
     newResults.add(r);
  return newResults;
}

List<Result> byKeyword(List<Result> list, String keyword) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.title().toLowerCase().contains(keyword.toLowerCase()))
     newResults.add(r);
  return newResults;
}

@Before
public void setUp() {
  Result r1 = new Result("Otter Box", 54, "Not sold online");
  Result r5 = new Result("Virgin iPhone", 0, "Not sold online");
  Result r7 = new Result("Apple iPhone", 450, "Not sold online");
  Result r2 = new Result("Rogers iPhone", 130, "Sold out online");
  Result r6 = new Result("SanDisk iXpand", 125, "Sold out online");
  Result r8 = new Result("Koodo iPhone", 280, "Sold out online");
  Result r3 = new Result("Fido iPhone", 400, "Available online only");
  Result r4 = new Result("TELUS iPhone", 0, "Available online");

  results = Arrays.asList(r1, r2, r3, r4, r5, r6, r7, r8);
}

@Test
public void test1() {

  for(Result r: withPriceGreaterThan0(results))
    assertTrue(r.price() > 0);

  for(Result r: withPriceEqualTo0(results))
    assertTrue(r.price() == 0);

  for(Result r: byStatus(results, "AVAILABLE ONLINE"))
    assertTrue(r.status().toLowerCase().contains("available online"));

  for(Result r: byKeyword(results, "IPHONE"))
    assertTrue(r.title().toLowerCase().contains("iphone"));

}

}

The filtering is being done by the following methods:

List<Result> withPriceGreaterThan0(List<Result> list
List<Result> withPriceEqualTo0(List<Result> list)
List<Result> byStatus(List<Result> list, String status
List<Result> byKeyword(List<Result> list, String keyword)

All get a List<Result> parameter, browse the list with a for statement and select the results that match a specific condition.

The code of the 4 filtering methods is pretty much identical with the only difference being the condition that the results are compared against:

r.price() > 0

r.price() == 0

r.status().toLowerCase().contains(status.toLowerCase())

r.title().toLowerCase().contains(keyword.toLowerCase())

 

How can we have 1 filtering method instead of 4?

One of the new features of Java 8 is lambda expressions.

lambda expressions

Lambda expressions and predicates will help us replace the 4 methods with 1.

Lambda expressions allow functionality (code) to be used as a method parameter in addition to objects.

A lambda expression can be assigned to a Predicate object which can be used as value for a method parameter.

If you are not familiar with lambda expressions yet, this is a good introduction.

Our code can be rewritten with predicates and lambda expressions as follows:

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

import org.junit.Before;
import org.junit.Test;

public class TestClass {

List<Result> results;

List<Result> filter(List<Result> list, Predicate<Result> p) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (p.test(r))
      newResults.add(r);
  return newResults;
}

@Before
public void setUp() {
 ..............................
}

@Test
public void test1() {

  Predicate<Result> priceGreaterThan0 = (r) -> r.price() > 0;
  for(Result r: filter(results, priceGreaterThan0))
    assertTrue(r.price() > 0);

  for(Result r: filter(results, (r) -> r.price() == 0))
    assertTrue(r.price() == 0);

  for(Result r: filter(results, (r) -> r.status().equalsIgnoreCase("AVAILABLE ONLINE") == true))
    assertTrue(r.status().toLowerCase().contains("available online"));

  Predicate<Result> keywordInTitle = (r) ->   r.title().toUpperCase().contains("IPHONE") == true;
  for(Result r: filter(results, keywordInTitle))
    assertTrue(r.title().toLowerCase().contains("iphone"));

  for(Result r: filter(results, priceGreaterThan0.and(keywordInTitle))) {
    assertTrue(r.price() > 0);
    assertTrue(r.title().toLowerCase().contains("iphone"));
  }

}

}

In this version of the code, all conditions are expressed as lambda expressions and can be assigned or not to Predicate objects:

Predicate<Result> priceGreaterThan0 = (r) -> r.price() > 0;

Predicate<Result> keywordInTitle = (r) -> r.title().toUpperCase().contains("IPHONE") == true;

(r) -> r.price() == 0))

(r) -> r.status().equalsIgnoreCase("AVAILABLE ONLINE") == true))

Since the conditions are expressed as lambda expressions that can be assigned to a Predicate<Result> object, we can have 1 filtering method only that gets 2 parameters:

  • list of results
  • predicate

 

Can we make the code simpler?

The predicates can be moved to a separate class as follows:

import java.util.function.Predicate;

import Result;

public class Predicates {

  public static Predicate<Result> priceGreaterThan0() {
    return (r) -> r.price() > 0;
  }

  public static Predicate<Result> priceEqualTo0() {
    return (r) -> r.price() == 0;
  }

  public static Predicate<Result> availableOnline() {
    return (r) -> r.status().equalsIgnoreCase("AVAILABLE ONLINE") == true;
  }

  public static Predicate<Result> soldOutOnline() {
    return (r) -> r.status().equalsIgnoreCase("SOLD OUT ONLINE") == true;
  }

  public static Predicate<Result> titleIncludes(String keyword) {
    return (r) -> r.title().toUpperCase().contains(keyword) == true;
  }

}

The test class becomes straightforward when the predicates are in their own class:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

import static org.junit.Assert.assertTrue;

import org.junit.Before;
import org.junit.Test;

import Result;

public class TestClass {

List<Result> results;

List<Result> filter(List<Result> list, Predicate<Result> p) {
   List<Result> newResults = new ArrayList<>();
   for (Result r : list)
     if (p.test(r)) 
       newResults.add(r);
   return newResults;
 }

@Before
public void setUp() {

...........................................

}

@Test
public void test1() {

for(Result r: filter(results, Predicates.priceGreaterThan0()))
  assertTrue(r.price() > 0);

for(Result r: filter(results, Predicates.priceEqualTo0()))
  assertTrue(r.price() == 0);

for(Result r: filter(results, Predicates.availableOnline()))
  assertTrue(r.status().toLowerCase().contains("available online"));

for(Result r: filter(results, Predicates.titleIncludes("iphone")))
  assertTrue(r.title().toLowerCase().contains("iphone"));

for(Result r: filter(results, Predicates.priceGreaterThan0()
.and(Predicates.titleIncludes("iphone")))) {
  assertTrue(r.price() > 0);
  assertTrue(r.title().toLowerCase().contains("iphone"));
}

}

}