DEV Community

Cover image for Best practices for test automation
Leonardo Barba Galani for Assert(QA)

Posted on

Best practices for test automation

It's great to see so many peers getting better on what they do, learn new technologies, learn new frameworks, calling themselves QAOps, QaDev, all sort of titles to show that they might know how to code or can put a test framework and an end2end test up and running in no time.

The only little problem is that some of those people kind of don't know what they are doing, don't know WHY they are applying specific techniques or why they are using certain frameworks.

If you already got bored of this intro and want to close this tab, please consider at least the most important take away from this article: 'Test Automation is a development process, end of the story'.


Understand your current project, and it's Decisions.

You might have entered on the project after the big decisions where made, but this process shouldn't be less relevant. Try asking around about the context of the decisions and exercise a little bit of critical thinking.

"Should we be using BDD for this project? Why is there no unit test? Why there are only U.I. Tests? Why don't we do stress tests Etc."

Before writing any line of code, understand the patterns that are in place and code itself, so you don't repeat yourself.

And if its a new project, you should start thinking strategy with your team and defining scope, coverage, and other stuff to guarantee the quality of the software you will deliver.


Always do a Proof of Concept

Before making a huge setup, install a bunch of dependencies or spend time thinking about how you should organize your stuff, take a moment to realize if you know what you are doing.

Do you know the language? Do you know the framework? Do you know if it's possible to automate that particular thing that the P.O. asked you?

Well, there is no answer before you try those stuff your self and this is
the reason why you should do a proof of concept as the first thing.

You should test your hypotheses with little/no dependency, so other people can also execute the piece of code you just did.

POC's can also apply for new features/validation on the test code that you are not sure how to do it.
Do you need to execute all the setup to "debug, fix and retry" every time?

It's also a good practice to talk to people and tell them your intends with the future POC that you are going to do and see if they want to check the results with you OR if they have another idea that you cant try out.
I don't see any reason for people to start coding away, especially a new test framework, without talking to people and showing them how you will do it.


Check your external dependencies

Have you ever stop and look at the dependencies of your code? Did you ever check the license of the packages you use? How many issues are open, and many commits and releases there have?

If you work at a startup that really doesn't care about anything and just want to put code into production, oh well, there is nothing we can do, right?

But if you somehow care about your work and the code you put in production, you might want to check the security breaches of the dependencies you use, if they have an apache/MIT license or GPL or if they are a fork of another library.


Invest in code isolation

Ever time you code some helper, util, or a shared method, think about code isolation.
Think about yourself if a few days in the future and how happy you will be not to have errors popping out of nowhere when you remove an import that you thought it was safe to remove.

Do you have hidden imports that searches and add your entire repo into memory just because you don't want to have a few more lines? Can you feel the code smell? A tricky one to fix if you don't start your project in the Right way. Even if you do things correctly in the beginning, you should always stay vigilant on every P.R.

Code isolation can be easily achieve if you understand what a code Domain / Namespace is.

+info: https://en.wikipedia.org/wiki/Domain-driven_design

A helper method should NOT know any implementation details of any other thing on your project.

Sometimes it's a bit hard to understand with plain text, right? Let me give you a simple pseudo-py-code example:

def login(user):
    field.email.set(user['Email'])
    field.password.set(user['Password'])

Can you see the problem with the code above?
The login method "knows" the implementation details of the user object. What would happen if somehow we change the interface of the user? Or maybe there are multiple ways to create a user, and one of those methods might create the user with just a "magic_link". Or maybe this user object has a cellphone number instead of email as identification. Can you see where I'm going?

The good refactor might look something like this, removing the knowledge of the user interface:

def login(email, password):
    field.email.set(email)
    field.password.set(password)

Put memory allocation into consideration

"Leonardo, I think you are living in the early 2000s when people didn't have decent computers with decent processing power".

You can tell me that your company gave you a brand new MacBook with tons of memory BUT, are you going to run the CI process inside your MacBook? I guess not.

Most of your tests will run in parallel inside a CI cluster that can be expansive. Don't count on having workers with a bunch of memory and processing power because the price to maintains those machines/instances are high.

"We have a nice machine for the CI man, stop being a jerk."

You know that if you have an excellent machine, it will probably going to use docker to run your CI software, and perhaps your test will be run on a container, in other words, docker inside docker. It's okay, but if your machine is not that good, buckle up because the slow and flaky tests are coming in your way.

To avoid this, make your code isolated. Only put in memory modules/libs/packages that you going to use. Avoid using external libs for trivial things.

I might be pushing too hard on this one, but stay dependency-free as much as you can.

Ex: In ruby, there is a framework called Airborne for API tests, which is a wrap of Faraday/http_request/assert.
You can use Airbone to do your API test, but you don't need it. You have all the tools you need to test API with pure ruby.

Its just a matter of understanding the language, what it comes with, and how to make most on you got.


Code smell and Language Style

Despite your language of choice, understand that every time you touch a repository, you are singing a blood pact that you and those after you will follow the language/repository guidelines.

Every language has its ways to define vars, constants, classes, and methods. Those principles will help you to avoid most of the things that lead to code smell, so please, don't turn off your Rubocop, pylint, eslint, or whatever you have in place to check your code.

Don't think that your case is unique and disable the rules. Treat it as Technical Debit and put it on your backlog.

Once you understand the rules, your new code will pass flawlessly on those checks, and you will see the benefits when shipping or debugging your code.

Some companies also define their patterns. You should apply it to your work, but IF by doing so, you feel that you are deriving away from the industry standards, speak up.


Avoid If's as much as possible.

+info: https://francescocirillo.com/pages/anti-if-campaign

One of the most problematic behaviors that I see young test developers doing is the tsunami of if's and try expect on their code.

The conditional operator "IF" is a robust tool that you will use a lot, but bear in mind that it increases the complexity of the code exponentially.
A test script or helper should be clear and have a single purpose.

Let's use the same example from before:

def login(user):
    if user['MagicLink']:
        do.login(user['MagicLink'])
        return
    elif user['Email]:
        field.email.set(user['Email'])
    elif user['Cellphone']:
        field.cellphone.set(user['CellPhone'])

    field.email.set(user['Password'])
    do.login

You may be laughing about this and saying, "I would never do such a thing," but the number of times I saw similar mistakes like that on forums and P.R.'s that I had to review is vast!

Let's refactor so you can understand what I'm saying:


def login_magic_link(magic_link):
    visit magic_link
    do.login

def login_with_email(email, password):
    field.email.set(email)
    field.password.set(password)
    do.login

def login_with_cellphone(cellphone, password):
    field.cellphone.set(cellphone)
    field.password.set(password)
    do.login

A code that is easy to read is also easy to debug and to extend.


Read code, docs and unit test

If you don't have the habit of reading other people's code or understanding what the unit tests are testing, You might want to put this process on your daily tasks.

Sometimes people will not have time to help you out on a particular task, or maybe the answer on how to instantiate a specific object is already in place on a unit test.

Make a habit of reading the implementation details before asking stuff on the internet.


CODE! CODE! CODE!

Don't code just test scripts. Code a custom tool or use a cocking recipe to create your twitter or something. Maybe a code challenge might be your thing.

Practice the craft as much as possible because "test automation" is on its way to not be a job title anymore.


Final Thoughts

I left out some topics like "pair programming" and "P.R. revision" because you can't use those technics and practices if you don't know what you are doing :)

I'm open to critics and suggestions... are you interested in a possible part 2? let me know!

Top comments (0)