Senior Level · Learning

Database & Backend Testing

Most defects live in the data layer, not the UI. A senior tester who can write a SELECT, read a query plan, and spot an orphaned foreign key is ten times more effective than one who can only click.

Senior ISTQB CTAL-TA 3.2 ~15 min read + exercise

1 The Hook

An Auckland insurance company migrates 500,000 policy records from a legacy system to Salesforce Financial Services Cloud. The project runs for eight months. Go-live day: the UI shows all policies loaded. The migration team signs off. Everyone goes home.

A month later, renewals start failing randomly. Customer services are getting calls. Root cause investigation takes two weeks. The migration script had mapped the renewal_date column correctly for policies with a January start date — but incorrectly for every other month. The formula treated month numbers as sequential offsets rather than calendar months. Policies with an April start date showed a renewal date in January of the following year. 487,000 of the 500,000 records were wrong.

The tester had validated the migration through the UI. Policy details looked correct on screen. Had she run SELECT * FROM policies WHERE renewal_date < GETDATE() AND renewal_status = 'Active' and cross-checked ten rows against the legacy extract, she would have found the mismatch before go-live. The rework cost exceeded the original migration budget.

2 The Rule

Trust the database, not the screen. Always verify calculated fields, foreign keys, and derived data directly in the source — not through the application layer.

3 The Analogy

Analogy — Use this in interviews

The application is a calculator display. The database is the working memory.

You can see the answer on the display, but if you want to know whether the calculation is correct, you open the memory and check the intermediate steps. A calculator can show "42" on screen while holding a completely wrong value in memory — the display formats whatever it's given. The database holds the actual numbers. Always check the memory, not the display.

4 Watch Me Do It

SQL test cases for verifying KiwiSaver contributions in a payroll system. These queries run against the test database after a payroll run has been processed.

-- 1. Verify contribution amounts match the calculated expected value
SELECT
  e.employee_id,
  e.ird_number,
  e.kiwisaver_rate,
  p.gross_salary,
  p.kiwisaver_contribution,
  ROUND(p.gross_salary * e.kiwisaver_rate, 2) AS expected_contribution,
  CASE
    WHEN p.kiwisaver_contribution = ROUND(p.gross_salary * e.kiwisaver_rate, 2)
    THEN 'PASS'
    ELSE 'FAIL'
  END AS result
FROM payroll p
JOIN employees e ON p.employee_id = e.employee_id
WHERE p.pay_period = '2026-06'
ORDER BY result DESC;

-- 2. Check for orphaned payroll records (no matching employee)
SELECT p.employee_id
FROM payroll p
LEFT JOIN employees e ON p.employee_id = e.employee_id
WHERE e.employee_id IS NULL;

-- 3. Verify referential integrity on policy table
SELECT COUNT(*) AS orphaned_policies
FROM policies pol
LEFT JOIN customers cust ON pol.customer_id = cust.customer_id
WHERE cust.customer_id IS NULL;

Run the first query. Any row showing FAIL is a defect. The CASE statement does the assertion work for you — you're not eyeballing numbers, you're running a query that produces a verdict.

Run the second query. Any row returned means a payroll record exists with no corresponding employee. That's referential integrity failure. If the count is zero, you pass.

Reading a query plan. Add EXPLAIN before any SELECT and the database shows you how it will execute the query. Look for "Seq Scan" or "Full Table Scan" — these read every row in the table and will be slow on large datasets. "Index Seek" or "Index Scan" means the database is using an index, which is fast. In test environments with small data sets, a full table scan won't hurt. On a table with 10 million rows in production, it can lock the table for minutes.

Pro tip: Keep your verification queries in a test scripts folder in the repo. After any migration or batch process, run the scripts and paste results into your test evidence. You have reproducible, automated database verification with a script anyone can re-run.

5 When to Use It

Use database testing when: validating any data migration; verifying calculated or derived fields (totals, rates, dates, balances); confirming batch processing results; checking referential integrity after an import; when the UI shows correct data but a business report is showing wrong figures.

Triggers to go directly to the database: the developer says "the data is correct"; a business report doesn't match the UI; a batch job ran overnight and you need to verify outputs; you're testing a data migration and need to compare source with destination.

Don't replace UI testing. Database queries tell you the data is right. UI tests tell you the data is presented correctly and users can interact with it. Both layers can independently fail. A correct database record can still display incorrectly in the UI.

6 Common Mistakes

🚫 Validating migrations through the UI

I used to think: if the UI shows the right total, the data is right.
Actually: the UI might be rounding, aggregating, or filtering differently to the back end. Always check the raw rows. Spot-check 10 records against the source system manually. The UI is a presentation layer — it can make wrong data look right.

🚫 Thinking you need write access to test the database

I used to think: I need to write UPDATE and INSERT statements to test properly.
Actually: SELECT is enough for verification. Never run writes against a test database you don't own without explicit approval. Your job as a tester is to observe, not to modify. READ access is sufficient for almost all database verification work.

🚫 Assuming referential integrity is automatically enforced

I used to think: foreign key constraints prevent orphaned records.
Actually: many systems disable FK constraints for performance, particularly during bulk imports and migrations. You need to explicitly test for orphaned records using LEFT JOIN queries. Don't assume the database enforces what the schema says.

🚫 Treating a correct row count as proof the data is correct

I used to think: if COUNT(*) matches before and after migration, the data is right.
Actually: COUNT(*) tells you how many rows exist, not whether the values are correct. You can migrate 500,000 rows perfectly and have every renewal_date wrong. Always spot-check actual values, not just counts.

7 Now You Try

🧪 Prompt Lab — Council Rates Payment System

A rates payment system for an NZ council processes rates notices. You have SELECT access to the database. Write 4 SQL test cases that verify: (1) every property has exactly one rates notice per rating year, (2) the calculated rates amount matches the rateable value multiplied by the rate-in-dollar, (3) no notice references a property that doesn't exist in the properties table, (4) overdue properties have a non-null overdue_fee. Ask the AI to review your queries and suggest any missing edge cases.

8 Self-Check

Click each question to reveal the answer.

Q1. What is a LEFT JOIN and when would you use it in testing?

A LEFT JOIN returns all rows from the left table and matching rows from the right. Where there's no match, the right-side columns are NULL. In testing, you use this to find orphaned records: SELECT * FROM orders LEFT JOIN customers ON orders.customer_id = customers.id WHERE customers.id IS NULL. Any row returned means an order with no matching customer — referential integrity failure.

Q2. Why is it dangerous to run SELECT * on a 10 million row table in production?

Without a WHERE clause and index, SELECT * forces a full table scan — the database reads every row sequentially. On a large table this can hold shared locks for minutes, slow down concurrent queries, and degrade the application for real users. In production, always filter to a specific set of rows and ensure there's an index on your WHERE column before running queries.

Q3. How would you verify a calculated field without access to the business rule document?

Ask the developer or business analyst to provide one worked example: an input record with a known correct output. Verify that example manually. Then write a query that applies the same formula and compare results across all records. If you get zero mismatches, the formula is consistent. You don't need the full specification — you need one verified anchor point.

9 ISTQB Mapping

CTAL-TA v3.1.2 — Section 3.2.2: Testing at integration level — covers component integration testing scope, which includes testing the integration between application logic and database layer.

CTAL-TA v3.1.2 — Section 3.3.1: Test design for data integrity — directly covers verification of data constraints, referential integrity, and derived field validation.

CTFL v4.0 — Section 4.3.2: Decision table testing — applicable to database business rule verification, where combinations of input conditions (rate type, zone, property category) produce different calculated outputs.