Common Anti-patterns in Automation coupled with BDT: Part-2

Atmaram Naik
Technogise
Published in
9 min readMay 31, 2020

In Part-1 of this blog, we talked about a few anti-patterns and made an attempt to correct the Login feature partially. We would like to thank everyone who provided us with their valuable inputs and feedback on the same. In this part, will address some of those inputs.

We will also talk about a few more anti-patterns, thereby improving the Login feature further. We will also take a new example to illustrate some other anti-patterns.

Giant Utils for non-UI code

One of the common mistakes done by coders is that when they can’t figure out proper Abstractions or Modeling for some executable code, they just dump it in the static function in some class named Util.

Util, as the name itself suggests, should only have a utility purpose. Also, it should be noted that Utils should also be modeled like any other class. Making larger and larger Util classes just makes your code difficult to understand for other people and hard to maintain as well. Utils also need to be as loosely coupled and driven by contract as possible, because in the future, if you want to change the utility which was for a specific purpose, with other variations of the utility (either with the same purpose or with some enhancements), it should be easy.

Nowadays, QAs understand Page Object Pattern. They are good at modeling abstractions around UI/Pages/Elements. But when it comes to non-UI code, they often consider those parts of code as Utils. And Utils become a huge set of procedural code wrapped in classes with static functions. If you are working in an object-oriented programming paradigm, and yet doing these things, then you are not getting benefited by it.

Let’s take an example of the Todo App. Suppose there is a feature in this app that marks Todo Item as Done. To verify this feature, we first need to create a Todo Item. Then we may also want to verify the status of Todo. The scenario, in that case, would look like:

Given I have “Pay Internet bill” Todo
When I mark this Todo as done
Then this Todo should be “Done”

For precondition step [Given I have “Pay Internet bill” Todo], we first need to create Todo. Notice that the creation of Todo is just the precondition and not the trigger for which we want to validate behavior. Hence the creation of Todo could be done in several ways (…via UI, via REST API, via directly putting Todo in DB…)

Let’s assume for the first cut, we don’t have UI or REST API that creates Todo and we want to directly insert Todo in DB. (…P.S: This is an ugly way of data creation since it couples your Test Framework with underneath Development Stack tightly. However, since we don’t have other mechanisms, we’re using this as a stop-gap arrangement, until we get a better mechanism…).

A shortcut for this could be that we just create Util class “DBInteractions” as follows:

The name DBInteractions suggests that it has scope for bearing multiple responsibilities, even though it is just dealing with Todo right now.

For e.g, suppose tomorrow you want to create User in the database, you would be tempted to put that code as well in the same class as a static function. If DBInteraction starts handling multiple responsibilities, it will violate the Single Responsibility Principle (SOLID principles). Also, if we use this class at multiple places, we would need to refactor the code at several places in the future when we create Todo via rest API.

Therefore, a better way of doing this would be:

First, we create a service interface “TodoManager”, which defines the contract and the possible features it provides.

Then we create DBTodoManager class which implements the above interface as follows:

And we will initialize the object of this DBTodoManager by using a factory method (for simplicity I will create a static function, but note that this could be done in a better way).

And this is how we use it in the step definition:

In the future, when we want to change the way we create Todo (let’s say via API), we can just create a new class “APITodoManager”, which implements TodoManager and just change the instantiation in Factory with this new class. Also, this will help us gradually deprecate DBTodoManager without affecting much code.

Confusing How for What

A well written Gherkin scenario only deals with the What part of product behavior. It does not dwell into the How part of product implementation.

However, being heavily involved in technical discussions/solution aspects of the product, we QAs often tend to dilute our scenarios with the How part.

A well-abstracted scenario can be used at various levels of testing (Unit/Integration/API/Functional) by just changing the glue and not affecting the feature. The same scenario could be referred by QAs for manual testing, by Developers for understanding a feature, and by Product Owners for defining the product in terms of Product Specification.

Let's take the example below:

Given I create “Pay Internet bill” Todo by inserting into DB
When I click the checkbox to mark this Todo as Done
Then This Todo should not appear on Todo List Page

The above scenario has too much of how details for the inner implementation of steps. We don’t need to specify the Todo creating mechanism (inserting into DB). It is sufficient to say that I have Todo with the title “Pay Internet bill”.

Same is the case with [When I click the checkbox to mark this Todo as Done]. It is too much tied to the implementation that there is a checkbox to mark Todo as Done. What if tomorrow we change that checkbox to some other UI component.. or altogether a different UX (How) implementation? We are just marking Todo as Done in the behavioral sense here. How we do that is not important. In UI tests, it could be via UI elements; in API tests, it could be via API as well.

Similarly, in the step [Then This Todo should not appear on Todo List Page], we are again talking in terms of application UI. But the same verification could be done via API as well.

So with that refined understanding, let's try to rewrite the above scenario.

Given I have Todo “Pay Internet bill”
When I mark this Todo as done
Then this Todo should be “Done”

Gherkin-as a test script

The above-mentioned problem will be prevalent if you haven’t come up with features or scenarios before starting test automation. And then you will feel that Gherkin is just being an additional layer on the test automation suite without adding any other benefits. This happens when we mistake BDD as just a Test Framework.

BDD is created to be a communication framework between participants so that while discussing product features and specifications, all understand the same ubiquitous language.

So the first step in any product discovery process would be to come up with features that are illustrated with concrete examples. These concrete examples could be automated to executable specifications wherever possible. An executable specification serves many purposes at once It can be used to validate software, It can also serve as automatically updated technical and functional documentation. Using this philosophy, our mindset shifts from writing automated tests to writing executable specifications.

In fact, these stories and examples serve as goalposts for developers to write software that does what it’s intended to do. The executable specification helps track a feature’s pulse, as it gives you a clear picture of what so far is built correctly. It can also check what is happening to features when we keep on changing code (Regression Test).

Let's re-look at the example from the previous part of the blog, where we wrote Login Feature 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

Here, our acquaintance with the feature started when we already had a software application with a Login Page (complete with username & password fields) and a Landing Page. However, we could’ve written the executable specification in a more abstract fashion. In fact, all we needed to know was that “User should be able to log in if the user provides correct credentials”. As explained above, it’s more about What goal we want to achieve out of the feature…rather than How we are going to do this.

So the above feature could have been written like below (when the software application didn't even exist)

Given User has provided “correct” credentials
When User tries to log in
Then User is logged in

Notice that we shifted the step that takes credentials from user to Given. To understand why we needed to do so, we need to understand Gherkin well.

Given stands for any precondition or context required by the trigger which is mentioned in When to already have occurred.

Here, we want to validate the behavior that is exhibited by the product when the user tries to log in to the given context. Therefore, providing “correct” credentials is a precondition or context for that behavior.

Finally Then is a post-condition, which we want to ensure has happened after the trigger was fired. In other words, ensuring desired behavior.

Notice that providing “correct” credentials is more abstract here. It doesn’t necessarily need to be through username and password fields. If later on implementation changes to email validation or some other mechanism, your step won’t change. Only the implementation will change.

Steps with hidden behavior

If you need to dive into the implementation details of a step to understand What is its purpose, then it means your step has hidden behavior.

Please note that when I say this, I just mean What and not How (which should be abstracted). A clearly defined step tells you in abstract sense What it means in behavioral terms.

When we said we can combine steps or make our steps more abstract, we never meant that those steps should miss on the details of the What parts... :-)

Let's take an example. Assume we are creating a feature that allows users to book a flight ticket and while booking a ticket, they can opt for a meal as well, and if they do so they will be charged meal price for total purchase. If this scenario is not written with proper care, then it could look like

Given I provide details for booking my flight
When I book my flight
Then I will be charged appropriately

This scenario is way more abstract and very generic. It doesn't properly tell about all possible examples of booking flights.

One could book a flight with or without options for a meal. This is part of what possibly the user could do while booking a flight.

Don’t confuse meal preference as just being a UI action with drop-downs or radio buttons. Opting for a meal is more like a feature because this preference changes the underneath behavior of the software. So we can break this scenario and write in meaningful steps as follows:

When you opt for meal

Given I provide details for booking my flight with meal option
When I book my flight
Then I will be charged for meal

When you don't opt for a meal

Given I provide details for booking my flight without meal option
When I book my flight
Then I won’t be charged for meal

Just notice that we expressed a software behavior of charging customers with two different examples and we converted those examples into specifications. These specifications further could be automated at any level by API tests, UI tests, etc. but core behavior will remain the same.

Closing Thoughts

There is nothing like a perfect Automation framework that can be used in any project and which also addresses most of the above-mentioned anti-patterns. This is because your framework will differ with variations in technology, product, and system architecture. Yet, if you follow the chosen framework with appropriate care, Testing becomes more productive. There is a misconception about BDD that it is required only if Product Owners or non-tech participants need to look at tests. But as I said earlier as well, BDD is not just for testing or automation testing.

--

--