Install
openclaw skills install testing-plusEnhanced testing with framework-specific templates, CI/CD integration, mutation testing, and performance testing. Covers unit, integration, E2E, and security testing.
openclaw skills install testing-plusEnhanced testing with framework templates, CI/CD integration, and advanced testing strategies.
| Test Type | Tool | Purpose |
|---|---|---|
| Unit | Jest, Vitest, pytest | Individual functions |
| Integration | Supertest, Testcontainers | Module boundaries |
| E2E | Playwright, Cypress | Full user workflows |
| Mutation | Stryker, mutmut | Test quality verification |
| Performance | k6, Artillery | Load testing |
| Security | OWASP ZAP, Snyk | Vulnerability detection |
| Level | Ratio | Speed | Cost | Confidence |
|---|---|---|---|---|
| Unit | ~70% | ms | Low | Low |
| Integration | ~20% | seconds | Medium | Medium |
| E2E | ~10% | minutes | High | High |
// jest.config.js
export default {
preset: 'ts-jest',
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
// src/utils.test.ts
import { calculateTotal } from './utils';
describe('calculateTotal', () => {
it('should calculate total with tax', () => {
const items = [{ price: 10, qty: 2 }, { price: 5, qty: 1 }];
const taxRate = 0.08;
expect(calculateTotal(items, taxRate)).toBe(27.0);
});
it('should handle empty items', () => {
expect(calculateTotal([], 0.08)).toBe(0);
});
});
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});
// src/utils.test.ts
import { describe, it, expect } from 'vitest';
import { calculateTotal } from './utils';
describe('calculateTotal', () => {
it('should calculate total with tax', () => {
const items = [{ price: 10, qty: 2 }, { price: 5, qty: 1 }];
expect(calculateTotal(items, 0.08)).toBe(27.0);
});
});
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --cov=src --cov-report=term-missing
# tests/test_utils.py
import pytest
from src.utils import calculate_total
def test_calculate_total_with_tax():
items = [{"price": 10, "qty": 2}, {"price": 5, "qty": 1}]
assert calculate_total(items, 0.08) == 27.0
def test_calculate_total_empty():
assert calculate_total([], 0.08) == 0
@pytest.mark.parametrize("items,tax_rate,expected", [
([{"price": 10, "qty": 1}], 0.1, 11.0),
([{"price": 20, "qty": 2}], 0.05, 42.0),
])
def test_calculate_total_parametrized(items, tax_rate, expected):
assert calculate_total(items, tax_rate) == expected
// utils_test.go
package utils
import "testing"
func TestCalculateTotal(t *testing.T) {
tests := []struct {
name string
items []Item
taxRate float64
expected float64
}{
{
name: "with tax",
items: []Item{{Price: 10, Qty: 2}, {Price: 5, Qty: 1}},
taxRate: 0.08,
expected: 27.0,
},
{
name: "empty items",
items: []Item{},
taxRate: 0.08,
expected: 0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := CalculateTotal(tc.items, tc.taxRate)
if got != tc.expected {
t.Errorf("CalculateTotal() = %f, want %f", got, tc.expected)
}
})
}
}
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
# .gitlab-ci.yml
test:
image: node:20
stage: test
script:
- npm ci
- npm test
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
# Install
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
# Run
npx stryker run
// stryker.conf.json
{
"$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker/master/packages/core/schema/stryker-core.json",
"packageManager": "npm",
"reporters": ["html", "clear-text", "progress"],
"testRunner": "jest",
"jestProjectFile": "jest.config.ts",
"mutate": ["src/**/*.ts", "!src/**/*.test.ts"],
"thresholds": {
"high": 80,
"low": 60,
"break": 50
}
}
# Install
pip install mutmut
# Run
mutmut run
# View results
mutmut results
mutmut html
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 20 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
# load-test.yml
config:
target: "https://api.example.com"
phases:
- duration: 60
arrivalRate: 10
- duration: 120
arrivalRate: 50
- duration: 60
arrivalRate: 10
scenarios:
- name: "Get users"
flow:
- get:
url: "/users"
expect:
- statusCode: 200
- contentType: json
# Docker scan
docker run -t owasp/zap2docker-stable zap-baseline.py \
-t https://example.com \
-r report.html
# npm audit
npm audit
npm audit fix
# Snyk
npx snyk test
npx snyk monitor
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Testing implementation | Tests break on refactor | Test behavior |
| Flaky tests | Non-deterministic failures | Remove time/order dependencies |
| Overmocking | Tests nothing real | Only mock boundaries |
| No tests for bugs | Regression reappears | Add regression tests |