Common Anti-patterns in automation coupled with BDT: Part 1

Atmaram Naik
Technogise
Published in
6 min readMay 26, 2020

Writing automation code in Behaviour Driven Development (BDD) style gives lots of advantages when done correctly. Through my code review experiences with various colleagues and through my own mistakes, I have observed some common anti-patterns in most of the test suites. Let’s discuss them one by one.

Misunderstanding UI actions as behavioral steps

Consider a login page in which when you enter the correct username, correct password, and click on the login button; then you land on Home Page. We can represent this interaction or Scenario in Gherkin syntax as:

GIVEN I am on Login Page

WHEN I enter username “atmnk” and password “correct” and try to log in

THEN I see the Landing page

One can also write the same scenario in more granular steps as below:

GIVEN I am on Login Page

WHEN I enter username “atmnk”

AND I enter password “correct”

AND I click on Login Button

THEN I see Landing page

The core advantage of BDD is in understanding the desired behavior of the application in ubiquitous language that all understand.

In the second example above, the steps [I enter username “atmnk”], [I enter password “correct”] and [I click on Login Button] are more like UI actions that you are performing on the application; yet all together they combine to form a simple behavioral step of ‘trying to log in’.

Now, it is perfectly OK to write your step definitions in more refined/granular action steps. This could help you in reusing smaller steps in other variations on scenarios.

For example, suppose in the same application, I want to verify whether an error message is displayed as soon as I enter an invalid value in one of the fields, then obviously I would want to create the small step of ‘entering or changing field value’ and I can reuse it well in a larger step.

However, most of the time, it is useful to make your scenarios shorter and more readable by combining action steps to form more meaningful behavioral steps in a product context.

Please note, the First example mentioned as well is not BDD done well, We will come to it in the next part of the blog where I will talk about anti-pattern “Fitting BDD to Test Framework” or “Gherkin as a test script” and we will improve it.

Lengthy Scenarios

While the practice mentioned above can help you reduce the length of scenarios, it takes a good QA and part BA to define proper scenarios.

Your scenarios are not meaningful if they cannot state a single purpose for their existence. This goes hand in hand with the testing principle that we test one thing at a time.

However, we should not confuse “one thing” with “one validation” (…which is sometimes a practice used in unit testing…).

It is perfectly OK to have multiple validations (which could relate to several data fields in your application) in a single scenario; just like in functional testing. However, all these validations should tie to a single purpose.

A very well known golden rule; “One scenario per acceptance criteria” could also help solve this problem (…when taken with some adjustments as a pinch of salt…).

Let’s see an example of a scenario with multiple purposes:

Given I am on Login Page

When I enter username “atmnk” and password “incorrect” and try to log in

Then I see error message “Username or Password Entered is incorrect”

When I enter username “atmnk” and password “correct” and try to log in

Then I see Landing Page

The above scenario in itself has two different concrete meanings; one about verifying application behavior when invalid credentials are entered and other when valid credentials are entered. Hence the above-mentioned scenario can be broken down into two different scenarios like below:

Given I provide “correct” credentials

When I try to login

Then I see error message “Username or Password Entered is incorrect”

And second

Given I provide “incorrect” credentials

When I try to login

Then I see Landing Page

Smaller scenarios turn into more meaningful and readable specifications. They also help you with execution time, since now you can execute these two scenarios in parallel as well.

Sometimes due to the inherent nature of the domain and complexity tied up with the application, scenarios can be lengthy. This is perfectly fine as long as your scenarios have a single meaning and purpose.

(Notice that we have changed things that are in Given and When as well We will talk about this in next part of the blog when we talk about “Misuse of Gherkin”.

Diluted page object with step definitions

If you are using a page object model along with Gherkin style BDD steps, people sometimes confuse what should go inside the step and what should go in the page classes.

The simplest smell that you can look for in your code in such a situation, is whether your code is interacting with a webdriver / locators inside step definition. If yes, then your page object model is compromised or diluted.

Let’s take the same example mentioned above.

Let’s look at sample code that can be written for implementing Login Related Steps;

In the above code, the steps “I am on Login Page” and “I enter username {string} and password {string} and try to login” are perfectly OK as they are just interacting with Page objects.

However, the step “I see error message {string}” has diluted PageObject.

If we look closely, we are directly accessing webdriver in Steps Implementation code. The code related to accessing elements should actually go in LoginPage.

So, let’s rewrite that step

Notice that we have now moved the code that accesses Error Text in login page’s getErrorMessage() method, which should look something like:

public String getErrorMessage(){return driver.findElement(errorLabel).getText();
}

Misuse of Global objects

If we have a closer look at the code above again, we would notice that we have declared WebDriver as static. When we write automation code, we create several other objects as static or create Singleton Patterns either by mistake, or on purpose.

Now, it is perfectly acceptable to use Singleton Patterns in your code. But it should be done carefully.

What problem do you foresee that could occur in the above code?

Suppose now you want to run tests in parallel. What will happen to the WebDriver object?

It will be reassigned a new value based on the executing scenario and this will be erroneous for other tests that share that instance.

So what could be done to improve this?

Fortunately Cucumber-JVM (..as well as other language flavors of Cucumber..) come with Dependency Injection frameworks that help to introduce and sharing Test Context among steps.

Here I will show the usage of “cucumber-picocontainer” to solve the above problem. (Note that you can use any other dependency injection framework and you may not need to use picocontainers if you have one already)

First, add a dependency

Maven<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-picocontainer -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
Gradle
testCompile group: 'io.cucumber', name: 'cucumber-picocontainer', version: '5.7.0'

Now let’s create a class TestContext that stores or initializes an instance of WebDriver like below:

Now let’s rewrite our Step Definitions as below

Notice that we created TestContext class, which now holds or creates an instance of ChromeDrive.

The TestContext will be automatically initialized (using zero-argument constructor) when Scenario execution starts, and the same instance will be shared for all Step Definition classes via dependency injection in the constructor. Also, notice that this instance is not static and will be unique for each execution.

Coming up: Part 2

In Part 2, I will talk about a few more interesting anti-patterns like “Hidden meanings for steps”, “Giant utils for non-UI code”, “Fitting BDD to Test Framework” or “Gherkin as a test script” and few more, before sharing my closing thoughts on the topic. Meanwhile, you can find code that corrects the above-mentioned anti-patterns in this repo.

--

--