Q34 of 37 · API testing
How would you build performance tests on top of your existing functional API test framework?
Short answer
Short answer: Reuse the functional auth, fixtures, and helpers, but call them from a load tool (k6, Locust, JMeter). Treat performance as a separate suite with separate SLAs (p95 < 500ms, error rate < 0.1%). Don't reuse functional assertions — performance tests assert on response time, not response correctness.
Detail
Functional and performance tests share request-shaping logic (auth, payloads) but diverge on assertion intent: functional asserts on correctness, performance on response time and throughput. Building on the functional suite saves duplicate work without conflating concerns.
The reuse boundary:
- Reuse: auth helpers, fixture builders, request payloads, base URLs.
- Don't reuse: functional assertions, per-test setup/teardown.
In Playwright + k6:
// shared/payloads.ts — used by both
export const sampleOrderPayload = (suffix: number) => ({
items: [{ sku: 'SKU-1', qty: 1 }],
customerEmail: `load-${suffix}@test`,
});
// k6 script
import http from 'k6/http';
import { check } from 'k6';
import { sampleOrderPayload, getToken } from './shared/load.js';
export const options = {
scenarios: {
steady: { executor: 'constant-arrival-rate', rate: 100, duration: '5m', preAllocatedVUs: 50 },
},
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.001'],
},
};
export function setup() {
return { token: getToken() };
}
export default function (data) {
const res = http.post(`${__ENV.BASE_URL}/orders`,
JSON.stringify(sampleOrderPayload(__VU)), {
headers: {
Authorization: `Bearer ${data.token}`,
'Content-Type': 'application/json',
},
});
check(res, { '201': (r) => r.status === 201 });
}
SLAs as test thresholds:
- p95 latency < 500ms.
- p99 latency < 1000ms.
- Error rate < 0.1%.
- Throughput at target load >= 100 req/s.
If thresholds fail, k6 exits non-zero; CI fails the build. This makes performance regressions detectable.
Test scenarios to run:
- Steady load — sustained target rate for 5-10 minutes. Catches "fine for 1 minute, dies at 5."
- Ramp-up — 0 → target over 5 minutes. Catches scale-out / caching cold-start issues.
- Spike — instant 5x load. Tests rate limits, queue depth, autoscaling response.
- Soak — target load for 1 hour. Catches memory leaks, connection pool exhaustion.
- Stress — increase load until failure. Tells you the actual ceiling.
What to monitor while tests run:
- Server-side metrics (CPU, memory, DB connections, queue depth).
- Database query times, slow query log.
- Cache hit rates.
- Tail latency (p99.9, max) — averages hide everything important.
Where to run:
- Dedicated perf environment, sized like production.
- Don't run perf tests against shared staging — you'll affect other tests.
- Don't run against prod unless you have a clear plan and capacity headroom.
Cadence:
- Smoke perf test (1 minute, low load) on PR — catches obvious regressions.
- Full perf suite weekly or before release.
- Soak tests monthly.
Anti-patterns:
- Asserting on individual response correctness in load tests. The point is throughput; correctness is the functional suite's job.
- Running perf tests with too few virtual users — bottlenecks shift. 1 user shows server CPU; 100 users show queueing.
- Treating perf as one-off ("we did a perf test in Q1"). Performance regresses silently; periodic re-runs are non-negotiable.
The senior signal: reusing functional building blocks, separating concerns (functional = correctness; perf = throughput), and treating thresholds as build-blocking.