DEV Community

Ernesto Herrera Salinas
Ernesto Herrera Salinas

Posted on

Domain-Specific Languages for Automated Testing Development

The difference between a good and an excellent product is quality. As an application grows, the effort required to validate its functionality increases. Automated tests save us time and money since they can be repeated repeatedly throughout the development cycle to ensure product quality. Each time a change, fix, or new element gets implemented, we can validate the complete functionality quickly and consistently. Our test cases can execute with various data sets in various environments without requiring additional maintenance as the project grows.

Automated tests are themselves applications whose objective is to validate the software's functionality, so it is necessary to apply the same approach used to create the product. We will use a domain-specific language for tests along with the PageObject pattern to demonstrate how we can design highly effective tests. The examples below focus on validating web applications, but the same principles apply in tests for desktop or mobile applications.

The Page Object pattern

This design pattern allows us to map the elements of the page to an object and the actions we can perform within it, such as adding users, updating product descriptions, and many others. Essentially, this model is a specialized form of the Façade pattern, meaning we replace a poorly descriptive API with one that is easier to understand. Let us look at the following example to illustrate this concept; the code is written with Java using Selenium:

WebElement formDiv = driver.findElement(By.className("form-group"));
assertEquals(formDiv.findElement(By.xpath("//input[3]")).getText(), "Guitar");
Enter fullscreen mode Exit fullscreen mode

In this code snippet, the API is oriented towards HTML elements. Now let's look at the same example using the pattern described above:

assertEquals(WishList.Description, "Guitar");
Enter fullscreen mode Exit fullscreen mode

As we can see, we have an object that models our Wishlist page, which has the product description as a property. Here it is clearer what we are validating, and even more importantly, our model allows us to abstract the specific implementation. In the first example, if a field is added to the application, the description would not be in the third input, and updating our test would require changing all the parts where "//input[3]" appears, but by using a model, we only need to modify the selector in one place, which makes it easier to maintain our tests. In an ideal scenario where both the tests and the application share the same selector dictionary, when the developer modifies the same selector, the tests would be automatically updated. However, this requires close collaboration between the quality team and the developers. But even when we do not have this common element between application and tests, we can see the advantages that this pattern offers us:

  • The model represents the web application screens as a series of objects and encapsulates the characteristics represented by a page.
  • It allows us to generate intelligent and robust tests.
  • It reduces code duplication in tests.
  • It simplifies long-term maintenance.

This design pattern allows us to create a domain-specific language for automated testing development. Quoting Martin Fowler:

"I think the hardest part of software projects, the most common source of project failure, is communicating with the customers and users of that software. By providing a clear and precise language to address domains, a DSL can help improve this communication."

In this case, our domain is testing. So far, we have only created the model of a page, but we can use this same principle to get more out of it and define a DSL (Domain Specific Language) as such. The following example presents a test that uses it:

I Login TestCase.UserName TestCase.Password
And Create TestCase.NewProduct
And Save
Enter fullscreen mode Exit fullscreen mode

The test meets all the requirements we have set, now we will break it down to see how we got to this point. For this particular example, we are using F#, FluentAutomation, and different models, one for the page and another for the data.

FluentAutomation

This framework can be used with Selenium WebDriver or WatiN, which allows us to perform tests with different browsers and devices. We have selected this tool for its flexibility and because it already includes in its API the ability to easily implement the PageObject pattern. In this article, we will not delve into all the features of this framework, for which I recommend the reader to review its documentation. Continuing with our example, we present the page model written in C#:

public class SalesLoginPage: PageObject {
  public SalesLoginPage(FluentTest test): base(test) {
    Url = string.Format("{0}/{1}", TestCase.Environment, "/sales/login");
    At = () => ;
    I.Expect.Exists(SalesLoginElements.UserNameInput);
    I.Expect.Exists(SalesLoginElements.PasswordInput);
  }
  public SalesHomePage login(string userName, string password) {
    I.Enter(userName).In(SalesLoginElements.UserNameInput);
    I.Enter(password).In(SalesLoginElements.PasswordInput);
    I.Click(SalesLoginElements.LoginButton);
    return Switch < SalesHomePage > ();
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's analyse this code a bit before moving on. First, let's talk about the TestCase object, which is static, however, the values of its properties are read from our data source. For this example, we use an XML file, but another different source could be used (database, JSON, CSV, etc.), which allows us to run tests in different environments (local, test instance, pre-production) or different users.

The SalesLoginElements object is a resource (*.resx file) in which we keep the list of selectors, as we mentioned before. This list allows us to facilitate the maintenance of tests when there are changes on the page, and it can even be shared with the web application.

SalesLoginPage and SalesHomePage are models that inherit from PageObject. Here, we have used the convention of naming the actions that we can perform on the page with lowercase letters, as we can see in the original test, and it is only a nomenclature to differentiate these actions from the functions of the DSL (I, And, etc.).

Writing tests in F

Now that we have our models, dictionaries, and data sources, we only need to write the test suites. To achieve this, we opted for a functional language since it allows us to write expressive code that we can execute interactively. F# provides the syntax that allows us to write tests as specifications. Just like some BDD (behaviour-driven development) tools, these tests are readable to more people, in addition to developers and quality engineers.

The project with complete source code for this article is available on our GitHub: https://github.com/ScioMx


Conclusion

As we have seen in the examples, creating automated tests that serve as a tool for quality control, using the PageObject pattern and a specific language for tests, is a simple task.

It is very important to mention that even when we use other languages, frameworks, or tools different from the examples presented in this article, it is possible to achieve the same result using the key concepts. For this, I would like to mention some frameworks/tools that allow us to define DSLs: Coulda (Ruby), Jasmine (JS), PyParsing (Python), Apache Camel (Java), or even Parrot.

Top comments (0)