Junior Automation · Test Structure

Writing Your First Test

A test without structure is just a script. Learn the Arrange-Act-Assert pattern that turns fragile code into maintainable automation.

Junior Automation ISTQB CTAL-TAE v2.0 — Chapter 3 ~10 min read + exercise

1 The Hook — Why This Matters

In 2022, a junior engineer at Trade Me wrote a login automation script that "passed" for six consecutive weeks. The script opened the login page, filled the username and password fields, and clicked the submit button. But there was no assertion. None.

During a deploy, the application began redirecting authenticated users to a maintenance page instead of the dashboard. The test kept passing because it never checked the destination URL or any on-screen text. The bug reached production and stayed there for three days before a manual tester spotted it.

A script that never asserts is not a test. It is a demonstration. The Arrange-Act-Assert pattern exists to prevent exactly this kind of false confidence.

2 The Rule — The One-Sentence Version

Every automated test must arrange its preconditions, perform exactly one action, and assert one observable outcome — or it is not a test.

When a test checks multiple unrelated behaviours, a failure forces you to investigate which part broke. When a test has no assertion, it cannot fail — which means it cannot tell you when the application is broken. Structure is not bureaucracy; it is the mechanism that makes failures meaningful.

3 The Analogy — Think Of It Like...

Analogy

A breathalyser test.

You blow into the tube (act), and the device measures your blood-alcohol content (assert). If the device just lets you blow and never measures, it is a straw, not a test. The arrange step is making sure the device is calibrated, the battery is charged, and the officer knows the legal limit. Skip assert, and you might as well skip the test.

4 Watch Me Do It — Step by Step

Here is the Arrange-Act-Assert pattern applied to a real login flow. Use this structure for every UI test you write.

  1. Arrange — set up preconditions Create page objects, navigate to the URL, and prepare test data.
    login_page = LoginPage(page)
    login_page.open()
  2. Act — perform exactly one action Trigger the behaviour you are testing. One action per test.
    login_page.login("alice@example.com", "Secret123!")
  3. Assert — verify one observable outcome Check something a user could see or experience.
    expect(page).to_have_url("/dashboard")
    expect(page.get_by_text("Welcome, Alice")).to_be_visible()
Complete AAA test — Python / Playwright
def test_login_success(page):
    # Arrange
    login = LoginPage(page)
    login.open()

    # Act
    login.login("alice@example.com", "Secret123!")

    # Assert
    expect(page).to_have_url("/dashboard")
    expect(page.get_by_role("heading", name="Dashboard")).to_be_visible()
Naming convention quick reference
PatternExampleBest for
should_test_should_display_error_when_password_emptyxUnit frameworks
Given_When_Thentest_givenValidCredentials_whenLogin_thenDashboardDisplayedBDD / Gherkin
method_condition_resulttest_login_withEmptyPassword_showsErrorUnit tests
Pro tip: Test names should read like specifications. If you can't read the test name aloud and understand what is being proven, rewrite the name. Future-you will thank present-you when the test fails at 2 a.m. during a production incident.

5 When to Use It / When NOT to Use It

✅ Use AAA when...

  • Writing any repeatable automated verification
  • Building regression suites that run in CI
  • Onboarding new engineers to existing test code
  • The behaviour has clear preconditions and outcomes
  • You need a single, diagnosable reason to fail

❌ Don't force AAA when...

  • Doing pure exploratory testing
  • Writing one-off debugging scripts
  • The cost of maintenance exceeds the value of the check
  • The test depends on hidden global state from previous tests

Before you apply this technique, ask:

  • Does your test have exactly one main behaviour it's checking?
  • Are your Arrange, Act, and Assert sections clearly separated?
  • Would this test fail if the feature broke, and pass if it works?
 -->

6 Common Mistakes — Don't Do This

🚫 Forgetting the assertion

I used to think: If the script runs to the end without crashing, the test passed.
Actually: A test without an assertion is like a car without a speedometer. You might be moving, but you have no idea if you're within the limit. Always assert an observable outcome: a URL, a visible element, or a status code.

🚫 Multiple unrelated asserts in one test

I used to think: One big test with ten assertions is more efficient than ten small tests.
Actually: When the fifth assertion fails, you don't know whether assertions six through ten would have passed or failed. You lose diagnostic precision. One behaviour per test. Use soft assertions only when the fields are related parts of the same validation event.

🚫 Testing implementation details

I used to think: Asserting on a private method or internal variable is more precise.
Actually: Tests that lock themselves to implementation break every time the code is refactored, even when the behaviour is correct. Test what the user sees: the URL, the button state, the confirmation message. If a user can't observe it, your test shouldn't assert it.

When this technique fails

If you test three things in one test and one fails, you know something broke—but not what. You'll spend time debugging to find out whether login is broken, the dashboard isn't showing, or the property creation failed. Plus, your test report is confusing to the team.



7 Now You Try — Interview Warm-Up

🎯 Interactive Exercise

Spot the bug: This test "passes" every time. Why?

def test_add_to_cart(page):
    page.goto("/product/42")
    page.get_by_role("button", name="Add to Cart").click()
    # TODO: verify cart updated

Write down what's missing before revealing the answer.

The missing assertion:

expect(page.get_by_test_id("cart-count")).to_have_text("1")
expect(page.get_by_text("Added to cart")).to_be_visible()

The comment # TODO: verify cart updated means the test never actually checks anything. It will pass even if the button is broken, the page 500-errors, or the product is out of stock. Every test must end with an assertion that would fail if the behaviour is broken.

8 Self-Check — Can You Actually Do This?

Click each question to reveal the answer. If you got all three, you're ready to practice.

Q1. What do the three A's in Arrange-Act-Assert stand for?

Arrange sets up preconditions (open the page, create data). Act performs the single behaviour under test (click, submit, call API). Assert verifies the observable outcome (URL changed, element visible, status code 200).

Q2. Why should a test have only one reason to fail?

Because diagnosis is the expensive part. When a test with ten assertions fails on assertion three, you don't know whether assertions four through ten are also broken. One failure per test means the test name tells you exactly what is wrong.

Q3. What is wrong with asserting on a private method or internal variable?

It couples the test to implementation rather than behaviour. When a developer refactors the code — changing the internal structure but preserving the user-facing behaviour — the test breaks even though the application still works correctly. Assert on what the user can see.

Interview prep

Interview questions explore whether you understand the Arrange-Act-Assert pattern and why focused tests matter.

Q1: Arrange, Act, Assert—explain each step in your own words using a BNZ login test as an example.
Arrange: Load the BNZ login page and fill in valid credentials. Act: Click the 'Sign In' button. Assert: Verify that the customer dashboard appears and the user's account balance is visible. Each step has a clear job.
Q2: Your test passes locally but fails in CI. Why might AAA help you debug this faster?
Because the three sections are separate, you can add logging or breakpoints to each one. If the Arrange section is wrong, your test is testing the wrong thing. If the Act section is flaky, you're clicking at the wrong time. If the Assert is failing, the feature is broken. AAA isolates the problem.
Q3: You write a test: 'User logs in, navigates to reports, clicks export, and a CSV downloads.' Why is this a smell?
You're testing four behaviours—login, navigation, button click, and file download—in one test. If it fails, you don't know which step broke. Plus, testing file downloads is complicated. Write a login test, a navigation test, and a separate download test.
Q4: Your Arrange section sets up 10 different database records. Is this good or bad?
Bad. If the test fails, you don't know which of those 10 records caused the problem. Set up only what's needed for the one behaviour you're testing. If you need 10 records for different tests, write 10 tests—each with its own Arrange.