Testability and Shift-Left Testing Questions
Designing software to be testable and moving quality earlier in the lifecycle. Covers testability and design review, shift-left practices, collaborating with developers on quality at design time, and improving testability of existing code. Emphasizes preventing defects rather than only catching them.
In XCTest, JUnit, or Jest, how do you typically mock or stub network calls and persistence layers? Walk through the trade-offs between mocks, stubs, and fakes, and explain when each is appropriate for mobile tests.
Sample Answer
I usually separate the problem into two concerns: network I/O and persistence.
For network calls, I prefer a stub or fake server over a pure mock when I want realistic request/response behavior. In iOS that often means abstracting the client behind a protocol and stubbing the transport; in Android/Jest-style tests, I’ll use a fake repository or a local test server. For persistence, an in-memory database or fake store is usually better than mocking every DAO method, because it exercises real reads, writes, and queries.
Trade-offs:
- Mocks: best for verifying interactions, but they can make tests brittle if you over-specify call order.
- Stubs: good for fixed responses and deterministic tests, but they don’t model state changes.
- Fakes: great for higher-confidence tests because they behave like a simplified real dependency, but they take more effort to build.
My rule is: use mocks sparingly for collaboration boundaries, use stubs for simple branching, and use fakes or in-memory implementations when the behavior itself matters. That keeps tests fast, readable, and closer to production behavior.
Design a global test scheduler that assigns thousands of tests to multiple Kubernetes clusters based on resource requirements, affinity, cache locality of container images, priority rules, and per-team quotas. Ensure tests run isolated, secrets are injected securely, and cleanup is robust even if nodes fail mid-run.
Sample Answer
Requirements (clarify):
- Functional: schedule thousands of test jobs across multiple K8s clusters considering CPU/GPU/memory, affinity, image cache locality, priority, per-team quotas, isolation, secrets injection, reliable cleanup.
- Non-functional: multi-region scale, low latency scheduling, high availability, observability, SLOs for job start/completion.
High-level architecture:
- Global Scheduler API (stateless, HA) + Scheduler Workers (placement logic)
- Cluster Agents (one per Kubernetes cluster) exposing cluster capacity, image cache metadata, node attributes, and executing assigned jobs
- Central State Store (etcd or CockroachDB) for job queue, quotas, and leases
- Matching Engine (in Scheduler Workers) + Scoring service
- Secrets Broker (short-lived credentials) integrated with KMS/Vault
- Controller in-cluster to create Namespaces/Pods from templates
- Cleanup Controller + Garbage Collector + Node-failure reconciler
- Observability: metrics, tracing, audit logs, alerts
Placement flow:
- Client submits job with resource spec, affinities, priority, team.
- Scheduler writes job to central store, evaluates eligible clusters by:
- Resource availability (from Cluster Agent)
- Image cache locality score (agent reports image layers cached per node/pool)
- Affinity/anti-affinity constraints and node labels
- Team quota and priority preemption rules
- Matching Engine computes scores (weighted: quota compliance, cache locality, resource fit, proximity, priority). Selects cluster + preferred node pool and issues a lease with TTL.
- Cluster Agent receives assignment, creates isolated Namespace, ServiceAccount, NetworkPolicy, and uses PodSecurityPolicies/PSA for runtime limits.
- Secrets Broker mints short-lived secrets injected via CSI Secrets Store or projected volumes; no long-lived cluster secrets.
- Pod is created with imagePullSecrets referencing the broker token; readiness and liveness probes added.
Isolation & security:
- Per-job Namespace, ResourceQuota, NetworkPolicy restricting egress/ingress.
- Run pods as non-root, use seccomp/AppArmor, enable rootless containers for untrusted tests.
- Use K8s PodSecurity admission + OPA/Gatekeeper policies.
- Secrets delivered via CSI driver with KMS-backed encryption and automatic TTL rotation; secrets never stored in central DB.
Cleanup & failure robustness:
- Assignments use leases; if Cluster Agent misses heartbeats, lease expires and job marked for reschedule.
- In-cluster Cleanup Controller watches Namespaces and enforces finalizers; when node fails, a reclamation process:
- Mark job as orphaned, attempt to read persistent artifacts (PVC, logs) via CSI snapshot; snapshot to central artifact store.
- Reschedule new job instance if job is idempotent or supports checkpoint restore.
- Use node-level 'ephemeral' label for cache locality; cluster agent evicts and drains to allow deterministic cleanup.
- Global GC runs TTL-based namespace deletion, and a safety window to allow artifact upload; audit logs stored separately.
Scalability & performance:
- Shard scheduling by job queue shards; use consistent hashing to assign jobs to Scheduler Workers.
- Cluster Agents push capacity deltas (push model) and heartbeat; reduce polling.
- Caching layer for image-locality metrics aggregated per node-pool; update frequency tuned.
Observability & SLOs:
- Metrics: queue length, scheduling latency, start latency, success rate per team.
- Alerts on quota exhaustion, spike in orphaned jobs, cluster capacity saturation.
- SLA: 95% jobs scheduled within X seconds; automated autoscaling of clusters based on pending workload.
Trade-offs:
- Strong consistency via central store simplifies quotas but adds latency; can relax to eventually-consistent for throughput.
- Preferring image-local clusters reduces cold pulls but may increase imbalance; scoring weights tunable.
- Secrets-as-a-service adds complexity but reduces blast radius.
Implementation notes:
- Use Go for scheduler and agents. Use gRPC between components. Store artifacts in S3-compatible store. Use Vault with short-lived Kubernetes tokens and CSI driver for secret injection. Use Kubernetes Job/Pod APIs and CRD for Job metadata.
Create a pragmatic, low-risk plan to instrument a legacy Python app with minimal code changes to improve testability and observability. Include a plan for wrappers, decorator-based input validation, adding a health endpoint, basic metrics for latency and error rates, structured logging, and an incremental migration strategy with rollback considerations.
Sample Answer
Situation: You inherit a monolithic legacy Python app with no observability or tests. Goal: add low-risk instrumentation with minimal code changes to improve testability, latency/error metrics, and deploy incrementally with safe rollback.
Plan (phased, low-risk):
- Prep: add lightweight libraries to requirements: prometheus_client, structlog (or python-json-logger), pydantic (optional).
- Non-invasive wrapper/decorator layer: introduce utilities module instrumentation.py and apply selectively.
Example: decorator-based input validation + metrics + error capture
# instrumentation.py
from prometheus_client import Summary, Counter
from functools import wraps
import structlog
from pydantic import validate_arguments, ValidationError
latency = Summary('handler_latency_seconds', 'Latency of handlers', ['handler'])
errors = Counter('handler_errors_total', 'Handler errors', ['handler'])
log = structlog.get_logger()
def instrument(handler_name):
def decorator(fn):
@wraps(fn)
@validate_arguments # pydantic input validation, non-intrusive
def wrapper(*args, **kwargs):
with latency.labels(handler_name).time():
try:
return fn(*args, **kwargs)
except Exception:
errors.labels(handler_name).inc()
log.exception("handler_error", handler=handler_name)
raise
return wrapper
return decorator
- Health endpoint (minimal Flask example):
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST, CollectorRegistry
from flask import Flask, Response
app = Flask(__name__)
@app.route('/health')
def health():
# quick checks: DB ping, disk, config flag - keep cheap and fast
return {'status':'ok'}, 200
@app.route('/metrics')
def metrics():
return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)
-
Structured logging: configure structlog to emit JSON to stdout for centralized ingestion; include request-id, trace-id. Non-invasive: replace top-level logger config only.
-
Incremental migration strategy:
- Start in staging: instrument 5% of routes or background jobs (use feature flag or decorator applied to limited modules).
- Canary in prod: deploy to 10% instances with config toggles (env var to enable instrumentation).
- Observe metrics (latency/error spikes), logs, and resource use for several days.
- Rollback and safety:
- Instrumentation is read-only except for added latency metrics; ensure decorators are cheap — validate_arguments can be toggled off by env var to avoid overhead.
- Use feature flags and config to disable instrumentation globally; deployment rollback plan identical to normal release rollback.
- Add circuit-breaker (timeout) around heavy validation if needed.
- Testability improvements:
- Wrap external calls (DB, HTTP) with small wrappers to allow injection/mocking.
- Add unit tests for decorators by calling decorated functions directly; add integration tests hitting /health and /metrics.
Why this is low-risk:
- Changes localized to new utilities module and decorator annotations; original business logic untouched.
- Feature flags and small canaries allow quick rollback.
- Observability uses standard Prometheus/structured logging stack.
Metrics/SLOs to define: p50/p95 latency per handler, error rate per minute, health check uptime. Set alerts on error budget breach and latency P95 regression.
For a mobile feature that also needs to be usable with VoiceOver or TalkBack, how do you incorporate accessibility checks into your testing strategy? What can be validated automatically, and what still needs manual verification?
Sample Answer
I’d test accessibility at three levels: semantic checks, interaction checks, and real screen-reader verification.
What I can automate:
- Every interactive element has a meaningful label, hint, or content description.
- Focusable elements are exposed in a sensible order.
- Buttons and controls have the right traits and are large enough to tap.
- State changes are announced or at least reflected in accessible text.
- Accessibility scanners can catch obvious issues like missing labels or low-contrast text.
What still needs manual review:
- Whether VoiceOver or TalkBack reads the screen naturally.
- Whether the focus order matches the visual and task flow.
- Whether dynamic updates, modal dialogs, and errors are announced at the right time.
- Whether the feature is usable with only the screen reader, no sighted assistance.
I’d build the automated checks into CI and include accessibility in the definition of done, but I’d still do at least one manual pass on a real device before release. Automation finds regressions; humans catch usability problems.
Explain a consumer-driven contract testing workflow between service A and service B using tools like Pact. Describe how contracts are published, how providers verify contracts in their CI, and how SREs can integrate contract verification to reduce regressions and enable safer rollouts.
Sample Answer
Requirements:
- Consumer (Service A) defines expected interactions with Provider (Service B): request/response pairs, headers, status codes, and provider states (pact “provider states” or setup hooks).
- Non-functional: fast CI verification, signed/pinned contract versions, traceability to deployments.
Workflow summary:
- Consumer authoring & tests
- Service A’s unit/integration tests use Pact consumer libraries to record interactions against a mock provider.
- Tests assert behaviour; on success a pact file (JSON) is generated containing the contract and metadata (consumer version, branch, tags).
- Publishing contracts
- CI for Service A publishes the pact file to a central Pact Broker (or artifact store) with metadata: consumer version, build id, git commit, tags (e.g., branch, env).
- Optionally sign or checksum pacts for tamper-proofing.
- Provider verification in CI
- Service B’s CI pulls relevant pacts from the broker (e.g., all pacts for the consumer’s “main” tag or specific version).
- Provider CI runs Pact provider verification: spins up Service B (container) in a test env, sets provider states (via setup endpoints or DB fixtures), and verifies each interaction against real endpoints.
- On success, provider CI publishes verification results back to the broker (pass/fail, provider version).
- Failures block builds and create precise failure reports showing which interaction or provider state broke.
- SRE integration for reliability & safer rollouts
- Gate deployments: incorporate pact verification status as a gating signal in deployment pipelines (e.g., require successful verification for combining feature branch to main or for Canary -> Prod promotion).
- Canary/Progressive rollouts: use consumer-driven contracts to run live canary verification—deploy B Canary, run contract checks against live canary endpoints, abort or roll back if mismatches occur.
- Monitoring & alerting: map contract endpoints to SLOs and create synthetic checks that exercise crucial contract interactions; alert on contract verification failures or increased error rates from synthetic runs.
- Incident response & observability: include pact metadata (consumer version, pact version) in logs and traces to quickly identify incompatible consumer/provider pairs during incidents.
- Automation: auto-trigger provider verifications when new pacts are published; auto-tag verification results to reflect compatibility matrix and enable quick rollbacks.
- Chaos / resilience testing: combine contract verification with fault-injection to ensure graceful degradation for expected failure responses.
Benefits:
- Shift-left API compatibility checks, earlier detection of regressions, clearer blame (consumer vs provider), safer canary/blue-green rollouts, and improved runbook automation for SREs.
Unlock Full Question Bank
Get access to all Testability and Shift-Left Testing interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.