This article describes an approach to test automation inspired by a well known development design pattern; that being Model View Controller and describes how it can be adapted to make your automation code more stable and maintainable.
What is Model View Controller (MVC)?
Model View Controller or MVC is a design pattern that encourages you to organise your code in a way that is easy to understand and manage. Each layer of code handles only one specific task in one specific area. The biggest benefit is the separation of concerns which makes the application a lot easier to manage and maintain. This separation also enables developers to specialize so that they write code faster and allows you to develop the layers in parallel.
MVC is a design pattern separating:
- data (MODEL)
- view (VIEW)
- logic (CONTROLLER)
Into different layers
The MODEL handles data. It’s main purpose is to provide data objects that make sense from a business perspective to the CONTROLLER and store data it receives
from the CONTROLLER into the data storage systems.Its purpose is storing and providing data, making sure you don’t have orphan objects, and making sure our data makes sense from a business logic perspective.
Having only one layer handling data brings both great reusability and also less effort in maintenance. If the data source changes, the only layer you need to change is the MODEL.
The VIEW is the layer that handles user interaction. Its main job is making sure data is presented to the user in a readable and user friendly way. The other job is submitting the user’s actions to the Controller along with the data the user provides and the application context. Having one layer that handles user interaction allows you to change the way the user views data with ease without affecting the other layers.
The CONTROLLER links the View and the Model and provides the logic of the application, controls the flow, the way your application is presented to the user. This is the layer where we decide how we manage our interaction with the user, what data we present and when, and where the user should be redirected after each action.
How can we use it (MUT):
In order to take advantage of the benefits MVC provides you need to implement this clear separation of concerns in your test framework as well.
MUT is a test design pattern that separates:
- data (MODEL)
- UI interaction (UI-DRIVER)
- TESTS
into different layers.
The MODEL has the same function as in the MVC, it provides complete business objects to the TEST layer so you can have reliable and maintainable data to test against.
This layer contains only functionality to read and update test data. The data provided by the model can range from usernames and passwords to error messages localized in specific languages, all the data needed by our tests.
The UI-DRIVER handles all interactions with your application interface. It’s the equivalent of the VIEW from MVC. In this layer you have all the object locators and functionality that drives the application UI. Assuming you are automating a web application using Java and Webdriver, in this context, the UI-DRIVER layer contains the Page objects. This layer handles all the logic involved in interacting with the application and will provide data to the TEST layer so we can perform our tests.
The TEST layer handles all application logic. It’s the equivalent of the CONTROLLER from MVC. This layer is only responsible for getting the application in the correct state and asserting that the expected outcome is present. It gets data from the MODEL, drives the application using the UI-DRIVER layer and asserts that correct messages and values appear in the UI.
Example of MUT:
Let’s assume we have a web application that requires login and we are trying to see if after a successful login the user’s display name appears in the page header.
For this we need to have:
- the MODEL providing us a User object that contains username, password and display name,
- the UI-DRIVER controlling the interaction with the application
- the TEST layer bringing these together to form a scenario that makes sense and allows us to assert that the display name actually appears in the page header.
The MODEL layer contains:
<strong><span style="color: #333399;">public class User {</span></strong><span style="color: #333399;"> String username;</span><span style="color: #333399;"> String password;</span><span style="color: #333399;"> String email;</span><span style="color: #333399;"> String displayName;</span><span style="color: #333399;"> String languageSetting;</span><span style="color: #333399;"> public String getUsername() {</span><span style="color: #333399;"> return username;</span><span style="color: #333399;"> }</span> <span style="color: #333399;"> public void setUsername(String username) {</span><span style="color: #333399;"> this.username = username;</span><span style="color: #333399;"> }</span> <span style="color: #333399;">...</span><strong><span style="color: #333399;">public class Model {</span></strong><span style="color: #333399;"> public User getUser(){</span><span style="color: #333399;"> return readUserFromSource();</span><span style="color: #333399;"> } ...</span><strong><span style="color: #333399;">public class LoginPage {</span></strong>
The UI_DRIVER layer contains:
<span style="color: #333399;"> private WebDriver driver; </span><span style="color: #333399;"> By usernameInput = By.id("user");</span><span style="color: #333399;"> By passwordInput = By.id("pass");</span><span style="color: #333399;"> By submitBtn = By.id("submit");</span><span style="color: #333399;"> By displayNameLnk = By.id("userDisplayName");</span><span style="color: #333399;"> public void login(User user){</span><span style="color: #333399;"> driver.findElement(usernameInput).sendKeys(user.getUsername());</span><span style="color: #333399;"> driver.findElement(passwordInput).sendKeys(user.getPassword());</span><span style="color: #333399;"> driver.findElement(submitBtn).click();</span><span style="color: #333399;"> }</span><span style="color: #333399;"> public String getDisplayName(){</span><span style="color: #333399;"> return driver.findElement(displayNameLnk).getText();</span><span style="color: #333399;"> } ...</span><strong><span style="color: #333399;">public class LoginTest {</span></strong><span style="color: #333399;"> <a class='bp-suggestions-mention' href='https://huddle.eurostarsoftwaretesting.com/members/test/' rel='nofollow'>@Test</a></span><span style="color: #333399;"> public void TC_validLogin() {</span>
The TEST layer:
<span style="color: #333399;"> SoftAssert softAssert = new SoftAssert(); </span><span style="color: #333399;"> User user = new Model().getUser();</span><span style="color: #333399;"> LoginPage page = new LoginPage();</span><span style="color: #333399;"> page.login(user);</span><span style="color: #333399;"> softAssert.assertEquals(page.getDisplayName(), user.getDisplayName());</span><span style="color: #333399;"> softAssert.assertAll(); } ...</span>LoginPage page = new LoginPage();
page.login(user);
softAssert.assertEquals(page.getDisplayName(), user.getDisplayName());
softAssert.assertAll(); } ...
This enables you to change the structure of the User object, as long as we keep username, password and displayName fields without affecting this test at all. Also another great advantage is that you can change the data source and still not affect the test. You only change one layer leaving the others unaffected. The same applies to the UI-DRIVER layer, if changes in page structure or login flow happen you only need to change the page object and the tests still function. If in the page header you decide to show the username instead of the display name, the only change required is at the test level where we do the assertion.
Wrapping up:
Some disadvantages of MUT are:
- it introduces a bit more complexity thus initially slowing the automation effort
- it requires more senior developers
- it requires you to be organized and maintain the structure
Having this layered structure in your test automation framework allows you to stay very flexible when it comes to changes in application, infrastructure and view. Traditionally tests contain a lot of hard-coded data making changes quite costly and tests rigid. This approach allows you to pull the data out of the tests so you don’t have it duplicated in each test and also allows you to replace it in time.
MUT allows you to have low maintenance cost and a lot of reusable code.
The MUT pattern is especially beneficial for big teams, it allows you to work on layers in parallel and have specialized resources on each layer thus speeding up the development.