Senior · Data Integrity & Architecture

Advanced Database Testing & Data Integrity

At senior level, database testing expands beyond form submissions. You must understand data integrity patterns, schema contracts, migrations, transactions, and compliance implications that protect the entire system.

Senior ISTQB CTAL-TTA 3.3 — K3 Apply ~15 min read + exercise

1 The Hook — Why This Matters

A major NZ bank pushed a schema migration to production: renaming a column from account_balance_nzd to balance. The migration ran. Tests passed. But the mobile app was still calling the old column name in its API calls. For three hours, the API returned NULL for every balance check. Customer support received 14,000 calls. The bank's reputation took a hit.

A senior data integrity tester would have tested the migration by: (1) running it against a production-like replica, (2) verifying both the old and new column names worked during a transition period, (3) checking for cascading deletes or constraint violations, (4) confirming rollback was reversible, and (5) validating API contracts matched the schema change.

Database testing at senior level is about protecting data contracts, preventing silent data loss, and ensuring the entire system adapts to schema changes. It's not just "did the data save"—it's "is the data intact, consistent, and accessible across all systems?"

2 The Rule — The One-Sentence Version

Test the schema contract, the data constraints, the transaction boundaries, and the migration path—not just the happy path.

Data integrity is a system property, not a UI property. It spans schema design, triggers, stored procedures, cascading changes, rollback paths, and API compatibility. A single weak link silently corrupts data for thousands of users.

3 The Analogy — Think Of It Like...

Analogy

Building a bridge with invisible supports.

The visible part of a database is the data users see: account balances, order lists, customer details. The invisible parts are the constraints (no negative balances), triggers (log all deletions), transactions (all-or-nothing atomicity), and foreign keys (no orphaned records). A bridge that looks solid on the surface but has corroded internal cables fails catastrophically. Database testing is inspecting the internal structure: Are constraints enforced? Do triggers fire? Do transactions roll back when they should? Do schema changes propagate correctly?

4 Watch Me Do It — Step by Step

Here is a complete advanced database testing workflow for a real NZ scenario: testing a payment processing system migration.

  1. Document the schema contract Before testing, understand the schema: table structure, column types, constraints, triggers, and stored procedures. Create a schema diagram or document showing relationships.
    -- Example: payments table contract
    CREATE TABLE payments (
      id BIGINT PRIMARY KEY AUTO_INCREMENT,
      amount_nzd DECIMAL(10,2) NOT NULL CHECK (amount_nzd > 0),
      currency VARCHAR(3) DEFAULT 'NZD',
      customer_id BIGINT NOT NULL FOREIGN KEY REFERENCES customers(id),
      status VARCHAR(20) DEFAULT 'pending',
      created_at TIMESTAMP DEFAULT NOW(),
      UNIQUE KEY (id, created_at)
    );
  2. Test constraint enforcement Verify that constraints are actually enforced in the database, not just the application. Try inserting invalid data:
    -- Should FAIL: negative amount violates CHECK
    INSERT INTO payments (amount_nzd, customer_id) VALUES (-50.00, 123);
    -- Expected: Error "Check constraint violated"

    -- Should FAIL: duplicate unique key
    INSERT INTO payments (id, amount_nzd, customer_id, created_at)
    VALUES (999, 100.00, 123, '2026-04-24 10:00:00');
    -- Expected: Error "Duplicate entry"
  3. Test transactions and rollback A transaction must be all-or-nothing. If a payment is split across two inserts (payment record + audit log), both must succeed or both must fail.
    START TRANSACTION;
    INSERT INTO payments (amount_nzd, customer_id, status)
    VALUES (500.00, 123, 'pending');
    INSERT INTO payment_audit_log (payment_id, action)
    VALUES (LAST_INSERT_ID(), 'created');

    -- Simulate error: try to insert invalid audit log
    INSERT INTO payment_audit_log (payment_id, action)
    VALUES (LAST_INSERT_ID(), NULL); -- This will fail (NOT NULL constraint)

    -- Rollback: both inserts should be undone
    ROLLBACK;

    -- Verify: payment should NOT exist
    SELECT * FROM payments WHERE amount_nzd = 500.00;
  4. Test cascading deletes and orphaned records If a customer is deleted, what happens to their payments? Are they deleted (cascade), marked as orphaned, or do they block the delete?
    -- Check constraint definition
    SHOW CREATE TABLE payments\G

    -- If CASCADE DELETE is defined:
    DELETE FROM customers WHERE id = 123;
    -- Expected: all payments for customer 123 are ALSO deleted

    -- Verify no orphans exist
    SELECT p.* FROM payments p
    LEFT JOIN customers c ON p.customer_id = c.id
    WHERE c.id IS NULL; -- Should return 0 rows
  5. Test stored procedures and business logic Many systems enforce critical logic in stored procedures, not the application. Test them directly:
    -- Example: stored procedure to process payment
    DELIMITER $$
    CREATE PROCEDURE process_payment(IN p_payment_id BIGINT)
    BEGIN
      DECLARE p_amount DECIMAL(10,2);
      SELECT amount_nzd INTO p_amount FROM payments WHERE id = p_payment_id;
      IF p_amount IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Payment not found';
      END IF;
      UPDATE payments SET status = 'completed' WHERE id = p_payment_id;
      INSERT INTO payment_audit_log (payment_id, action) VALUES (p_payment_id, 'processed');
    END$$
    DELIMITER ;

    -- Test it: call with invalid payment_id
    CALL process_payment(99999); -- Should error: "Payment not found"
  6. Test schema migrations and reversibility Before running a migration in production, test it on a replica. Verify: it succeeds, it is reversible, data is not corrupted, and all APIs still work.
    -- Migration: rename column amount_nzd to amount
    ALTER TABLE payments RENAME COLUMN amount_nzd TO amount;

    -- Test 1: Data is intact
    SELECT COUNT(*) FROM payments; -- Should match before count

    -- Test 2: Old query fails (breaking change for apps using old name)
    SELECT amount_nzd FROM payments WHERE id = 123; -- Should error

    -- Test 3: New query works
    SELECT amount FROM payments WHERE id = 123; -- Should work

    -- Test 4: Can rollback if needed
    ALTER TABLE payments RENAME COLUMN amount TO amount_nzd;
  7. Test NZ Privacy Act 2020 compliance for data retention NZ law requires deletion of personal data when no longer needed. Verify your deletion logic works:
    -- Test: soft delete (mark as deleted, keep for audit)
    UPDATE customers SET deleted_at = NOW() WHERE id = 123;

    -- Test: queries exclude soft-deleted rows
    SELECT * FROM customers WHERE deleted_at IS NULL;
    -- Verify: customer 123 is NOT returned

    -- Test: hard delete (actually remove)
    -- First, archive to backup table
    INSERT INTO customers_archive SELECT * FROM customers WHERE id = 123;
    -- Then delete
    DELETE FROM customers WHERE id = 123;

    -- Test: orphaned payments are also deleted or archived
    SELECT * FROM payments WHERE customer_id = 123;
    -- Should return 0 rows (cascade delete worked) or be in payments_archive
  8. Test concurrent access and eventual consistency If multiple processes update the same record, verify they don't corrupt data:
    -- Session 1: Start transaction
    START TRANSACTION;
    SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- Lock the row
    -- ... some processing ...
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;

    -- Session 2 (blocked until session 1 commits):
    SELECT balance FROM accounts WHERE id = 1; -- Waits for lock

    -- Verify: read is consistent, no lost updates
    COMMIT;

Common data integrity test matrix:

PatternWhat to testWhy it matters
ConstraintsInsert invalid data (negative amounts, NULLs in required fields, duplicates)Prevents corrupt data from entering system
TransactionsFail one of two coordinated inserts; verify rollbackPrevents partial updates (balance updated, audit log not)
CascadesDelete parent record; verify children are deleted or blockedPrevents orphaned records and inconsistent relationships
TriggersVerify trigger fires on insert/update/delete; audit logs createdEnsures compliance, debugging, and state tracking
MigrationsRun on replica; test rollback; verify APIs still workPrevents outages from schema changes
ConcurrencySimultaneous updates to same record; check for lost updatesPrevents race conditions in high-load systems
Pro tip: Create a test database that mirrors production schema and includes realistic test data (at scale—1 million customers, not 10). Run your integrity tests on the replica first. This catches issues before they hit production.

5 When to Use It / When NOT to Use It

✅ Use advanced DB testing when...

  • Testing schema migrations or database refactoring
  • Implementing financial, payment, or PII systems
  • Building multi-system integrations (need data consistency)
  • Testing data deletion or archival for compliance
  • Verifying stored procedures and triggers
  • Stress-testing concurrent database access
  • Ensuring rollback strategies work correctly

❌ Don't use when...

  • You cannot access the database (production-only with no test env)
  • Testing read-only reporting (no data mutation)
  • The system uses eventual consistency and you need instant verification
  • You lack the SQL expertise on your team
  • You're testing only the frontend, not backend logic

6 Common Mistakes — Don't Do This

🚫 Testing migrations only on dev databases

I used to think: If the migration works on dev, it will work on production.
Actually: Dev has 100 test records. Production has 100 million. A migration that takes 2 seconds on dev might take 2 hours on production, causing timeouts. Constraints that don't trigger on test data might block the entire migration. Always test on a production-scale replica before running in production.

🚫 Assuming the application enforces constraints

I used to think: If the code checks for negative amounts, the database doesn't need to.
Actually: If the code is bypassed, patched, or has a bug, the database constraint is your last line of defense. Test constraints at the database layer directly. Constraints should be enforced where the data lives, not just where it's entered.

🚫 Not testing rollback paths

I used to think: If a transaction succeeds, I don't need to test what happens if it fails.
Actually: The failure path is where bugs hide. A transaction that updates balance and inserts an audit log must roll back both if the audit fails. If only the balance rolls back and the audit stays, you have inconsistent state. Test rollback explicitly.

🚫 Ignoring NZ Privacy Act implications

I used to think: Deletion is just a SQL DELETE statement.
Actually: NZ Privacy Act 2020 requires data deletion when no longer needed. But you also need audit trails (which require keeping data). Test both: soft deletes (mark as deleted, keep for audit) and hard deletes (remove from system). Test that queries don't return soft-deleted data. Test archival before deletion.

7 Now You Try — Payment System Integrity

🎯 Interactive Exercise

Scenario: You are testing a payment system with this schema:

CREATE TABLE payments (
  id BIGINT PRIMARY KEY,
  amount DECIMAL(10,2) NOT NULL CHECK (amount > 0),
  customer_id BIGINT NOT NULL FOREIGN KEY REFERENCES customers(id),
  status VARCHAR(20) DEFAULT 'pending'
);

CREATE TABLE payment_logs (
  id BIGINT PRIMARY KEY,
  payment_id BIGINT NOT NULL FOREIGN KEY REFERENCES payments(id) ON DELETE CASCADE
);

Your task: Write TWO test cases:

  1. Test constraint enforcement: try to insert a negative amount (should fail)
  2. Test cascade delete: delete a payment and verify its logs are also deleted

Test Case 1: Constraint Enforcement

-- Should FAIL: negative amount violates CHECK constraint
INSERT INTO payments (id, amount, customer_id, status)
VALUES (999, -50.00, 1, 'pending');

-- Expected result: Error "Check constraint failed" or similar
-- If the insert succeeds, you have found a bug!

Why this matters: If the database allows negative payments, refunds could be entered as negative debits, corrupting financial records.

Test Case 2: Cascade Delete

-- Insert a payment with a log
INSERT INTO payments (id, amount, customer_id) VALUES (100, 50.00, 1);
INSERT INTO payment_logs (id, payment_id) VALUES (200, 100);

-- Verify both exist
SELECT * FROM payments WHERE id = 100; -- Should return 1 row
SELECT * FROM payment_logs WHERE payment_id = 100; -- Should return 1 row

-- Delete the payment
DELETE FROM payments WHERE id = 100;

-- Verify BOTH are gone (cascade worked)
SELECT * FROM payments WHERE id = 100; -- Should return 0 rows
SELECT * FROM payment_logs WHERE payment_id = 100; -- Should return 0 rows

-- If the log still exists, cascade delete is not configured!

Why this matters: If cascade deletes don't work, you can create orphaned logs that never get cleaned up, eventually bloating the database and confusing audit trails.

8 Self-Check — Can You Actually Do This?

Click each question. If you answer all three correctly, you can test data integrity at senior level.

Q1. What is the difference between a constraint (e.g., NOT NULL, CHECK) and application-level validation? Which is more important?

A constraint is enforced at the database layer and cannot be bypassed, even if the application code is patched, bypassed, or has a bug. Application validation is enforced by the code and can be circumvented. Both are important: application validation provides user feedback, database constraints prevent corrupt data from ever entering the system. A mature system has both.

Q2. You are testing a stored procedure that updates a balance and inserts an audit log. The balance update succeeds but the audit log fails. What should happen?

Both should roll back. The stored procedure should wrap both operations in a transaction. If the audit fails, the entire transaction (including the balance update) must be undone. Otherwise, you have a financial record (balance changed) with no audit trail, which violates compliance requirements. Test this explicitly by forcing the audit insert to fail and verifying the balance reverted.

Q3. You are testing a schema migration that renames a column. What must you verify before it goes to production?

Three things: (1) Reversibility: Can you roll back the change? (2) API compatibility: Do all APIs that reference the old column name still work, or will they break? (3) Scale: Does the migration complete in acceptable time on production-scale data (millions of records)? Test on a replica, not dev. If APIs break, implement a transition period where both old and new names work.

9 Interview Prep — Senior Questions

Q. "How do you test data integrity in a system with triggers and stored procedures?"

I test them directly at the database layer, not through the application. I write SQL that calls the stored procedures and verifies the results. I also test triggers by inserting/updating/deleting records and checking that side effects occur (audit logs created, balances updated, etc.). I pay special attention to error cases: what happens if the trigger fails? Does the entire transaction roll back, or does the main operation succeed and the trigger silently fail?

Q. "You discover that a schema migration will block production for 2 hours. What do you do?"

I work with the DBA to find an alternative: (1) run the migration in parallel on a read replica, (2) use online schema tools (e.g., pt-online-schema-change) that allow reads and writes during migration, or (3) break the migration into smaller steps that can be rolled back quickly if they fail. If none of these options work, I recommend a maintenance window. The key is testing the migration on production-scale data first to discover the time impact before it happens live.

Q. "How do you ensure data consistency when the system spans multiple databases or microservices?"

It's hard. I test for eventual consistency: data may be inconsistent for a few seconds or minutes, but it converges to a consistent state. I verify: (1) idempotency—operations can be retried without duplication, (2) audit trails—every change is logged so inconsistencies can be detected and corrected, and (3) reconciliation jobs—background processes that detect and fix inconsistencies. I also test failure scenarios: what if one database succeeds and the other fails? How do we recover?

Q. "What are the NZ Privacy Act 2020 implications for database testing?"

The Privacy Act requires deletion of personal data when no longer needed. I test both soft deletes (mark as deleted, keep for audit) and hard deletes (actually remove). I verify that queries don't return soft-deleted data and that deletion actually removes personal information from backups. For financial data, I balance the need for deletion with audit trail requirements. I also test data minimization: systems should not collect more data than necessary.