正在加载,请稍候…

API 测试:Postman 集合、k6 负载测试与契约测试

全面的 API 测试指南,涵盖 Postman 自动化、k6 负载测试、Pact 契约测试以及 CI/CD 流水线中的 API 测试最佳实践。

API Testing: Postman Collections, k6 Load Testing, and Contract Testing

API 测试:完整指南

Postman 集合与自动化

// Postman pre-request script
pm.environment.set("timestamp", Date.now())
pm.environment.set("request_id", pm.variables.replaceIn("{{$randomUUID}}"))

// HMAC authentication
const crypto = require('crypto-js')
const secret = pm.environment.get("API_SECRET")
const body = pm.request.body.raw || ''
const signature = crypto.HmacSHA256(body, secret).toString()
pm.request.headers.add({key: 'X-Signature', value: signature})

// Postman test script
pm.test("Status is 200", () => pm.response.to.have.status(200))
pm.test("Response time < 500ms", () => pm.expect(pm.response.responseTime).to.be.below(500))

pm.test("Response has required fields", () => {
  const json = pm.response.json()
  pm.expect(json).to.have.property('id')
  pm.expect(json).to.have.property('email')
  pm.expect(json.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
})

// Store response for use in subsequent requests
pm.test("Store user ID", () => {
  const json = pm.response.json()
  pm.environment.set("userId", json.id)
})

API Testing: Postman Collections, k6 Load Testing, and Contract Testing illustration

Newman CLI(Postman 自动化)

# Install
npm install -g newman

# Run collection
newman run MyAPI.postman_collection.json \
  --environment prod.postman_environment.json \
  --reporters cli,junit \
  --reporter-junit-export results.xml

# With data file (data-driven testing)
newman run MyAPI.postman_collection.json \
  --data test-data.json \
  --environment staging.json

# GitHub Actions integration
# - uses: actions/checkout@v4
# - run: npm install -g newman
# - run: newman run tests/api.collection.json -e tests/staging.env.json

API Testing: Postman Collections, k6 Load Testing, and Contract Testing illustration

k6 负载测试

// k6/load-test.js
import http from 'k6/http'
import { check, sleep, group } from 'k6'
import { Rate, Trend, Counter } from 'k6/metrics'

// Custom metrics
const errorRate = new Rate('errors')
const loginDuration = new Trend('login_duration')
const totalRequests = new Counter('total_requests')

export const options = {
  stages: [
    { duration: '30s', target: 10 },   // Ramp up
    { duration: '1m', target: 50 },    // Stay at 50 users
    { duration: '30s', target: 100 },  // Spike
    { duration: '30s', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],  // 95% under 500ms
    errors: ['rate<0.01'],             // < 1% errors
    http_req_failed: ['rate<0.01'],
  },
}

const BASE_URL = 'https://api.example.com'

export function setup() {
  // Login and get token
  const resp = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
    email: 'test@example.com',
    password: 'password123',
  }), { headers: { 'Content-Type': 'application/json' } })
  
  return { token: resp.json('access_token') }
}

export default function(data) {
  const headers = {
    'Authorization': `Bearer ${data.token}`,
    'Content-Type': 'application/json',
  }

  group('User Operations', () => {
    // GET users
    const listResp = http.get(`${BASE_URL}/users`, { headers })
    check(listResp, {
      'list status 200': (r) => r.status === 200,
      'list has users': (r) => r.json('data').length > 0,
    })
    errorRate.add(listResp.status !== 200)
    totalRequests.add(1)

    sleep(0.5)

    // Create user
    const start = Date.now()
    const createResp = http.post(`${BASE_URL}/users`,
      JSON.stringify({ name: 'Test User', email: `test${Date.now()}@example.com` }),
      { headers }
    )
    loginDuration.add(Date.now() - start)

    check(createResp, {
      'create status 201': (r) => r.status === 201,
    })
  })

  sleep(1)
}

export function teardown(data) {
  // Cleanup after test
  console.log('Test complete')
}
# Run k6 test
k6 run k6/load-test.js

# Run with cloud
k6 cloud k6/load-test.js

# Generate HTML report
k6 run --out json=results.json k6/load-test.js
k6 report results.json > report.html

API Testing: Postman Collections, k6 Load Testing, and Contract Testing illustration

使用 Pact 进行契约测试

// Consumer test - defines contract
import { Pact } from '@pact-foundation/pact'
import { fetchUser } from '../src/api/userService'

const provider = new Pact({
  consumer: 'frontend-app',
  provider: 'user-api',
  port: 8080,
  dir: './pacts',
})

describe('User API Contract', () => {
  beforeAll(() => provider.setup())
  afterAll(() => provider.finalize())

  it('should fetch user by ID', async () => {
    await provider.addInteraction({
      state: 'User 123 exists',
      uponReceiving: 'a request for user 123',
      withRequest: {
        method: 'GET',
        path: '/users/123',
        headers: { Accept: 'application/json' },
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: '123',
          name: like('John Doe'),
          email: like('john@example.com'),
          createdAt: like('2026-01-01T00:00:00Z'),
        },
      },
    })

    const user = await fetchUser('123')
    expect(user.id).toBe('123')
    expect(user.email).toBeTruthy()
  })
})

受 REST-assured 启发的测试(Node.js)

import request from 'supertest'
import app from '../src/app'

describe('Users API Integration Tests', () => {
  let authToken: string
  let createdUserId: string

  beforeAll(async () => {
    const resp = await request(app)
      .post('/auth/login')
      .send({ email: 'admin@test.com', password: 'admin123' })
    authToken = resp.body.access_token
  })

  it('creates a user', async () => {
    const resp = await request(app)
      .post('/users')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ name: 'Test User', email: 'newuser@test.com' })
      .expect(201)
      .expect('Content-Type', /json/)

    expect(resp.body.id).toBeDefined()
    expect(resp.body.email).toBe('newuser@test.com')
    createdUserId = resp.body.id
  })

  it('returns 404 for missing user', async () => {
    await request(app)
      .get('/users/nonexistent-id')
      .set('Authorization', `Bearer ${authToken}`)
      .expect(404)
      .expect(resp => expect(resp.body.error).toBe('User not found'))
  })
})

API 测试金字塔

层级 工具 速度 覆盖范围
单元测试 Jest/Vitest 逻辑
集成测试 Supertest 端点
契约测试 Pact 契约
端到端测试 Playwright 流程
负载测试 k6 可变 性能