The initial versions of our tests framework utilized the facade design pattern. This is the first design that we are going to evaluate through the proposed assessment system. A facade is an object that provides a simplified interface to a larger body of code, such as a class library. It makes the software library easier to use and understand, is more readable, and reduces dependencies on external or other code.
Regions in Facades
There are not any real drawbacks, as it provides a unified interface to a set of interfaces in a subsystem. However, the biggest problem for us was the size of the facade files. They got enormous, like thousands of lines of code. We had to use regions inside to separate the different parts of the code. Regions let you specify a block of code that you can expand or collapse when using the outlining feature of the Visual Studio Code Editor. As depicted in the image, in the Billing facade, four different regions were used for separating the element map properties, the private
fields, and the rest of the methods.
Improved Facade Design Pattern
This is the code of our shopping cart facade responsible for creating purchases. We decided to use the facade design pattern in a little different way. It combines the different pages' methods to complete the wizard of the order. If there is a change in the order of the executed actions, I can edit it only here. It will apply to tests that are using the facade. The different test cases are accomplished through the different parameters passed to the facade's methods. These types of facades contain much less code because most of the logic is held by the pages instead of the facade itself.
public class ShoppingCart
{
private readonly ItemPage itemPage;
private readonly PreviewShoppingCartPage previewShoppingCartPage;
private readonly SignInPage signInPage;
private readonly ShippingAddressPage shippingAddressPage;
private readonly ShippingPaymentPage shippingPaymentPage;
private readonly PlaceOrderPage placeOrderPage;
public ShoppingCart(
ItemPage itemPage,
PreviewShoppingCartPage previewShoppingCartPage,
SignInPage signInPage,
ShippingAddressPage shippingAddressPage,
ShippingPaymentPage shippingPaymentPage,
PlaceOrderPage placeOrderPage)
{
this.itemPage = itemPage;
this.previewShoppingCartPage = previewShoppingCartPage;
this.signInPage = signInPage;
this.shippingAddressPage = shippingAddressPage;
this.shippingPaymentPage = shippingPaymentPage;
this.placeOrderPage = placeOrderPage;
}
public void PurchaseItem(
string itemUrl,
string itemPrice,
ClientLoginInfo clientLoginInfo,
ClientPurchaseInfo clientPurchaseInfo)
{
this.itemPage.Navigate(itemUrl);
this.itemPage.ClickBuyNowButton();
this.previewShoppingCartPage.ClickProceedToCheckoutButton();
this.signInPage.Login(clientLoginInfo.Email, clientLoginInfo.Password);
this.shippingAddressPage.FillShippingInfo(clientPurchaseInfo);
this.shippingAddressPage.ClickDifferentBillingCheckBox(clientPurchaseInfo);
this.shippingAddressPage.ClickContinueButton();
this.shippingPaymentPage.ClickBottomContinueButton();
this.shippingAddressPage.FillBillingInfo(clientPurchaseInfo);
this.shippingAddressPage.ClickContinueButton();
this.shippingPaymentPage.ClickTopContinueButton();
double totalPrice = double.Parse(itemPrice);
this.placeOrderPage.AssertOrderTotalPrice(totalPrice);
}
}
Usage in Tests
This is a sample usage of the facade in tests. First, we need to initialize all required parameters. After that, you simply call the main workflow's method.
[TestMethod]
public void Purchase_ShoppingCartFacade()
{
var itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
var itemPrice = "40.49";
var 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"
});
clientPurchaseInfo.CouponCode = "99PERDIS";
var clientLoginInfo = new ClientLoginInfo()
{
Email = "g3984159@trbvm.com",
Password = "ASDFG_12345"
};
var shoppingCart = container.Resolve<ShoppingCart>();
shoppingCart.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
}
Facade Design Pattern Pros
- Hide complex logic
- Simplify tests' creation
- Workflow's changes- single place
Facade Design Pattern Cons
- Enormous files
- Huge constructors
- Not clear tests' workflow from test's body
- Affect a large number of tests
- Hard to orient- new people
Evaluate Facade Based Tests- Assessment System
Maintainability
public void PurchaseItem(
string itemUrl,
string itemPrice,
ClientLoginInfo clientLoginInfo,
ClientPurchaseInfo clientPurchaseInfo)
{
this.itemPage.Navigate(itemUrl);
this.itemPage.ClickBuyNowButton();
this.previewShoppingCartPage.ClickProceedToCheckoutButton();
this.signInPage.Login(clientLoginInfo.Email, clientLoginInfo.Password);
this.shippingAddressPage.FillShippingInfo(clientPurchaseInfo);
this.shippingAddressPage.ClickDifferentBillingCheckBox(clientPurchaseInfo);
this.shippingAddressPage.ClickContinueButton();
this.shippingPaymentPage.ClickBottomContinueButton();
this.shippingAddressPage.FillBillingInfo(clientPurchaseInfo);
this.shippingAddressPage.ClickContinueButton();
this.shippingPaymentPage.ClickTopContinueButton();
double totalPrice = double.Parse(itemPrice);
this.placeOrderPage.AssertOrderTotalPrice(totalPrice);
}
The maintainability is Very Good (4). The troubleshooting and adding new features to the facades is straightforward. However, the rating is not marked as Excellent because you can easily introduce a regression in the existing tests with small changes in the facade.
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | - |
Code Complexity Index | - |
Usability | - |
Flexibility | - |
Learning Curve | - |
Least Knowledge | - |
Readability
var shoppingCart = container.Resolve<ShoppingCart>();
shoppingCart.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
The readability is evaluated as Poor (2). The tests contain much less code compared to all other solutions. However, as you call only a single method from the facade in the tests, it is not clear to the user what this method is doing under the hood. Further, due to their large sizes, the facades are relatively unreadable and it is not an easy job to find something inside them.
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | 2 |
Code Complexity Index | - |
Usability | - |
Flexibility | - |
Learning Curve | - |
Least Knowledge | - |
Code Complexity Index
The facade classes have a poor (3) index because they are large in size and they depend on lots of other classes such as other facades and lots of pages. On the opposite, the tests' classes are fairly short in size and call only the facade itself.
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | 2 |
Code Complexity Index | 3 |
Usability | - |
Flexibility | - |
Learning Curve | - |
Least Knowledge | - |
Usability
var itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
var itemPrice = "40.49";
var 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"
});
clientPurchaseInfo.CouponCode = "99PERDIS";
var clientLoginInfo = new ClientLoginInfo()
{
Email = "g3984159@trbvm.com",
Password = "ASDFG_12345"
};
The usability is Very Good (4). The writing of new tests is straightforward. The rating is not Excellent just because the user has to initialize the tests context upfront.
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | 2 |
Code Complexity Index | 3 |
Usability | 4 |
Flexibility | - |
Learning Curve | - |
Least Knowledge | - |
Flexibility
public void PurchaseItem(
string itemUrl,
string itemPrice,
ClientLoginInfo clientLoginInfo,
ClientPurchaseInfo clientPurchaseInfo)
{
this.itemPage.Navigate(itemUrl);
this.itemPage.ClickBuyNowButton();
this.previewShoppingCartPage.ClickProceedToCheckoutButton();
this.signInPage.Login(clientLoginInfo.Email, clientLoginInfo.Password);
this.shippingAddressPage.FillShippingInfo(clientPurchaseInfo);
this.shippingAddressPage.ClickDifferentBillingCheckBox(clientPurchaseInfo);
this.shippingAddressPage.ClickContinueButton();
this.shippingPaymentPage.ClickBottomContinueButton();
this.shippingAddressPage.FillBillingInfo(clientPurchaseInfo);
this.shippingAddressPage.ClickContinueButton();
this.shippingPaymentPage.ClickTopContinueButton();
double totalPrice = double.Parse(itemPrice);
this.placeOrderPage.AssertOrderTotalPrice(totalPrice);
}
The flexibility is Very Poor (1). If you change some of the existing workflows, you will affect all existing tests and possibly create regression issues. If you need to create custom workflow you have to add custom public workflow method which makes the already large facade even bigger. You cannot change or customize the already constructed workflows on a test level.
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | 2 |
Code Complexity Index | 3 |
Usability | 4 |
Flexibility | 1 |
Learning Curve | - |
Least Knowledge | - |
Learning Curve
var itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
var itemPrice = "40.49";
var 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"
});
clientPurchaseInfo.CouponCode = "99PERDIS";
var clientLoginInfo = new ClientLoginInfo()
{
Email = "g3984159@trbvm.com",
Password = "ASDFG_12345"
};
var shoppingCart = container.Resolve<ShoppingCart>();
shoppingCart.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
There are two tricky parts with the approach. First, you should know how to initialize the test context correctly. Secondly, if there are multiple public workflow methods, you should be aware which is the most appropriate to call.
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | 2 |
Code Complexity Index | 3 |
Usability | 4 |
Flexibility | 1 |
Learning Curve | 3 |
Least Knowledge | - |
Least Knowledge
var itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
var itemPrice = "40.49";
var 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"
});
clientPurchaseInfo.CouponCode = "99PERDIS";
var clientLoginInfo = new ClientLoginInfo()
{
Email = "g3984159@trbvm.com",
Password = "ASDFG_12345"
};
var shoppingCart = container.Resolve<ShoppingCart>();
shoppingCart.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
The facade has access to the whole test context which is usually enormous in size. Not all methods need all of the information present in the test context. Because of that, the rating is marked as poor (2).
| Facade Based Tests |
---|
Maintainability | 4 |
Readability | 2 |
Code Complexity Index | 3 |
Usability | 4 |
Flexibility | 1 |
Learning Curve | 3 |
Least Knowledge | 2 |
In my next article from the series, I will use the assessment system to evaluate the behavior based tests.
You can watch my conference talk dedicated to the system or download the whole slide deck.
Design & Architecture
The post Assessment System for Tests Architecture Design- Facade Based Tests appeared first on Automate The Planet.
All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement