在actix-web应用用构建集成测试

6. 第一次集成测试

/health_check 是我们实现的第一个端点。此前,我们使用 curl 手动测试并验证它能够正常工作。

然而,如果项目变大,手动测试会变得低效且容易出错。每次修改代码后都要人工验证,不仅耗时,还可能因为疏忽而遗漏问题。

为了解决这个问题,我们希望尽可能实现自动化测试。这样一来,每次提交代码时,这些检查都能在持续集成(CI)流水线上自动运行,以防止出现回归问题。

这里提到的“回归(Regression)”指的是: 在修改或新增功能后,原本正常的功能因为这次改动而出现故障的情况。自动化测试的目标之一,就是防止功能回归。

虽然健康检查端点的逻辑非常简单,几乎不会变化,但在这里建立起测试脚手架,是构建自动化测试体系的一个理想起点。
被称为"黑盒测试",即通过检查给定的一组输入和输出来测试验证系统的行为,而无需了解内部实现细节。

6.1 如何对端点进行测试

API 是实现特定功能的一种手段,它向外部公开接口,以便执行某种任务(例如存储文档、发送电子邮件等)。
我们在 API 中公开的端点(Endpoint)定义了系统与客户端之间的契约——即对输入和输出的约定。

契约的变更类型

随着时间的推移,API 契约可能会发生变化。一般可以分为两种情况:

  1. 向后兼容的变更
    例如新增一个端点。这类变更不会影响现有客户端的正常使用。

  2. 破坏性变更
    例如删除某个端点,或从响应结构中移除字段。
    如果客户端依赖了被删除或修改的部分,那么这些集成可能会因此失效。

为什么要测试端点

虽然我们有时会有意地对 API 契约进行重大调整,但我们必须确保不会无意间破坏现有功能。
为了防止引入用户可见的回归问题,最可靠的方法是模拟用户的实际使用方式,对 API 进行测试。

6.2 黑盒测试的意义

它的核心思想是:只关心输入和输出,而不依赖内部实现细节。这种测试方式通常被称为 黑盒测试(Black-box Testing)
黑盒测试通过向系统发送一组特定的输入(例如 HTTP 请求),并根据系统返回的输出(响应结果)来验证行为是否符合预期。
它不依赖于内部实现细节,因此可以从用户视角确保整个 API 契约的正确性和稳定性。

遵循这个原则,我们不会满足于直接调用处理器函数的测试,例如

#[cfg(test)]
mod tests {
   
   
    use crate::health_check;

    #[tokio::test]
    async fn health_check_succeeds() {
   
   
        let response = health_check().await;
        //这需要将`health_check`函数的返回类型从impl Responder修改为HttpResponse才能编译通过
        // 你还需要通过 `use actix_web::HttpResponse` 导入这个类型
        assert!(response.status().is_success())
    }
}

这种测试方式有几个问题:

  1. 没有经过 HTTP 层
    你没有真正通过 GET 请求调用 /health_check,因此无法检查路由是否正确配置。

  2. API 契约可能被破坏
    如果处理器路径或请求方法发生变化,这类测试仍然可能通过,但实际上 API 已经不符合预期。

  3. 过于依赖框架实现
    Actix-web 的 App 结构体内部某些参数是私有的,直接在测试中共享初始化逻辑比较困难。
    如果应用重构或迁移到其他 Web 框架,现有的测试套件很可能完全失效。

6.3 完全黑盒的解决方案

为了避免上述问题,推荐真正的黑盒测试方法:

  1. 每次测试启动一个完整的应用程序实例
  2. 使用 HTTP 客户端(如 reqwest)发送请求
  3. 通过响应验证 API 的行为

6.4 更改项目结构以便于测试

在/tests下编写真正的测试之前,我们还有一些事要做
/tests下的任何东西最终都会编译成独立的二进制文件–所有测试代码都是以包的形式倒入的。但我们的项目目前是二进制文件,它应该被执行,而不是被共享。因此,不能像这样导入main函数
不妨测试一下


6.4.1 创建一个新的tests文件夹

mkdir -p tests

6.4.2 创建一个新的tests/health_check.rs文件
// tests/health_check.rs
use zero2prod::main; // 错误的

#[test]
fn dummy_test() {
   
   
    main();
}

构建会失败


我们需要将项目重构为一个库和一个二进制文件;所有逻辑都存在于库中,而二进制文件(例如main.rs)本身是一个入口点,只包含轻量的main函数


6.4.2 首先,需要更改Cargo.toml
[package]
name = "zero2prod"
version = "0.1.0"
edition = "2024"

[lib]
path = "src/lib.rs"

[dependencies]
# ...

我们需要脱离自动配置后,一切简单明了

[package]
name = "zero2prod"
version = "0.1.0"
edition = "2024"

[lib]
path = "src/lib.rs"

[dependencies]
# ...

[[bin]]
path = "src/main.rs"
name = "zero2prod"

lib.rs项目目前还不存在,需要手动创建


6.4.3 将主函数逻辑移入库中

为了让应用程序代码更易于测试,我们将 main 函数中的主要逻辑迁移到库文件 lib.rs 中。
这样可以在测试中直接调用它,而不必依赖可执行文件。

//! main.rs
use zero2prod::run;
#[tokio::main]
async fn main()-> std::io::Result<()> {
   
   
    run().<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值