Three test automation patterns for C#

Whether you like it or not, test automation also entails software development. So, the usage of standards like developing unit tests, executing code reviews, and applying design patterns comes with the job!

If you use design patterns you usually increase the quality and readability of your code. And developers can more easily recognize them. However, using design patterns is not necessary! Use it situational, if it for instance is not useful, adds too much complexity or the project lacks maturity, do not use a pattern.

In this blog I will describe three patterns that can be useful for test automation:

  • Factory pattern
  • Command pattern
  • Page object model

Factory-pattern

Just imagine, you have written 1,000 test cases with Selenium and your team decided to make the application available on mobile devices. I hope you have given the structure of your framework a lot of thought. Because if you haven’t, you can rewrite those 1,000 testcases in a new tool. That’s going to hurt! Especially if you realize that this problem can be avoided (relatively) easily if you give the structure of your framework some thought. The solution? Define your test data and test execution in separate layers. The factory pattern can be helpful. Below three separate layers of test automation.

 

Testcases in DSTL

The first thing to do is to make sure your test cases are written in a Domain Specific Test language (DSTL). It is a language that you setup with your team to define specific actions, for instance a logon action or a menu option with your own naming convention. The logon action consists of multiple sub-actions but is grouped under one specific name to keep the test cases easier to read.

A test case written in DSTL could look like this:

 

 

Abstract definition of keywords

For a second layer you would want to define an abstract handling of your keywords. There should not be any direct reference with your test tool.

In this layer the factory pattern is particularly useful. A factory pattern makes a noticeably clear distinction between factory class and product class. The pattern encapsulates the creation of objects. The factory also determines how a specific product object must be created. A different implementation of the factory class can result in the delivery of a different object. The example below shows how the factory is determined by the test tool that has been used, Selenium. Should you want to use another test tool, you only need to create a new factory.

 

Bron: https://nl.wikipedia.org/wiki/Factory_(ontwerppatroon)

Testcase

This shows the creation of an IAgent object, that in this case becomes a Selenium agent. In our example the IAgent is the factory. When we do a function call on this IAgent interface, the interface already knows which test tool to use. Like this you can integrate an unlimited number of test tools in your framework. The only thing you need to do is implement the generic functions of the IAgent interface.

KeyWord-class

As you can see here, there is only an abstract definition of what this keyword should do. We ask the factory to create a new control, which we will need later when searching for elements. This is done in the CreateUiControl(); method. The agent knows which type of control to create it, because we defined in the previous step that the IAgent is a Selenium agent. The same thing happens with the IAgentAction. The CreateUiControl() function then looks like this:

 

Selenium Agent class

This pattern can help you differentiate between your generic features and those that target your testing tool. As a test engineer you want to be independent from your test tool and by applying this pattern you only must adjust one of the three layers when there are changes in the application.

Command-pattern

It should be possible to run test cases without testers. It is called test automation for a reason. It is therefore essential to generate a good log, so that you can see what might go wrong. In addition, a decent log with screenshots and readable test steps is helpful to your developers when encountering findings. This way your developers can easily replicate the finding step by step, without the help of a tester.

If you spend too much time debugging the test cases that go wrong, you (probably) have a logging problem. You need to always know what has been done with which action. My own rule of thumb for this is that if I am debugging for more than fifteen minutes, there is a fundamental logging problem. One pattern that can help you with such a log is the command pattern.

The idea behind this pattern is that you define multiple commands or actions as separate objects. In our example, a command can be a click action or edit-field action. These commands are separate from the invoker class. This invoker asks a command to execute itself via the execute function in the command. In the invoker class you could possibly pass multiple parameters to a command. A command called via the execute function always first goes to an abstract class or interface, the concrete command. In the concrete-command class, repetitive steps required by each command can be performed. For example, consider writing a log. Finally, the command may call a receiver. The receiver knows how to execute the command. In our example, we are going to use a retry controller here.

 

Bron: https://en.wikipedia.org/wiki/Command_pattern

This image is an abstract representation of what this pattern looks like.

The usual structure of a command-pattern: Client -> Invoker -> Concrete command -> Command -> Receiver.

The structure used in this article: Client -> Invoker -> Receiver -> Concrete command -> Command.

The structure is different from the original format. This is done to make it a bit more readable and to show that deviations can easily occur when applying design patterns. It’s a bonus to use the receiver in this pattern. So don’t worry if you don’t need it. It just shows that you need to define command so that it can be handled by another object. In our example, we use a retry controller for this.

This pattern is often used when there is a need to execute commands with a delay. Think, for example, of queuing jobs or executing jobs in parallel. In the case of test automation, we want to have these commands executed for a certain time via a retry controller. You can try the command via the receiver until a command is successful. This way you avoid any network issues.

To give you an example of this structure, let me show you some code samples, returning this pattern with use cases for test automation.

  • Command (AgentAction): This is an abstract class that every ConcreteCommand inherits from.
  • ConcreteCommand (AgentClick): The specific commands where the actions happen.
  • Client – MSTestvs2 (TestRunner): The test runner that runs the test cases, in our example it is MSTest (not mentioned in the example).
  • Invoker (IKeyWord): Defines which AgentAction to invoke.
  • Receiver (IAgent): Takes care of the execution of the commands.

Invoker

The Invoker class is a class where the data is defined, but nothing concrete is done yet.

In the example there are two objects defined: IUiControl and IAgentAction. The IUiControl is a description of what element we need for the action. In the example we are looking for an input element with a certain ButtonId. In addition, we create a new click action via a factory pattern. This is only the definition, not the actual implementation.

Finally, here we trigger the ExecuteCommand() of the IAgent class. Normally you have the receiver call the execute() of a command. In our case we trigger the ExecuterCommand() of the receiver, which only then executes the command. With this example you see that design patterns only need to be used as a template and that you can deviate from them where necessary, provided the layer structure remains the same.

Receiver

The receiver class has the responsibility to ensure that the command is executed correctly. This does not contain a direct reference to the command itself. In our case, we created a retry controller here. It tries to execute a command in a try/catch function until it is successful.

This example shows a while loop trying to call the action.ExecuteAction for twenty-five seconds. If this action is successful, the loop will stop. You can adjust these maximum waiting times per test. You can see that this layer is only responsible for the course of the output, and not for the actual execution. This emphasizes the essence of that pattern, as it clearly distinguishes between how the command is executed and the command itself.

The thread.sleep() is a very minimal sleep needed to keep your test tool from running too fast. You often hear about this as bad practice. I (kind of) agree since you are blocking the CPU altogether. But as a test automation engineer you should always opt for stable test cases. Your test cases should pass 100% of the time and if a thread.sleep function is needed for this then it’s no problem to use it. The tests usually run at night, leaving plenty of time for testing.

A function that waits for the element to load will work in most cases, but very occasionally it won’t because it runs too fast. You can see this function in our example in the abstract class command.

Command

It is important that this is an interface or abstract class. You are still not performing the action here either. You can, however, use logic here, which is generic for every action. In my example, I want each action to take these steps:

  • You must wait until the elements are visible and clickable.
  • A red highlight must be drawn around an element.
  • A screenshot must be taken.

Here you can use even more logic, such as resetting the DOM at IFrames or switching tabs.

AgentAction is an abstract class that inherits from an interface. Finally, you call the Execute() in this method. You define this Execute in the ConcreteClasses.

Example in red the elements

Concrete-class

After all the abstract layers we finally arrived at the actual call. The concrete class has as parent the abstract class AgentAction. He has yet to define the Execute() function.

With this pattern you can include logs at any location that is essential for your UI test automation. Because you define the concrete actions separately, you avoid writing code twice.

Page-objectmodel

The page object model is an absolute must-have for your framework. This pattern not only ensures more readable test cases, but also less maintenance. A page object model is a pattern where a translation is made between the functional name and the technical name of elements in an application. For example, the functional name becomes: “login name” and the technical name becomes: “User_Login”. This pattern keeps the test cases and the locations of the elements separate.

Advantages:

  1. readable test cases
  2. maintainability of large numbers of test cases
  3. can help colleagues with writing without technical knowledge
  4. clearly separated structure between test cases and locations of elements

Below is an example of one of the 3,000+ pages of our application. This is automatically generated as a C# class. The awkward name, Scherm_CLI_CLI_C1, of the page is precisely one of the reasons why you want to use a page object model.

 

If you reference one of the elements in a test case, you do this via the functional name. After that, the page object model is checked to see what the technical name is. If you have an application without proper technical names, you can create a page object model that also describes this way of searching. The testability of an application is not sufficient in such a situation and as a QA engineer you have the right to have this adjusted by your developers. Read this blog for more information about this: https://www.centric.eu/NL/Default/Craft/Blogs/2017/06/28/Als-tester-opkomen-voor-de-testability-hoe-dan.

The functional name is legible and easier to understand. This allows you as a team to have several people help write test scripts.

A page object model should be a read-only collection that can be read independently of a tool. You also prefer to generate a page object model, since it is impossible to keep up with the work of all developers in a large application.

A link with Selenium By() is not recommended, as this creates a dependency on Selenium. You can, however, use a custom by class. In C# use a collection of IEnumerable rather than a list<t>. The performance of the IEnumerable is, if you use it correctly, faster than that of the list. The IEnumerable also ensures that you cannot edit objects in the collection. This prevents unexpected problems because another test script has changed the page object model.

To sum-up

These are examples of how to add design patterns to test automation. They serve as a description, not as a holy grail. In some cases, you can deviate a little from it to get what you want.

Thinking about the structure of your framework and using design patterns can save you months in the future when changes occur.

A design pattern is a generic structure that serves as a template to solve common software problems and as a description of a solution for known software problems.

Our latest articles

Want Craft updates sent straight to your inbox?

By clicking send you'll receive occasional emails from Craft. You always have the choice to unsubscribe within every email you receive.

Mail

Newsletter

Want CRAFT updates sent straight to your inbox?