Specification Design Pattern in Automated Testing

Design Patterns
51 Shares
Specification Design Pattern

If you follow my series about Design Patterns in Automated Testing, I explain how you can utilize the power of various design patterns in your tests. In the current publication, I am going to share with you the idea how your automation can benefit from the usage of Specification Design Pattern. It is a little bit different from the previously presented Rules Design Pattern. Its main idea is to separate individual rules from the rules processing logic.

Definition

In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic.

  • Reusability
  • Maintainability
  • Readabilty
  • Easy Testing
  • Loose coupling of business rules from the business objects

UML Class Diagram

Specification Design Pattern Class Diagram

Participants

The classes and objects participating in Specification Design Pattern are:

  • ISpecification– Defines the interface for all specifications.
  • Specification– An abstract class that contains the implementation of the And, Or and Not methods. Only the IsSatisfiedBy varies based on the business rule.
  • AndSpecification– Specification class used for chaining purposes defines the “And” boolean operator.
  • OrSpecification – Defines the “Or” boolean operator.
  • NotSpecification– Defines the “Not” boolean operator.
  • CreditCardSpecification– A concrete specification where the IsSatisfiedBy method is implemented. Holds the concrete business rule.

Specification Design Pattern C# Code

Test’s Test Case

Consider that we have to automate a shopping cart process. During the purchase, we can create orders via wire transfer, credit card or free ones through promotions. Our tests’ workflow is based on a purchase input object that holds all data related to the current purchase e.g. type of purchase and the total price.

public class PurchaseTestInput
{
public bool IsWiretransfer { get; set; }
public bool IsPromotionalPurchase { get; set; }
public string CreditCardNumber { get; set; }
public decimal TotalPrice { get; set; }
}

On the last step of the purchase wizard, there is a page that we are going to call PlaceOrderPage. There are a couple of possible scenarios that can be performed on this page. Complete the purchase via credit card or wire transfer. Enter a promotional code that applies a discount. Complete the order as a free purchase. Usually, the whole order process contains a lot of steps. This means that there are a lot of combinations between the different steps. Sometimes some of them won’t be executed, e.g. the client won’t always activate a promo code. So there won’t be the need to assert the promo code label. However, in general, the core workflow stays the same.
The most obvious solution to this problem will be to build the workflow of methods for every possible test case. Nonetheless, if there is a slight change in the workflow, this will force us to change this order in all tests and possibly introduce regression.
I think a better approach to the problem is to build the workflow in one place e.g. Facade (read more about facades in my post- Improved Facade Design Pattern in Automation Testing v.2.0). Some of the methods might not be executed based on the PurchaseTestInput.

public partial class PlaceOrderPage : BasePage
{
private readonly PurchaseTestInput purchaseTestInput;
public PlaceOrderPage(IWebDriver driver, PurchaseTestInput purchaseTestInput) : base(driver)
{
this.purchaseTestInput = purchaseTestInput;
}
public override string Url
{
get
{
return @”http://www.bing.com/”;
}
}
public void ChoosePaymentMethod()
{
if (!string.IsNullOrEmpty(this.purchaseTestInput.CreditCardNumber)
&& !this.purchaseTestInput.IsWiretransfer
&& !(this.purchaseTestInput.IsPromotionalPurchase && this.purchaseTestInput.TotalPrice < 5)
&& !(this.purchaseTestInput.TotalPrice == 0))
{
this.CreditCard.SendKeys(371449635398431);
this.SecurityNumber.SendKeys(1234);
}
else
{
this.Wiretransfer.SendKeys(pathToFile);
}
}
}

When you call the ChoosePaymentMethod method, runtime it will be decided based on the input object how the order will be completed- via credit card or wire transfer.
However, as you can guess these rules cannot be reused this way. Also, the Single Responsibility Principle is not followed. Similar conditional logic may be needed in various other scenarios like a different type of asserts.
In my opinion, Specification Design Pattern is the perfect fix for this kind of situations.
As you can see from the participants list, the first thing that we need is the ISpecification<TEntity> interface.

public interface ISpecification<TEntity>
{
bool IsSatisfiedBy(TEntity entity);
ISpecification<TEntity> And(ISpecification<TEntity> other);
ISpecification<TEntity> Or(ISpecification<TEntity> other);
ISpecification<TEntity> Not();
}

It defines the methods that will be used to chain the different business rules and the primary method IsSatisfiedBy.
The implementation of the And, Or, Not methods is the same across all the specifications, only the IsSatisfiedBy varies based on the business rule. So, we define the abstract class called Specification that implements the And, Or, Not methods and leave the IsSatisfiedBy method to its child classes to implement by declaring this method as abstract.

public abstract class Specification<TEntity> : ISpecification<TEntity>
{
public abstract bool IsSatisfiedBy(TEntity entity);
public ISpecification<TEntity> And(ISpecification<TEntity> other)
{
return new AndSpecification<TEntity>(this, other);
}
public ISpecification<TEntity> Or(ISpecification<TEntity> other)
{
return new OrSpecification<TEntity>(this, other);
}
public ISpecification<TEntity> Not()
{
return new NotSpecification<TEntity>(this);
}
}

The And, Or and Not methods create and return an AndSpecification, OrSpecification, and NotSpecification object respectively. These classes are used mainly for chaining purposes. The AndSpecification and OrSpecification classes accept two ISpecification parameters, unlike NotSpecification which is needs just one, considering the fact that former ones are binary operators and the later being unary.

AndSpecification

public class AndSpecification<TEntity> : Specification<TEntity>
{
private readonly ISpecification<TEntity> leftSpecification;
private readonly ISpecification<TEntity> rightSpecification;
public AndSpecification(ISpecification<TEntity> leftSpecification, ISpecification<TEntity> rightSpecification)
{
this.leftSpecification = leftSpecification;
this.rightSpecification = rightSpecification;
}
public override bool IsSatisfiedBy(TEntity entity)
{
return this.leftSpecification.IsSatisfiedBy(entity) && this.rightSpecification.IsSatisfiedBy(entity);
}
}

The only job of this class is to implement the IsSatisfiedBy method and chain the specifications with And boolean operator.
OrSpecification

public class OrSpecification<TEntity> : Specification<TEntity>
{
private readonly ISpecification<TEntity> leftSpecification;
private readonly ISpecification<TEntity> rightSpecification;
public OrSpecification(ISpecification<TEntity> leftSpecification, ISpecification<TEntity> rightSpecification)
{
this.leftSpecification = leftSpecification;
this.rightSpecification = rightSpecification;
}
public override bool IsSatisfiedBy(TEntity entity)
{
return this.leftSpecification.IsSatisfiedBy(entity) || this.rightSpecification.IsSatisfiedBy(entity);
}
}

The accepted specifications are chained with Or boolean operator in the IsSatisfiedBy method.
NotSpecification

public class NotSpecification<TEntity> : Specification<TEntity>
{
private readonly ISpecification<TEntity> specification;
public NotSpecification(ISpecification<TEntity> specification)
{
this.specification = specification;
}
public override bool IsSatisfiedBy(TEntity entity)
{
return !this.specification.IsSatisfiedBy(entity);
}
}

Refactor

Refactor PlaceOrderPage to Use Specification Design Pattern

We can move the different purchase-related conditions in a couple of specification classes.

public class CreditCardSpecification : Specification<PurchaseTestInput>
{
private readonly PurchaseTestInput purchaseTestInput;
public CreditCardSpecification(PurchaseTestInput purchaseTestInput)
{
this.purchaseTestInput = purchaseTestInput;
}
public override bool IsSatisfiedBy(PurchaseTestInput entity)
{
return !string.IsNullOrEmpty(this.purchaseTestInput.CreditCardNumber);
}
}

The class inherits the Specification abstract class and implements the IsSatisfiedBy method where the credit card related condition is moved.
Here is one more example regarding the promotional purchases’ conditions.

public class PromotionalPurchaseSpecification : Specification<PurchaseTestInput>
{
private readonly PurchaseTestInput purchaseTestInput;
public PromotionalPurchaseSpecification(PurchaseTestInput purchaseTestInput)
{
this.purchaseTestInput = purchaseTestInput;
}
public override bool IsSatisfiedBy(PurchaseTestInput entity)
{
return this.purchaseTestInput.IsPromotionalPurchase && this.purchaseTestInput.TotalPrice < 5;
}
}

Now if we use the newly created specification, we can refactor the PlaceOrderPage class. It will look like the code below.

public partial class PlaceOrderPage : BasePage
{
private readonly PurchaseTestInput purchaseTestInput;
private readonly PromotionalPurchaseSpecification promotionalPurchaseSpecification;
private readonly CreditCardSpecification creditCardSpecification;
private readonly WiretransferSpecification wiretransferSpecification;
private readonly FreePurchaseSpecification freePurchaseSpecification;
public PlaceOrderPage(IWebDriver driver, PurchaseTestInput purchaseTestInput) : base(driver)
{
this.purchaseTestInput = purchaseTestInput;
this.promotionalPurchaseSpecification = new PromotionalPurchaseSpecification(purchaseTestInput);
this.wiretransferSpecification = new WiretransferSpecification(purchaseTestInput);
this.creditCardSpecification = new CreditCardSpecification(purchaseTestInput);
this.freePurchaseSpecification = new FreePurchaseSpecification();
}
public override string Url
{
get
{
return @”http://www.bing.com/”;
}
}
public void ChoosePaymentMethod()
{
if (this.creditCardSpecification.
And(this.wiretransferSpecification.Not()).
And(this.freePurchaseSpecification.Not()).
And(this.promotionalPurchaseSpecification.Not()).
IsSatisfiedBy(this.purchaseTestInput))
{
this.CreditCard.SendKeys(371449635398431);
this.SecurityNumber.SendKeys(1234);
}
else
{
this.Wiretransfer.SendKeys(pathToFile);
}
}
}

If you need to change how it is determined if the test purchase should be completed via credit card or wire transfer, you can do it only in a single place- specification classes.
The only problem here is that if you need to do similar conditional logic in your static assert classes, you don’t have access to the specifications. I don’t think it is a good idea to expose the specifications through the page itself because of that we can expose special boolean properties.

public partial class PlaceOrderPage : BasePage
{
private readonly PurchaseTestInput purchaseTestInput;
private readonly PromotionalPurchaseSpecification promotionalPurchaseSpecification;
private readonly CreditCardSpecification creditCardSpecification;
private readonly WiretransferSpecification wiretransferSpecification;
private readonly FreePurchaseSpecification freePurchaseSpecification;
public PlaceOrderPage(IWebDriver driver, PurchaseTestInput purchaseTestInput) : base(driver)
{
this.purchaseTestInput = purchaseTestInput;
this.promotionalPurchaseSpecification = new PromotionalPurchaseSpecification(purchaseTestInput);
this.wiretransferSpecification = new WiretransferSpecification(purchaseTestInput);
this.creditCardSpecification = new CreditCardSpecification(purchaseTestInput);
this.freePurchaseSpecification = new FreePurchaseSpecification();
this.IsPromoCodePurchase = this.freePurchaseSpecification.Or(this.promotionalPurchaseSpecification).IsSatisfiedBy(this.purchaseTestInput);
this.IsCreditCardPurchase = this.creditCardSpecification.
And(this.wiretransferSpecification.Not()).
And(this.freePurchaseSpecification.Not()).
And(this.promotionalPurchaseSpecification.Not()).
IsSatisfiedBy(this.purchaseTestInput);
}
public bool IsPromoCodePurchase { get; private set; }
public bool IsCreditCardPurchase { get; private set; }
public override string Url
{
get
{
return @”http://www.bing.com/”;
}
}
public void ChoosePaymentMethod()
{
if (this.IsCreditCardPurchase)
{
this.CreditCard.SendKeys(371449635398431);
this.SecurityNumber.SendKeys(1234);
}
else
{
this.Wiretransfer.SendKeys(pathToFile);
}
}
public void TypePromotionalCode(string promoCode)
{
if (this.IsPromoCodePurchase)
{
this.PromotionalCode.SendKeys(promoCode);
}
}
}

The IsCreditCardPurchase holds the info if the purchase should be completed via credit card. The IsPromoCodePurchase has a similar purpose. Both can be used in the conditions on the page itself, as well as in the static assert extension methods of the page.

public static class PlaceOrderPageAsserter
{
public static void AssertPromoCodeLabel(this PlaceOrderPage page, string promoCode)
{
if (!string.IsNullOrEmpty(promoCode) && page.IsPromoCodePurchase)
{
Assert.AreEqual<string>(page.PromotionalCode.Text, promoCode);
}
}
}

The extension method accepts the page as a parameter, so it has access to previously created boolean properties.

Summary

The Specification Design Pattern can be used to improve the reusability, maintainability, and readability of your tests. Also, it gives you loose coupling of the business rules from the business objects. If needed, the specifications are easy for testing.
In the next article from the series, I am going to show you how to improve the Specification Design Pattern even more. How to use LINQ to configure the concrete specifications without the need for additional classes, create extension methods for the abstract base class and how to decouple the specifications from the pages through the usage of test context objects.

References:


banner