Click here to Skip to main content
13,898,462 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

4.1K views
3 bookmarked
Posted 24 Jan 2016
Licenced Ms-PL

Specification Design Pattern in Automated Testing

, 24 Jan 2016
Rate this:
Please Sign up or sign in to vote.
A detailed overview of how to utilize the Specification Design Pattern in automated tests to segregate business rules based on Single responsibility principle. The post Specification Design Pattern in Automated Testing appeared first on Automate The Planet.

Introduction

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.

Specification Design Pattern

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
  • Readability
  • 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 - <a href="http://automatetheplanet.com/improved-facade-design-pattern/">Improved Facade Design Pattern in Automation Testing v.2.0</a>). 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 <a href="http://www.oodesign.com/single-responsibility-principle.html">Single Responsibility Principle</a> 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 information 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.

So Far in the "Design Patterns in Automated Testing" Series

  1. Page Object Pattern
  2. Advanced Page Object Pattern
  3. Facade Design Pattern
  4. Singleton Design Pattern
  5. Fluent Page Object Pattern
  6. IoC Container and Page Objects
  7. Strategy Design Pattern
  8. Advanced Strategy Design Pattern
  9. Observer Design Pattern
  10. Observer Design Pattern via Events and Delegates
  11. Observer Design Pattern via IObservable and IObserver
  12. Decorator Design Pattern- Mixing Strategies
  13. Page Objects That Make Code More Maintainable
  14. Improved Facade Design Pattern in Automation Testing v.2.0
  15. Rules Design Pattern
  16. Specification Design Pattern
  17. Advanced Specification Design Pattern

 

If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!

Source Code

References

The post- Specification Design Pattern in Automated Testing appeared first on Automate The Planet.

All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Anton Angelov
CEO Automate The Planet
Bulgaria Bulgaria
Anton Angelov is an IT Consultant and Quality Assurance Architect at Innovative Lab. He is passionate about automation testing and designing test harness and tools, having the best industry development practices in mind. In addition, he is an active blogger and the founder of Automate The Planet. He strives to make the site one of the leading authorities in Automation Testing by presenting compelling articles, inspiring ardent discussions amongst the community. He is also one of the most-rated-answer authors of questions about Test Automation Frameworks (WebDriver) on Stack Overflow.

You may also be interested in...

Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190306.1 | Last Updated 24 Jan 2016
Article Copyright 2016 by Anton Angelov
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid