Learn how to structure a test

Using a testing workflow of some kind (TDD, BDD, E2E...) is a good quality measure. Not because it guarantees that the function or object works as expected, but because it indicates that the project has a solid architecture and good developers behind it.

Testing is not just about writing code to verify other code, it is a professional working methodology that broadens your perspective on the code. You write a precise script of what you are looking for, making clear how it should behave and how it will react in edge cases. All of this without even writing the functionality. On top of that, you indirectly document how it works.

It is often said that testing is artisanal, that every case is unique. That is not true, we have patterns or templates that help us get started and give structure. Among the simplest and best known is Given-When-Then.

Created by Daniel Terhorst-North and Chris Matts as part of BDD (Behavior-Driven Development), it proposes a pattern of 3 informal comment blocks to divide the code.

  1. Given: You prepare the test scenario, such as the database, variables or the right conditions.
  2. When: Conditions that will transform the content.
  3. Then: You verify the final result, that everything you expected, or the proposed cases, has been met.

Let's put an example on the table in plain prose. We are going to write a test for the tale of "the 3 little pigs". The goal is to check that they are safe from the wolf.

Given 3 houses, ['straw', 'wood', 'bricks']...

When the wolf blows on each of them...

Then there must be 1 or more houses still standing.

With this structure, we can write the test in any language.

Before writing the code, let's define a table with the cases we want to test. In this case, we will test 3 fictional login cases.

ID SCENARIO TEST CASE PRE-CONDITION GIVEN (INPUTS) THEN (Expected results)
test_login_1 Verify correct login Enter valid Email and valid Password Need a valid account Valid email and Valid password Successful login page
test_login_2 Verify login with wrong fields Enter a wrong email and a valid password Need a valid account Wrong email and Valid password Same page, error message: "The account does not exist or the password is wrong”
test_login_3 Verify login with wrong fields Enter a valid email and a wrong password Need a valid account Valid email and Wrong password Same page, error message: "The account does not exist or the password is wrong”

Although I recommend writing it in English, you can do it in whatever language you prefer.

Now, let's write the code. I will use Python with the pytest library, but you can use whichever language you prefer.

import pytest

# Pre-condition: Need a valid account
VALID_EMAIL = "valid@example.com"
VALID_PASSWORD = "correctpassword"
WRONG_EMAIL = "wrong@example.com"
WRONG_PASSWORD = "wrongpassword"

# Given section: Input data for tests
@pytest.fixture
def valid_email():
    return VALID_EMAIL

@pytest.fixture
def valid_password():
    return VALID_PASSWORD

@pytest.fixture
def wrong_email():
    return WRONG_EMAIL

@pytest.fixture
def wrong_password():
    return WRONG_PASSWORD

# When section: Actions to be performed in tests
def login(email, password):
    # Mocked login function; replace with actual login implementation
    if email == VALID_EMAIL and password == VALID_PASSWORD:
        return {"msg": "Successful login page"}
    else:
        return {"msg": "The account does not exist or the password is wrong"}

# Then section: Expected results
def test_login_1(valid_email, valid_password):
    # Given
    email = valid_email
    password = valid_password

    # When
    result = login(email, password)

    # Then
    assert result.msg == "Successful login page"

def test_login_2(wrong_email, valid_password):
    # Given
    email = wrong_email
    password = valid_password

    # When
    result = login(email, password)

    # Then
    assert result.msg == "The account does not exist or the password is wrong"

def test_login_3(valid_email, wrong_password):
    # Given
    email = valid_email
    password = wrong_password

    # When
    result = login(email, password)

    # Then
    assert result.msg == "The account does not exist or the password is wrong"

We have implemented the same test we defined in the table. In each test, we have defined the initial conditions, the actions to perform and the expected results. The function names must match the test case names defined in the table. That way we can easily identify which test case is failing if any of them does not pass.

The most common cases in testing

When you do testing, you will find a pattern, common cases that you must keep in mind. Below is a list of points to consider when implementing:

  • The data is correct and the expected result is correct. The ideal case.
  • The data is incorrect and the expected result is incorrect. For example, a wrong email and a wrong password.
  • The input data is insufficient. For example, a required field is missing.
  • The input data has incorrect types. For example, a number instead of a list.
  • It returns every possible error message. For example, if the email already exists, the error message must be "Email already exists".
  • Extreme input data. For example, an email with 1000 characters.
  • Boundary input data. For example, an email with 254 characters, which is the limit of an email.

Summary

You have witnessed all the typical steps in a development with the TDD methodology.

  1. Document.
  2. Create the test.
  3. Run the test, getting a failure.
  4. Create the functionality.
  5. Run it again, getting a success.

If you want to dig deeper, you can explore my free testing course where you will learn other techniques and implementations in other languages.

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Will you buy me a coffee?

Comments

There are no comments yet.

Written by Andros Fenollosa

April 15, 2026

4 min of reading

You may also like

Visitors in real time

You are alone: 🐱