Introduction
In my last article, “Strategy Design Pattern“, I explained the benefits of the application of Strategy Design Pattern in your automation tests. Some of the advantages are more maintainable code, encapsulated algorithm logic, easily interchangeable algorithms and less complicated code.
In this part of the series, I’m going to extend my ideas about the application of the Strategy Design Pattern in automation tests. In my previous examples, I presented to you code samples where the tests used only one strategy at a time. Here, I’m going to show you how to apply the pattern so that you will be able to use multiple strategies at once. Another advanced usage that I want to demonstrate is the use of the Strategy Design Pattern, to validate the tests prerequisites.
Advanced Strategy Design Pattern C# Code
Test’s Test Case
The test case of the examples is going to be the same as of the previous article. The primary goal is going to be to purchase different items from Amazon. Also, the prices on the last step of the buying process should be validated- taxes, shipping costs, gift wrapping expenses, etc.
- Navigate to Item’s Page
- Click Proceed to Checkout
- Login with an existing Client
- Fill Shipping Info
- Choose a shipping speed
- Select a payment method
- Validate the Order Summary
The difference between this article’s examples and the previous ones is going to be that there is a need sometimes to mix multiple strategies in one test. For instance, buy an Amazon item with different billing and shipping information and so paying VAT and Sales taxes in a single purchase. Or even add a gift wrap to the same shopping cart. In my last post’s examples, these operations have been isolated in different strategy classes. The primary goal of today’s refactoring is going to be to extend the code to be able to operate with multiple strategy classes at once.
I have slightly modified the strategy interface with the addition of a new validation method.
public interface IOrderPurchaseStrategy
{
void ValidateOrderSummary(string itemPrice, ClientPurchaseInfo clientPurchaseInfo);
void ValidateClientPurchaseInfo(ClientPurchaseInfo clientPurchaseInfo);
}
This method is going to be used to validate the test prerequisite data passed to the pattern’s class. In SalesTaxOrderPurchaseStrategy
, if the shipping country is not United States, an ArgumentException
is thrown because the other methods of the class won’t be meaningful. The sales tax is applicable only for US.
public class SalesTaxOrderPurchaseStrategy : IOrderPurchaseStrategy
{
public SalesTaxOrderPurchaseStrategy()
{
this.SalesTaxCalculationService = new SalesTaxCalculationService();
}
public SalesTaxCalculationService SalesTaxCalculationService { get; set; }
public void ValidateOrderSummary(string itemsPrice, ClientPurchaseInfo clientPurchaseInfo)
{
States currentState = (States)Enum.Parse(typeof(States), clientPurchaseInfo.ShippingInfo.State);
decimal currentItemPrice = decimal.Parse(itemsPrice);
decimal salesTax = this.SalesTaxCalculationService.Calculate
(currentItemPrice, currentState, clientPurchaseInfo.ShippingInfo.Zip);
PlaceOrderPage.Instance.Validate().EstimatedTaxPrice(salesTax.ToString());
}
public void ValidateClientPurchaseInfo(ClientPurchaseInfo clientPurchaseInfo)
{
if (!clientPurchaseInfo.ShippingInfo.Country.Equals("United States"))
{
throw new ArgumentException("If the NoTaxesOrderPurchaseStrategy is used,
the country should be set to United States because otherwise
no sales tax is going to be applied.");
}
}
}
The same logic is implemented in the VatTaxOrderPurchaseStrategy
, GiftOrderPurchase
strategies. The only addition to these classes is the ValidateClientPurchaseInfo
method. It can be used to validate that your strategies are utilized in the correct manner. There are a lot of companies where quality assurance automation engineers write the core framework (strategies, context classes) while the tests are written by less technical people. So such preventative measures can be considered in cases like that.
Reconstruct Text Strategies Context
First Version Basic Strategy Design Pattern Applied
public class PurchaseContext
{
private readonly IOrderValidationStrategy orderValidationStrategy;
public PurchaseContext(IOrderValidationStrategy orderValidationStrategy)
{
this.orderValidationStrategy = orderValidationStrategy;
}
public void PurchaseItem(string itemUrl, string itemPrice,
ClientLoginInfo clientLoginInfo, ClientPurchaseInfo clientPurchaseInfo)
{
ItemPage.Instance.Navigate(itemUrl);
ItemPage.Instance.ClickBuyNowButton();
PreviewShoppingCartPage.Instance.ClickProceedToCheckoutButton();
SignInPage.Instance.Login(clientLoginInfo.Email, clientLoginInfo.Password);
ShippingAddressPage.Instance.FillShippingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickBottomContinueButton();
ShippingPaymentPage.Instance.ClickTopContinueButton();
this.orderValidationStrategy.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
}
By the way, during my research for the "Design Patterns in Automation Testing" series, I always first read about the presented pattern in several books. One of them that you might want to check is "Head First Design Patterns" by Eric Freeman. The author uses a very unique methodology for presenting the material that I haven't found anywhere else. Probably most of you will like it. For the more hardcore fans that might find the book too easy, I recommend the bible of the design patterns-"Design Patterns- Elements of Reusable Object-Oriented Software". It will change your way of thinking about object-oriented design.
Improved Version Advanced Strategy Design Pattern Applied
public class PurchaseContext
{
private readonly IOrderPurchaseStrategy[] orderpurchaseStrategies;
public PurchaseContext(params IOrderPurchaseStrategy[] orderpurchaseStrategies)
{
this.orderpurchaseStrategies = orderpurchaseStrategies;
}
public void PurchaseItem(string itemUrl, string itemPrice,
ClientLoginInfo clientLoginInfo, ClientPurchaseInfo clientPurchaseInfo)
{
this.ValidateClientPurchaseInfo(clientPurchaseInfo);
ItemPage.Instance.Navigate(itemUrl);
ItemPage.Instance.ClickBuyNowButton();
PreviewShoppingCartPage.Instance.ClickProceedToCheckoutButton();
SignInPage.Instance.Login(clientLoginInfo.Email, clientLoginInfo.Password);
ShippingAddressPage.Instance.FillShippingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickDifferentBillingCheckBox(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickBottomContinueButton();
ShippingAddressPage.Instance.FillBillingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickTopContinueButton();
this.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
public void ValidateClientPurchaseInfo(ClientPurchaseInfo clientPurchaseInfo)
{
foreach (var currentStrategy in orderpurchaseStrategies)
{
currentStrategy.ValidateClientPurchaseInfo(clientPurchaseInfo);
}
}
public void ValidateOrderSummary(string itemPrice, ClientPurchaseInfo clientPurchaseInfo)
{
foreach (var currentStrategy in orderpurchaseStrategies)
{
currentStrategy.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
}
}
There are a couple of significant changes in the above code compared to the first version. The most prominent one is that the strategies instances are now stored in an array.
An unspecified count of strategies can be passed to the constructor of the class as a result of the usage of the params
parameter.
public PurchaseContext(params IOrderPurchaseStrategy[] orderpurchaseStrategies)
{
this.orderpurchaseStrategies = orderpurchaseStrategies;
}
Two new methods are added, where for each registered strategy, a particular method is executed. If you pass two strategies, their two methods are going to be performed successively.
The ValidateClientPurchaseInfo
is called for each strategy so that if there are any misconfigured data, it is going to throw an ArgumentException
.
Advanced Strategy Design Pattern- Usage in Tests
The usage of the advanced strategy design pattern in tests stays as smooth as before but now you can use multiple strategies combined.
First Version Basic Strategy Pattern Applied
[TestClass]
public class AmazonPurchase_PurchaseStrategy_Tests
{
[TestInitialize]
public void SetupTest()
{
Driver.StartBrowser();
}
[TestCleanup]
public void TeardownTest()
{
Driver.StopBrowser();
}
[TestMethod]
public void Purchase_SeleniumTestingToolsCookbook()
{
string itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
string itemPrice = "40.49";
ClientPurchaseInfo clientPurchaseInfo = new ClientPurchaseInfo(
new ClientAddressInfo()
{
FullName = "John Smith",
Country = "United States",
Address1 = "950 Avenue of the Americas",
State = "New York",
City = "New York City",
Zip = "10001-2121",
Phone = "00164644885569"
})
{
GiftWrapping = Enums.GiftWrappingStyles.None
};
ClientLoginInfo clientLoginInfo = new ClientLoginInfo()
{
Email = "g3984159@trbvm.com",
Password = "ASDFG_12345"
};
new PurchaseContext(new SalesTaxOrderValidationStrategy()).PurchaseItem
(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
}
}
In the first version, it has been possible to pass only a single instance of the IOrderValidationStrategy
.
Improved Version Advanced Strategy Design Pattern Applied
[TestClass]
public class AmazonPurchase_AdvancedPurchaseStrategy_Tests
{
[TestInitialize]
public void SetupTest()
{
Driver.StartBrowser();
}
[TestCleanup]
public void TeardownTest()
{
Driver.StopBrowser();
}
[TestMethod]
public void Purchase_SeleniumTestingToolsCookbook()
{
string itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
string itemPrice = "40.49";
var shippingInfo = new ClientAddressInfo()
{
FullName = "John Smith",
Country = "United States",
Address1 = "950 Avenue of the Americas",
State = "New York",
City = "New York City",
Zip = "10001-2121",
Phone = "00164644885569"
};
var billingInfo = new ClientAddressInfo()
{
FullName = "Anton Angelov",
Country = "Bulgaria",
Address1 = "950 Avenue of the Americas",
City = "Sofia",
Zip = "1672",
Phone = "0894464647"
};
ClientPurchaseInfo clientPurchaseInfo = new ClientPurchaseInfo(billingInfo, shippingInfo)
{
GiftWrapping = Enums.GiftWrappingStyles.Fancy
};
ClientLoginInfo clientLoginInfo = new ClientLoginInfo()
{
Email = "g3984159@trbvm.com",
Password = "ASDFG_12345"
};
new PurchaseContext(new SalesTaxOrderPurchaseStrategy(),
new VatTaxOrderPurchaseStrategy(), new GiftOrderPurchaseStrategy())
.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
}
}
Here you are able to mix multiple strategies - SalesTaxOrderPurchaseStrategy
, VatTaxOrderPurcahseStrategy
, GiftOrderPurchaseStrategy
. If you have to add new strategies to your logic, it is going to be easy to plug them in the existing design. Also, you won’t have to modify the Context
class because of the usage of the IOrderPurchaseStategy
array and the params
parameter.
As a consequence of these actions, your tests’ architecture will follow the Open Close Principle, which states that the code should be open for extension, but closed for modification.
So Far in the "Design Patterns in Automated Testing" Series
- Page Object Pattern
- Advanced Page Object Pattern
- Facade Design Pattern
- Singleton Design Pattern
- Fluent Page Object Pattern
- IoC Container and Page Objects
- Strategy Design Pattern
- Advanced Strategy Design Pattern
- Observer Design Pattern
- Observer Design Pattern via Events and Delegates
- Observer Design Pattern via IObservable and IObserver
- Decorator Design Pattern- Mixing Strategies
- Page Objects That Make Code More Maintainable
- Improved Facade Design Pattern in Automation Testing v.2.0
- Rules Design Pattern
- Specification Design Pattern
- Advanced Specification Design Pattern
If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!
Source Code
References
All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement
CodeProject
The post Advanced Strategy Design Pattern in Automation Testing appeared first on Automate The Planet.