讲师中心 微信公众号
AI工具推荐 视频效率加速

Test Patterns

Polar Sponsor
爱发电 赞助
.NET 9.0

跨语言和框架编写并运行测试。适用于搭建测试套件、编写单元/集成/E2E测试、测量覆盖率、模拟依赖或调试测试失败。支持 Node.js (Jest/Vitest)、Python (pytest)、Go、Rust 和 Bash。

测试模式 : 写入、 运行和调试测试, 跨语言

功能概述

测试模式 : 写入、 运行和调试测试, 跨语言是一项面向实际任务的技能,主要用于覆盖单元测试、 集成测试、 E2E测试、 模拟、 覆盖和 TDD 工作流程;为新 p 设置测试套件。

核心要点

  • 它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
  • 使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。
  • 该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。

使用与执行

从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;

结果检查与注意事项

若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。

测试模式

跨语言编写、运行和调试测试。涵盖单元测试、集成测试、端到端(E2E)测试、模拟(mocking)、覆盖率分析及测试驱动开发(TDD)工作流。

适用场景

  • 为新项目搭建测试套件
  • 为函数或模块编写单元测试
  • 为 API 或数据库交互编写集成测试
  • 配置代码覆盖率统计
  • 模拟外部依赖(如 API、数据库、文件系统)
  • 调试不稳定(flaky)或失败的测试
  • 实施测试驱动开发(TDD)

Node.js(Jest / Vitest)

初始化

# Jest
npm install -D jest
# 添加到 package.json:"scripts": { "test": "jest" }

# Vitest(更快,原生支持 ESM)
npm install -D vitest
# 添加到 package.json:"scripts": { "test": "vitest" }

单元测试

// math.js
export function add(a, b) { return a + b; }
export function divide(a, b) {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
}

// math.test.js
import { add, divide } from './math.js';

describe('add', () => {
  test('adds two positive numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('handles negative numbers', () => {
    expect(add(-1, 1)).toBe(0);
  });

  test('handles zero', () => {
    expect(add(0, 0)).toBe(0);
  });
});

describe('divide', () => {
  test('divides two numbers', () => {
    expect(divide(10, 2)).toBe(5);
  });

  test('throws on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero');
  });

  test('handles floating point', () => {
    expect(divide(1, 3)).toBeCloseTo(0.333, 3);
  });
});

异步测试

// api.test.js
import { fetchUser } from './api.js';

test('fetches user by id', async () => {
  const user = await fetchUser('123');
  expect(user).toHaveProperty('id', '123');
  expect(user).toHaveProperty('name');
  expect(user.name).toBeTruthy();
});

test('throws on missing user', async () => {
  await expect(fetchUser('nonexistent')).rejects.toThrow('Not found');
});

模拟(Mocking)

// Mock 一个模块
jest.mock('./database.js');
import { getUser } from './database.js';
import { processUser } from './service.js';

test('processes user from database', async () => {
  // 设置 mock 返回值
  getUser.mockResolvedValue({ id: '1', name: 'Alice', active: true });

  const result = await processUser('1');
  expect(result.processed).toBe(true);
  expect(getUser).toHaveBeenCalledWith('1');
  expect(getUser).toHaveBeenCalledTimes(1);
});

// Mock fetch
global.fetch = jest.fn();

test('calls API with correct params', async () => {
  fetch.mockResolvedValue({
    ok: true,
    json: async () => ({ data: 'test' }),
  });

  const result = await myApiCall('/endpoint');
  expect(fetch).toHaveBeenCalledWith('/endpoint', expect.objectContaining({
    method: 'GET',
  }));
});

// 对已有方法进行间谍(Spy)——不替换,仅观测
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
// ... 执行代码 ...
expect(consoleSpy).toHaveBeenCalledWith('expected message');
consoleSpy.mockRestore();

覆盖率

# Jest
npx jest --coverage

# Vitest
npx vitest --coverage

# 检查覆盖率阈值(jest.config.js)
# coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } }

Python(pytest)

初始化

pip install pytest pytest-cov

单元测试

# calculator.py
def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("Division by zero")
    return a / b

# test_calculator.py
import pytest
from calculator import add, divide

def test_add():
    assert add(2, 3) == 5

def test_add_negative():
    assert add(-1, 1) == 0

def test_divide():
    assert divide(10, 2) == 5.0

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Division by zero"):
        divide(10, 0)

def test_divide_float():
    assert divide(1, 3) == pytest.approx(0.333, abs=0.001)

参数化测试

@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (-1, 1, 0),
    (0, 0, 0),
    (100, -50, 50),
])
def test_add_cases(a, b, expected):
    assert add(a, b) == expected

Fixture

import pytest
import json
import tempfile
import os

@pytest.fixture
def sample_users():
    """Provide test user data."""
    return [
        {"id": 1, "name": "Alice", "email": "alice@test.com"},
        {"id": 2, "name": "Bob", "email": "bob@test.com"},
    ]

@pytest.fixture
def temp_db(tmp_path):
    """Provide a temporary SQLite database."""
    import sqlite3
    db_path = tmp_path / "test.db"
    conn = sqlite3.connect(str(db_path))
    conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
    conn.commit()
    yield conn
    conn.close()

def test_insert_users(temp_db, sample_users):
    for user in sample_users:
        temp_db.execute("INSERT INTO users VALUES (?, ?, ?)",
                       (user["id"], user["name"], user["email"]))
    temp_db.commit()
    count = temp_db.execute("SELECT COUNT(*) FROM users").fetchone()[0]
    assert count == 2

# 带清理逻辑的 Fixture
@pytest.fixture
def temp_config_file():
    path = tempfile.mktemp(suffix=".json")
    with open(path, "w") as f:
        json.dump({"key": "value"}, f)
    yield path
    os.unlink(path)

模拟(Mocking)

from unittest.mock import patch, MagicMock, AsyncMock

# Mock 一个函数
@patch('mymodule.requests.get')
def test_fetch_data(mock_get):
    mock_get.return_value.status_code = 200
    mock_get.return_value.json.return_value = {"data": "test"}

    result = fetch_data("https://api.example.com")
    assert result == {"data": "test"}
    mock_get.assert_called_once_with("https://api.example.com")

# Mock 异步调用
@patch('mymodule.aiohttp.ClientSession.get', new_callable=AsyncMock)
async def test_async_fetch(mock_get):
    mock_get.return_value.__aenter__.return_value.json = AsyncMock(return_value={"ok": True})
    result = await async_fetch("/endpoint")
    assert result["ok"] is True

# 上下文管理器式 Mock
def test_file_reader():
    with patch("builtins.open", MagicMock(return_value=MagicMock(
        read=MagicMock(return_value='{"key": "val"}'),
        __enter__=MagicMock(return_value=MagicMock(read=MagicMock(return_value='{"key": "val"}'))),
        __exit__=MagicMock(return_value=False),
    ))):
        result = read_config("fake.json")
        assert result["key"] == "val"

覆盖率

# 运行并生成覆盖率报告
pytest --cov=mypackage --cov-report=term-missing

# HTML 报告
pytest --cov=mypackage --cov-report=html
# 打开 htmlcov/index.html

# 若覆盖率低于阈值则失败
pytest --cov=mypackage --cov-fail-under=80

Go

单元测试

// math.go
package math

import "errors"

func Add(a, b int) int { return a + b }

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// math_test.go
package math

import (
    "testing"
    "math"
)

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, 1, 0},
        {"zeros", 0, 0, 0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.expected {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.expected)
            }
        })
    }
}

func TestDivide(t *testing.T) {
    result, err := Divide(10, 2)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if math.Abs(result-5.0) > 0.001 {
        t.Errorf("Divide(10, 2) = %f, want 5.0", result)
    }
}

func TestDivideByZero(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Error("expected error for division by zero")
    }
}

运行测试

# 运行全部测试
go test ./...

# 显示详细输出
go test -v ./...

# 指定包运行测试
go test ./pkg/math/

# 启用覆盖率统计
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# 运行特定测试
go test -run TestAdd ./...

# 检测竞态条件
go test -race ./...

# 运行基准测试
go test -bench=. ./...

Rust

单元测试

// src/math.rs
pub fn add(a: i64, b: i64) -> i64 { a + b }

pub fn divide(a: f64, b: f64) -> Result {
    if b == 0.0 { return Err("division by zero".into()); }
    Ok(a / b)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_eq!(add(-1, 1), 0);
    }

    #[test]
    fn test_divide() {
        let result = divide(10.0, 2.0).unwrap();
        assert!((result - 5.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_divide_by_zero() {
        assert!(divide(10.0, 0.0).is_err());
    }

    #[test]
    #[should_panic(expected = "overflow")]
    fn test_overflow_panics() {
        let _ = add(i64::MAX, 1); // 在 debug 模式下将触发溢出 panic
    }
}
cargo test
cargo test -- --nocapture  # 显示 println 输出
cargo test test_add        # 运行指定测试
cargo tarpaulin            # 覆盖率工具(安装方式:cargo install cargo-tarpaulin)

Bash 测试

简易测试运行器

#!/bin/bash
# test.sh - 极简 Bash 测试框架
PASS=0 FAIL=0

assert_eq() {
  local actual="$1" expected="$2" label="$3"
  if [ "$actual" = "$expected" ]; then
    echo "  PASS: $label"
    ((PASS++))
  else
    echo "  FAIL: $label (got '$actual', expected '$expected')"
    ((FAIL++))
  fi
}

assert_exit_code() {
  local cmd="$1" expected="$2" label="$3"
  eval "$cmd" >/dev/null 2>&1
  assert_eq "$?" "$expected" "$label"
}

assert_contains() {
  local actual="$1" substring="$2" label="$3"
  if echo "$actual" | grep -q "$substring"; then
    echo "  PASS: $label"
    ((PASS++))
  else
    echo "  FAIL: $label ('$actual' does not contain '$substring')"
    ((FAIL++))
  fi
}

# --- 测试用例 ---
echo "Running tests..."

# 测试你的脚本
output=$(./my-script.sh --help 2>&1)
assert_exit_code "./my-script.sh --help" "0" "help flag exits 0"
assert_contains "$output" "Usage" "help shows usage"

output=$(./my-script.sh --invalid 2>&1)
assert_exit_code "./my-script.sh --invalid" "1" "invalid flag exits 1"

# 测试命令输出
assert_eq "$(echo 'hello' | wc -c | tr -d ' ')" "6" "echo hello is 6 bytes"

echo ""
echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1

集成测试模式

API 集成测试(任意语言)

#!/bin/bash
# test-api.sh - 启动服务、运行测试、清理环境
SERVER_PID=""
cleanup() { [ -n "$SERVER_PID" ] && kill "$SERVER_PID" 2>/dev/null; }
trap cleanup EXIT

# 后台启动服务
npm start &
SERVER_PID=$!
sleep 2  # 等待服务就绪

# 对运行中的服务执行测试
BASE_URL=http://localhost:3000 npx jest --testPathPattern=integration
EXIT_CODE=$?

exit $EXIT_CODE

数据库集成测试(Python)

import pytest
import sqlite3

@pytest.fixture
def db():
    """Fresh database for each test."""
    conn = sqlite3.connect(":memory:")
    conn.execute("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, price REAL)")
    yield conn
    conn.close()

def test_insert_and_query(db):
    db.execute("INSERT INTO items (name, price) VALUES (?, ?)", ("Widget", 9.99))
    db.commit()
    row = db.execute("SELECT name, price FROM items WHERE name = ?", ("Widget",)).fetchone()
    assert row == ("Widget", 9.99)

def test_empty_table(db):
    count = db.execute("SELECT COUNT(*) FROM items").fetchone()[0]
    assert count == 0

TDD 工作流

红–绿–重构循环:

  1. 红(Red):为下一个行为编写一个失败的测试
  2. 绿(Green):编写最少必要代码使其通过
  3. 重构(Refactor):在不改变行为的前提下优化代码(确保测试仍全部通过)
# 快速反馈循环
# Jest 监听模式
npx jest --watch

# Vitest 监听模式(默认启用)
npx vitest

# pytest 监听(配合 pytest-watch)
pip install pytest-watch
ptw

# Go(配合 air 或 entr)
ls *.go | entr -c go test ./...

调试失败的测试

常见问题

单独运行通过、在测试套件中失败 → 存在共享状态。请检查:

  • 测试间被修改的全局变量
  • 数据库未清理
  • Mock 未恢复(缺少 afterEach / teardown)

测试偶发性失败(flaky) → 时间或执行顺序问题:

  • 异步操作未正确使用 await
  • 测试间存在执行顺序依赖
  • 含时间敏感逻辑(应使用时钟模拟)
  • 单元测试中发起网络请求(应予以 Mock)

覆盖率报告显示存在未覆盖分支 → 缺少边界/异常用例:

  • 错误路径(例如 API 返回 500)
  • 空输入(空字符串、null、空数组)
  • 边界值(0、-1、MAX_INT)

运行单个测试

# Jest
npx jest -t "test name substring"

# pytest
pytest -k "test_divide_by_zero"

# Go
go test -run TestDivideByZero ./...

# Rust
cargo test test_divide

提示与建议

  • 测试行为而非实现细节。良好的测试应能经受住重构考验。
  • 每个测试聚焦一个概念(不要求每测试仅一条 assert,但应只验证一项逻辑)。
  • 为测试命名应具描述性:test_returns_empty_list_when_no_users_exist 优于 test_get_users_2。
  • 不要 Mock 你不拥有的第三方库 —— 应为其封装薄层接口,并 Mock 该封装层。
  • 集成测试可捕获单元测试遗漏的缺陷,请勿跳过。
  • 对基于文件的测试,请使用 tmp_path(pytest)、t.TempDir()(Go)或 tempfile(Node)创建临时路径。
  • 快照测试适合检测意外变更,但不适用于格式持续演进的场景。
目录

热门AI工具

更多
咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

0

2026.10.22

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

0

2026.10.22

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

0

2026.10.22

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

0

2026.10.22

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

0

2026.10.22

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

0

2026.10.22

Golangci-lint安装与入门教程
Golangci-lint安装与入门教程

本专题讲解Golangci-lint的安装、初始化、基础命令和首次代码检查,覆盖本地环境、Docker及多平台用法,帮助Go开发者快速上手。

0

2026.10.22

PHP2Shell高危漏洞修复教程
PHP2Shell高危漏洞修复教程

PHP2Shell漏洞是什么,哪些PHP版本和运行环境会受到影响,服务器应该如何检测与修复?本专题提供版本自查脚本、Linux与Docker升级命令、PHP-FPM和Apache排查步骤,以及修复验证、临时缓解和应急响应方案。

0

2026.10.22

剧云人物设定与关系管理教程
剧云人物设定与关系管理教程

本专题介绍剧云人物设定与关系管理,讲解角色小传、性格职业、人物目标、背景资料和关系网络的创建及维护方法。

0

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn