mcpField
A
api-tester

api-tester

github.com/curiouslearner测试与质量2943 次安装53 周前更新
代码仓库

关于 api-tester

api-tester 是面向 API 开发与测试的技能,涵盖端点验证、测试用例生成、性能和安全检查,并提供多种语言的请求示例。

主要功能

  1. 01验证 HTTP 方法与响应
  2. 02生成 curl、Postman 和自动化测试
  3. 03执行负载与响应时间测试
  4. 04检查认证、输入和常见安全问题
  5. 05覆盖 REST 与 GraphQL API

适用场景

  1. 01上线前验证端点和响应结构
  2. 02生成可复用的 API 测试用例
  3. 03检查速率限制与超时处理
  4. 04排查认证及输入验证问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/curiouslearner/devkit --skill api-tester

安装后在 Claude Code 中输入/api-tester确认已加载。

详细介绍

API 测试技能

快速测试 API 端点,并全面验证请求和响应。

说明

你是一名 API 测试专家。调用时:

  1. 测试 API 端点:

    • 验证 HTTP 方法(GET、POST、PUT、PATCH、DELETE)
    • 测试请求标头和正文格式
    • 验证响应状态码
    • 验证响应架构和数据类型
    • 检查身份验证和授权
  2. 生成测试用例:

    • 创建用于测试的 curl 命令
    • 生成 Postman 集合
    • 编写自动化测试脚本
    • 测试边缘情况和错误场景
    • 验证 API 契约
  3. 性能测试:

    • 使用并发请求进行负载测试
    • 对响应时间进行基准测试
    • 验证速率限制
    • 处理超时
    • 测试连接池
  4. 安全测试:

    • 检查身份验证和授权
    • 测试输入验证
    • 防止 SQL 注入
    • 防止 XSS
    • 检查 CORS 配置

使用示例

@api-tester
@api-tester --endpoint /api/users
@api-tester --method POST
@api-tester --load-test
@api-tester --generate-collection

REST API 测试

GET 请求示例
基本 GET 请求
# curl
curl -X GET https://api.example.com/api/users \
  -H "Content-Type: application/json"

# With authentication
curl -X GET https://api.example.com/api/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json"

# With query parameters
curl -X GET "https://api.example.com/api/users?page=1&limit=10&sort=created_at" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Verbose output (includes headers)
curl -v -X GET https://api.example.com/api/users
JavaScript/Node.js
// Using fetch
async function getUsers() {
  const response = await fetch('https://api.example.com/api/users', {
    method: 'GET',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const data = await response.json();
  return data;
}

// Using axios
const axios = require('axios');

async function getUsers() {
  try {
    const response = await axios.get('https://api.example.com/api/users', {
      headers: {
        'Authorization': 'Bearer YOUR_TOKEN'
      },
      params: {
        page: 1,
        limit: 10
      }
    });
    return response.data;
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    throw error;
  }
}
Python
import requests

# Basic GET request
response = requests.get('https://api.example.com/api/users')
print(response.json())

# With authentication and parameters
headers = {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json'
}

params = {
    'page': 1,
    'limit': 10,
    'sort': 'created_at'
}

response = requests.get(
    'https://api.example.com/api/users',
    headers=headers,
    params=params
)

if response.status_code == 200:
    data = response.json()
    print(data)
else:
    print(f"Error: {response.status_code}")
    print(response.text)
POST 请求示例
创建资源
# curl
curl -X POST https://api.example.com/api/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "[email protected]",
    "role": "user"
  }'

# From file
curl -X POST https://api.example.com/api/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d @user.json
JavaScript/Node.js
// Using fetch
async function createUser(userData) {
  const response = await fetch('https://api.example.com/api/users', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(userData)
  });

  const data = await response.json();
  return data;
}

// Usage
const newUser = {
  name: 'John Doe',
  email: '[email protected]',
  role: 'user'
};

createUser(newUser)
  .then(user => console.log('Created:', user))
  .catch(error => console.error('Error:', error));

// Using axios with error handling
async function createUser(userData) {
  try {
    const response = await axios.post(
      'https://api.example.com/api/users',
      userData,
      {
        headers: {
          'Authorization': 'Bearer YOUR_TOKEN'
        }
      }
    );
    return response.data;
  } catch (error) {
    if (error.response) {
      // Server responded with error
      console.error('Error:', error.response.status);
      console.error('Message:', error.response.data);
    } else if (error.request) {
      // No response received
      console.error('No response from server');
    } else {
      console.error('Error:', error.message);
    }
    throw error;
  }
}
Python
import requests

# Create user
user_data = {
    'name': 'John Doe',
    'email': '[email protected]',
    'role': 'user'
}

headers = {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json'
}

response = requests.post(
    'https://api.example.com/api/users',
    json=user_data,
    headers=headers
)

if response.status_code == 201:
    print('User created:', response.json())
else:
    print(f'Error: {response.status_code}')
    print(response.json())
PUT/PATCH 请求示例
# PUT - Replace entire resource
curl -X PUT https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Updated",
    "email": "[email protected]",
    "role": "admin"
  }'

# PATCH - Partial update
curl -X PATCH https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "admin"
  }'
DELETE 请求示例
# Delete resource
curl -X DELETE https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN"

# Delete with confirmation
curl -X DELETE https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Confirm-Delete: true"

身份验证示例

Bearer Token(JWT)
# Get token
curl -X POST https://api.example.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password123"
  }'

# Use token
curl -X GET https://api.example.com/api/users \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
API 密钥
# In header
curl -X GET https://api.example.com/api/users \
  -H "X-API-Key: your-api-key-here"

# In query parameter
curl -X GET "https://api.example.com/api/users?api_key=your-api-key-here"
Basic Auth
# Username and password
curl -X GET https://api.example.com/api/users \
  -u username:password

# Base64 encoded
curl -X GET https://api.example.com/api/users \
  -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ="
OAuth 2.0
// Get access token
async function getAccessToken() {
  const response = await fetch('https://oauth.example.com/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: 'YOUR_CLIENT_ID',
      client_secret: 'YOUR_CLIENT_SECRET'
    })
  });

  const data = await response.json();
  return data.access_token;
}

// Use access token
async function callAPI() {
  const token = await getAccessToken();

  const response = await fetch('https://api.example.com/api/users', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  return response.json();
}

GraphQL 测试

基本查询
# curl
curl -X POST https://api.example.com/graphql \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ users { id name email } }"
  }'

# With variables
curl -X POST https://api.example.com/graphql \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetUser($id: ID!) { user(id: $id) { id name email } }",
    "variables": { "id": "123" }
  }'
GraphQL 变更操作
// Create user mutation
async function createUser(name, email) {
  const query = `
    mutation CreateUser($name: String!, $email: String!) {
      createUser(input: { name: $name, email: $email }) {
        id
        name
        email
        createdAt
      }
    }
  `;

  const response = await fetch('https://api.example.com/graphql', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query,
      variables: { name, email }
    })
  });

  const data = await response.json();
  return data.data.createUser;
}

自动化测试

Jest 测试套件
const axios = require('axios');

describe('User API Tests', () => {
  const API_URL = 'https://api.example.com';
  const token = 'YOUR_TEST_TOKEN';

  const api = axios.create({
    baseURL: API_URL,
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  describe('GET /api/users', () => {
    test('should return list of users', async () => {
      const response = await api.get('/api/users');

      expect(response.status).toBe(200);
      expect(Array.isArray(response.data)).toBe(true);
      expect(response.data.length).toBeGreaterThan(0);
    });

    test('should return user by ID', async () => {
      const response = await api.get('/api/users/123');

      expect(response.status).toBe(200);
      expect(response.data).toHaveProperty('id', '123');
      expect(response.data).toHaveProperty('name');
      expect(response.data).toHaveProperty('email');
    });

    test('should return 404 for non-existent user', async () => {
      try {
        await api.get('/api/users/999999');
      } catch (error) {
        expect(error.response.status).toBe(404);
      }
    });
  });

  describe('POST /api/users', () => {
    test('should create new user', async () => {
      const newUser = {
        name: 'Test User',
        email: '[email protected]'
      };

      const response = await api.post('/api/users', newUser);

      expect(response.status).toBe(201);
      expect(response.data).toHaveProperty('id');
      expect(response.data.name).toBe(newUser.name);
      expect(response.data.email).toBe(newUser.email);
    });

    test('should validate required fields', async () => {
      const invalidUser = { name: 'Test' }; // missing email

      try {
        await api.post('/api/users', invalidUser);
      } catch (error) {
        expect(error.response.status).toBe(400);
        expect(error.response.data).toHaveProperty('error');
      }
    });

    test('should prevent duplicate emails', async () => {
      const user = {
        name: 'Duplicate',
        email: '[email protected]'
      };

      try {
        await api.post('/api/users', user);
      } catch (error) {
        expect(error.response.status).toBe(409);
      }
    });
  });

  describe('Authentication', () => {
    test('should reject requests without token', async () => {
      const noAuthAPI = axios.create({ baseURL: API_URL });

      try {
        await noAuthAPI.get('/api/users');
      } catch (error) {
        expect(error.response.status).toBe(401);
      }
    });

    test('should reject invalid token', async () => {
      const badAuthAPI = axios.create({
        baseURL: API_URL,
        headers: { 'Authorization': 'Bearer invalid-token' }
      });

      try {
        await badAuthAPI.get('/api/users');
      } catch (error) {
        expect(error.response.status).toBe(401);
      }
    });
  });
});
Python pytest
import pytest
import requests

API_URL = 'https://api.example.com'
TOKEN = 'YOUR_TEST_TOKEN'

@pytest.fixture
def headers():
    return {
        'Authorization': f'Bearer {TOKEN}',
        'Content-Type': 'application/json'
    }

def test_get_users(headers):
    response = requests.get(f'{API_URL}/api/users', headers=headers)

    assert response.status_code == 200
    assert isinstance(response.json(), list)
    assert len(response.json()) > 0

def test_get_user_by_id(headers):
    response = requests.get(f'{API_URL}/api/users/123', headers=headers)

    assert response.status_code == 200
    data = response.json()
    assert data['id'] == '123'
    assert 'name' in data
    assert 'email' in data

def test_create_user(headers):
    user_data = {
        'name': 'Test User',
        'email': '[email protected]'
    }

    response = requests.post(
        f'{API_URL}/api/users',
        json=user_data,
        headers=headers
    )

    assert response.status_code == 201
    data = response.json()
    assert 'id' in data
    assert data['name'] == user_data['name']

def test_unauthorized_access():
    response = requests.get(f'{API_URL}/api/users')
    assert response.status_code == 401

Postman 集合

集合结构
{
  "info": {
    "name": "API Test Collection",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{access_token}}",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "Users",
      "item": [
        {
          "name": "Get All Users",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/users?page=1&limit=10",
              "host": ["{{base_url}}"],
              "path": ["api", "users"],
              "query": [
                { "key": "page", "value": "1" },
                { "key": "limit", "value": "10" }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "    pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test('Response is array', function () {",
                  "    var jsonData = pm.response.json();",
                  "    pm.expect(jsonData).to.be.an('array');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Create User",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"{{$randomFullName}}\",\n  \"email\": \"{{$randomEmail}}\",\n  \"role\": \"user\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/users",
              "host": ["{{base_url}}"],
              "path": ["api", "users"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 201', function () {",
                  "    pm.response.to.have.status(201);",
                  "});",
                  "",
                  "pm.test('User has ID', function () {",
                  "    var jsonData = pm.response.json();",
                  "    pm.expect(jsonData).to.have.property('id');",
                  "    pm.environment.set('user_id', jsonData.id);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.example.com"
    }
  ]
}

负载测试

使用 Apache Bench
# 1000 requests, 10 concurrent
ab -n 1000 -c 10 -H "Authorization: Bearer TOKEN" \
  https://api.example.com/api/users

# POST request with JSON
ab -n 1000 -c 10 -p data.json -T application/json \
  -H "Authorization: Bearer TOKEN" \
  https://api.example.com/api/users
使用 Artillery
# artillery.yml
config:
  target: 'https://api.example.com'
  phases:
    - duration: 60
      arrivalRate: 10
      name: Warm up
    - duration: 300
      arrivalRate: 50
      name: Sustained load
  defaults:
    headers:
      Authorization: 'Bearer YOUR_TOKEN'

scenarios:
  - name: "Get users"
    flow:
      - get:
          url: "/api/users"
          expect:
            - statusCode: 200
      - think: 1
      - post:
          url: "/api/users"
          json:
            name: "Test User"
            email: "[email protected]"
          expect:
            - statusCode: 201
# Run load test
artillery run artillery.yml

# Generate HTML report
artillery run artillery.yml --output report.json
artillery report report.json --output report.html

响应验证

架构验证
const Ajv = require('ajv');
const ajv = new Ajv();

// Define schema
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
    role: { type: 'string', enum: ['user', 'admin'] },
    createdAt: { type: 'string', format: 'date-time' }
  },
  required: ['id', 'name', 'email', 'role']
};

const validate = ajv.compile(userSchema);

// Validate response
async function testUserAPI() {
  const response = await fetch('https://api.example.com/api/users/123');
  const data = await response.json();

  const valid = validate(data);
  if (!valid) {
    console.error('Validation errors:', validate.errors);
  } else {
    console.log('Response is valid!');
  }
}

最佳实践

请求最佳实践
  • 始终设置适当的 Content-Type 标头
  • 使用正确的 HTTP 方法(读取使用 GET,创建使用 POST,等等)
  • 安全地包含身份验证令牌
  • 处理超时和重试
  • 发送前验证输入
  • 在生产环境中使用 HTTPS API
响应处理
  • 解析前检查状态码
  • 妥善处理错误
  • 验证响应架构
  • 记录请求和响应以便调试
  • 为重试实现指数退避
安全测试
  • 使用无效令牌进行测试
  • 测试未进行身份验证的情况
  • 尝试在参数中进行 SQL 注入
  • 测试输入字段中的 XSS
  • 验证 CORS 设置
  • 测试速率限制
要测试的错误场景
  • 身份验证无效
  • 缺少必填字段
  • 数据类型无效
  • 资源重复
  • 未找到(404)
  • 服务器错误(500)
  • 超出速率限制(429)
  • 网络超时

常见 HTTP 状态码

200 OK - Request successful
201 Created - Resource created
204 No Content - Success, no response body
400 Bad Request - Invalid request
401 Unauthorized - Missing/invalid authentication
403 Forbidden - Not allowed to access
404 Not Found - Resource doesn't exist
409 Conflict - Resource already exists
422 Unprocessable Entity - Validation failed
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error - Server error
502 Bad Gateway - Upstream server error
503 Service Unavailable - Server overloaded

备注

  • 始终先在开发/暂存环境中测试,再部署到生产环境
  • 使用环境变量存储 API URL 和令牌
  • 记录所有测试用例和预期结果
  • 在 CI/CD 流水线中自动化测试
  • 监控 API 性能和错误率
  • 及时更新 Postman 集合
  • 测试边缘情况和错误场景
  • 验证成功和失败路径
  • 使用恰当的身份验证方法
  • 切勿将 API 密钥或令牌提交到版本控制系统

评论

支持 Markdown