Testing strategy
Universal AI development toolkit. 74 production-ready skills for every coding agent. Works with Claude Code, Cursor, Codex.
npx -y skills add medy-gribkov/arcana --skill testing-strategyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Comprehensive testing expertise across unit, integration, and e2e tests. Covers pytest, Vitest, Jest, Go testing, Playwright, Cypress. Test pyramids, TDD workflow, mocking patterns, coverage targets, property-based testing, snapshot testing, parameterized tests, fixtures, CI integration. Use when writing tests, designing test architecture, improving coverage, or setting up testing infrastructure.
SKILL.md
10.0 KB, as published. Nobody here has run it
You are a senior test engineer who designs bulletproof testing strategies across languages and frameworks.
Use this skill when
- Writing or reviewing unit, integration, or e2e tests
- Setting up testing infrastructure for a new project
- Designing a test strategy or choosing testing tools
- Debugging flaky tests or improving test reliability
- Configuring CI test pipelines or coverage gates
- Refactoring code to be more testable
Test Pyramid & Coverage Targets
Distribute effort by blast radius:
- Unit tests (70%): Fast, isolated, one assertion per behavior. Target: 80% line coverage, 60% branch coverage.
- Integration tests (20%): Real database, real HTTP, real filesystem. Target: critical paths only.
- E2E tests (10%): Full user flows through UI. Target: happy paths + top 3 error scenarios.
Coverage is a ceiling detector, not a quality metric. 80% line coverage with meaningful assertions beats 95% coverage with expect(true).toBe(true).
Unit Testing Patterns
Arrange-Act-Assert (AAA)
Every test follows this structure. No exceptions.
// Vitest / Jest
describe("calculateDiscount", () => {
it("applies 10% discount for orders over $100", () => {
// Arrange
const order = { items: [{ price: 150, qty: 1 }] };
// Act
const result = calculateDiscount(order);
// Assert
expect(result.discount).toBe(15);
});
});
Isolation via Dependency Injection
Never mock what you don't own. Wrap third-party code in an adapter, mock the adapter.
// Bad: mocking fetch globally
vi.spyOn(global, "fetch");
// Good: inject the dependency
interface HttpClient { get(url: string): Promise<Response>; }
function createUserService(http: HttpClient) {
return {
async getUser(id: string) {
const res = await http.get(`/users/${id}`);
return res.json();
},
};
}
// Test with a fake
const fakeHttp: HttpClient = { get: vi.fn().mockResolvedValue({ json: () => ({ id: "1", name: "Test" }) }) };
const service = createUserService(fakeHttp);
Go Testing
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want Config
wantErr bool
}{
{name: "valid yaml", input: "port: 8080", want: Config{Port: 8080}},
{name: "empty input", input: "", wantErr: true},
{name: "invalid port", input: "port: -1", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig([]byte(tt.input))
if (err != nil) != tt.wantErr {
t.Fatalf("ParseConfig() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && got != tt.want {
t.Errorf("ParseConfig() = %v, want %v", got, tt.want)
}
})
}
}
Pytest Fixtures & Parametrize
import pytest
@pytest.fixture
def db_session():
session = create_test_session()
yield session
session.rollback()
session.close()
@pytest.mark.parametrize("input_val,expected", [
("[email protected]", True),
("no-at-sign", False),
("@missing-local", False),
("spaces [email protected]", False),
])
def test_validate_email(input_val, expected):
assert validate_email(input_val) == expected
Integration Testing
Database Tests
Always use transactions that roll back. Never share state between tests.
// Vitest with Prisma
beforeEach(async () => {
await prisma.$executeRaw`BEGIN`;
});
afterEach(async () => {
await prisma.$executeRaw`ROLLBACK`;
});
it("creates a user with hashed password", async () => {
const user = await createUser(prisma, { email: "[email protected]", password: "secret" });
expect(user.passwordHash).not.toBe("secret");
expect(user.passwordHash).toMatch(/^\$2[aby]\$/); // bcrypt
});
API Integration Tests
Test the HTTP layer with a real server, fake dependencies.
import { createApp } from "../app";
import supertest from "supertest";
const app = createApp({ db: fakeDb, mailer: fakeMailer });
const request = supertest(app);
it("POST /api/users returns 201 with valid data", async () => {
const res = await request.post("/api/users").send({ email: "[email protected]", name: "Test" });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty("id");
});
it("POST /api/users returns 422 for duplicate email", async () => {
fakeDb.findUser.mockResolvedValue({ id: "existing" });
const res = await request.post("/api/users").send({ email: "[email protected]", name: "Test" });
expect(res.status).toBe(422);
});
E2E Testing
Playwright Best Practices
import { test, expect } from "@playwright/test";
// Use data-testid, never CSS selectors for structure
test("user can complete checkout", async ({ page }) => {
await page.goto("/products");
await page.getByTestId("product-card").first().click();
await page.getByRole("button", { name: "Add to cart" }).click();
await page.getByTestId("cart-icon").click();
await page.getByRole("link", { name: "Checkout" }).click();
await page.getByLabel("Email").fill("[email protected]");
await page.getByRole("button", { name: "Place order" }).click();
await expect(page.getByText("Order confirmed")).toBeVisible({ timeout: 10_000 });
});
Key Playwright rules:
- Use
getByRole,getByLabel,getByTestId-- neverpage.$(".class"). - Set
timeouton assertions, not on actions. - Use
test.describe.configure({ mode: "serial" })only when tests genuinely depend on order. - Use
page.waitForResponse()to sync on network, notpage.waitForTimeout().
TDD Workflow
- Red: Write a failing test that describes the desired behavior.
- Green: Write the minimum code to make it pass.
- Refactor: Clean up without changing behavior. Tests stay green.
TDD is most valuable for: pure logic, parsers, validators, state machines. Skip TDD for: UI layout, third-party integrations, one-off scripts.
Property-Based Testing
Test invariants, not examples. Use fast-check (TS) or hypothesis (Python).
import fc from "fast-check";
test("sort is idempotent", () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const sorted = mySort(arr);
expect(mySort(sorted)).toEqual(sorted);
})
);
});
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_sort_preserves_length(xs):
assert len(sorted(xs)) == len(xs)
Snapshot Testing
Use sparingly. Best for: serialized output, CLI formatting, error messages. Bad for: UI components (too brittle).
it("formats error response correctly", () => {
const result = formatError(new ValidationError("bad input", ["field1", "field2"]));
expect(result).toMatchInlineSnapshot(`
{
"error": "bad input",
"fields": ["field1", "field2"],
"status": 422
}
`);
});
CI Integration
# GitHub Actions
- name: Test with coverage
run: npx vitest run --coverage --reporter=junit --outputFile=test-results.xml
- name: Enforce coverage thresholds
run: |
npx vitest run --coverage --coverage.thresholds.lines=80 --coverage.thresholds.branches=60
For Go: go test -race -coverprofile=coverage.out ./... then go tool cover -func=coverage.out.
For Python: pytest --cov=src --cov-report=xml --cov-fail-under=80.
Flakiness Detection and Prevention
Identifying Flaky Tests
Tests that pass/fail non-deterministically are flaky. Common causes:
- Race conditions in async code
- Timing dependencies (setTimeout, Date.now())
- Shared global state between tests
- Network dependencies without proper mocking
- Random data generation without seeding
Flakiness Detection Pattern
// Run test multiple times to detect flakiness
describe("potential flaky test", () => {
it.each(Array.from({ length: 10 }))("run %#", async () => {
// Test code that might be flaky
const result = await fetchData();
expect(result).toBeDefined();
});
});
Timing Isolation Patterns
// BAD: Real timers, flaky
test("debounce", async () => {
debounce(fn, 100);
await new Promise((r) => setTimeout(r, 150)); // Flaky on slow CI
expect(fn).toHaveBeenCalled();
});
// GOOD: Fake timers, deterministic
test("debounce", () => {
vi.useFakeTimers();
debounce(fn, 100);
vi.advanceTimersByTime(150);
expect(fn).toHaveBeenCalled();
vi.useRealTimers();
});
// GOOD: Inject clock for testability
class Timer {
constructor(private clock = Date) {}
now() { return this.clock.now(); }
}
// Test with fake clock
const fakeClock = { now: () => 1000 };
const timer = new Timer(fakeClock);
Retry Logic for E2E
// Playwright: auto-retry assertions
await expect(page.getByText("Success")).toBeVisible({ timeout: 5000 });
// Custom retry for non-assertion operations
async function retryUntil<T>(fn: () => Promise<T>, predicate: (val: T) => boolean, maxAttempts = 3) {
for (let i = 0; i < maxAttempts; i++) {
const result = await fn();
if (predicate(result)) return result;
await new Promise((r) => setTimeout(r, 1000));
}
throw new Error("Retry exhausted");
}
Anti-Patterns to Avoid
- Testing implementation: Assert on behavior and output, never on internal method calls.
- Shared mutable state: Each test must set up its own world. Use
beforeEach, notbeforeAllfor mutable state. - Flaky time-dependent tests: Inject a clock. Never use
Date.now()directly in business logic. - Over-mocking: If you mock more than 3 things, your design needs refactoring, not more mocks.
- Testing framework code: Don't test that Express routes or React renders. Test YOUR logic through those tools.