DevOps QA / Basic: delivery evidence and suite design / Lesson 5
Deterministic data, clocks, and environments
Make a failing CI case reproducible by owning every input that can change its outcome.
On this page
Lesson goals
- Control time, random input, and fixture ownership.
- Distinguish reproducibility from unrealistic over-mocking.
Inventory hidden inputs
The same source can produce different outcomes when locale, timezone, clock, random seed, dependency version, service response, database contents, or execution order changes. Record these inputs before blaming CI speed. Deterministic tests control or capture the inputs relevant to their claim. This does not mean replacing every real dependency with a fake; integration tests should preserve the behavior they are intended to verify. The important question is whether a maintainer can recreate the condition that produced the failure without guessing which shared account or midnight boundary happened to be involved.
Inject the business clock
Expiration rules often fail near boundaries because tests read wall-clock time at several moments. Pass a clock or instant into the rule so the case can choose exactly before, at, and after expiration. Define whether equality means expired and which timezone applies when parsing user dates. Use a monotonic clock for elapsed durations, not calendar time. The example isolates an expiration decision; integration tests still need to verify that stored timestamps and transport serialization preserve the same meaning across components.
from datetime import datetime, timezone
def hold_expired(expires_at, now):
return now >= expires_at
expiry = datetime(2030, 1, 1, 12, 0, tzinfo=timezone.utc)
assert hold_expired(expiry, expiry) is True
assert hold_expired(expiry, expiry.replace(second=1)) is True
Uses Python standard-library timezone-aware datetimes and synthetic future dates. Add a separate before-expiry case in a real suite. Production parsing must reject or normalize ambiguous naïve datetimes rather than comparing them with timezone-aware values.
Give fixtures clear ownership and scope
A fixture should identify who may mutate it and when it is removed. A shared global booking account creates coupling even if browser sessions are isolated. Allocate a run namespace and a case-specific identity, create only the records needed, and clean by exact identity. Cleanup must be idempotent and must not delete another run’s data. A test transaction can help when all relevant work stays inside it; background workers or separate connections can escape that boundary. Choose isolation according to the actual architecture rather than assuming rollback covers every write.
Use variation without losing reproduction
Generated inputs can explore more combinations than a handful of examples. Capture the seed and minimized failing input so a discovered failure becomes reproducible. A fixed seed makes a particular sequence repeatable within the chosen generator and version; it is not a universal guarantee across language implementations. Keep a stable regression case for important discoveries and add controlled seed variation separately. Do not use random identifiers as the only collision defense across distributed workers. Namespace ownership and uniqueness constraints are stronger than hoping two seeds never meet.
import random
def generated_requests(seed):
rng = random.Random(seed)
return [rng.randint(0, 6) for _ in range(8)]
seed = 417
requests = generated_requests(seed)
assert requests == generated_requests(seed)
The local Random instance avoids altering global generator state used by other tests. A real report should record seed, generator version, and failing input. This example tests reproducibility of generation, not correctness of reservation handling.
Capture the environment contract
Record runtime and dependency versions, schema version, seed strategy, feature settings, locale, timezone, and service substitutes in a small environment manifest. Keep secrets out. Use locked dependencies and compatible browser binaries where the tooling requires them. Treat caches as performance aids, not sources of authoritative fixture data. A test that passes only with a warm cache reveals a missing input or setup step. Reproduce a case in a fresh controlled environment before calling it fixed, then check that the chosen realism still exercises the risk the suite was designed to address.
Common errors and fixes
A test fails only near midnight
- Why it happens
- The rule reads uncontrolled wall time or timezone.
- How to fix it
- Inject explicit instants and define boundary semantics.
- Prevent it next time
- Test before, at, and after the boundary.
Cleanup removes another run’s reservation
- Why it happens
- Records are deleted by a broad shared label.
- How to fix it
- Delete only ids owned by the current run and case.
- Prevent it next time
- Enforce namespace ownership in fixture helpers.
A generated failure cannot be reproduced
- Why it happens
- Only the random test name was logged.
- How to fix it
- Capture the seed and minimized failing data.
- Prevent it next time
- Promote important discoveries to stable regression cases.
Check your understanding
1. Two tests share one account but have separate browser contexts. What remains coupled?
Show worked solution
Server-side profile, permissions, bookings, and quotas can still collide. Allocate separate owned accounts or partition all mutable backend resources explicitly. Browser isolation only covers browser-side state; verify the data boundary through controlled parallel execution.
2. A rollback fixture works for requests but not queued jobs. Explain the missing isolation boundary.
Show worked solution
The job may use another connection or execute after the transaction ends. Give it a dedicated database/schema or an owned namespace and wait for bounded completion before cleanup. Do not assume an outer test transaction encloses asynchronous work.
Key takeaways
- Determinism requires control or capture of relevant inputs.
- Isolation follows actual write boundaries.
- Realistic integration and reproducible setup should reinforce each other.