第17章:测试策略:单元测试、集成测试、压力测试、混沌工程

说实话,做量化系统这么多年,我见过太多「上线即崩」的惨案了。

有一次,我们一个做市商策略刚上线,行情一波动,整个系统直接挂了。后来复盘发现,就是一个边界条件没测到。嗯,从那以后,我对测试策略的重视程度,直接拉满。

今天咱们聊聊结构化产品做市商系统的测试策略。说白了,就是怎么保证你的系统在真实战场上不拉胯。

17.1 单元测试:把每个零件都拧紧

单元测试,我个人的习惯是「能测尽测」。尤其是那些核心计算逻辑,比如定价引擎、风险计算、订单管理。

你想想看,一个定价公式里,如果有个符号写反了,集成测试可能半天都查不出来。但单元测试,几秒钟就能定位。

核心原则: 每个函数只测一件事,测到边界。

举个例子,我们有个计算期权理论价格的函数:

// 伪代码示例
func TestOptionPricing(t *testing.T) {
    // 正常情况
    price := CalculateOptionPrice(100, 105, 0.3, 0.05, 30)
    assert.InDelta(t, 2.5, price, 0.01)

    // 边界:行权价等于现价
    price = CalculateOptionPrice(100, 100, 0.3, 0.05, 30)
    assert.Greater(t, price, 0.0)

    // 边界:剩余天数为0
    price = CalculateOptionPrice(100, 105, 0.3, 0.05, 0)
    assert.Equal(t, 0.0, price)
}

我在项目中遇到过一个问题:有个同事写了个波动率计算函数,没测输入为0的情况。结果生产环境里,某只股票停牌了,波动率算出来是NaN,整个风险模块全崩了。

所以,单元测试的覆盖率,我建议至少到80%以上。尤其是那些「看起来不会出问题」的地方,往往就是坑。

小技巧: 用表格驱动测试(Table-Driven Test),把各种输入输出列清楚,一目了然。

17.2 集成测试:看零件能不能一起转

单元测试过了,不代表系统能跑通。集成测试,就是看各个模块之间能不能正常协作。

比如,行情模块收到数据后,能不能正确传给定价引擎?定价引擎算完价格,订单模块能不能正确发出指令?

我一般会做这么几类集成测试:

  • 行情到定价的链路: 模拟行情数据,看定价引擎输出是否合理
  • 定价到风控的链路: 检查风控模块能否正确拦截超限订单
  • 订单到交易所的链路: 模拟交易所响应,看订单状态更新是否正确

这里有个坑:集成测试的环境,最好跟生产环境保持一致。我曾经因为测试环境用的数据库版本不一样,结果上线后才发现有个SQL语法不兼容。嗯,那晚的加班餐,味道真不怎么样。

注意: 集成测试不要只测「快乐路径」。要测异常情况,比如网络超时、数据格式错误、交易所拒绝订单等。

17.3 压力测试:看看系统能扛多大风浪

做市商系统,最怕的就是行情剧烈波动。平时风平浪静,一到关键时刻就掉链子,那可就惨了。

压力测试,说白了就是模拟极端行情,看看系统能不能扛住。

我常用的压力测试场景:

  1. 高并发行情: 模拟每秒1000笔以上的行情更新,看系统延迟
  2. 大量订单: 同时提交数百笔订单,看订单处理速度
  3. 资源耗尽: 模拟内存、CPU、网络带宽接近极限的情况

举个例子,我们用JMeter做过一个压力测试:

// 压力测试配置示例
Thread Group:
  - Number of Threads: 500
  - Ramp-up Period: 10 seconds
  - Loop Count: 100

HTTP Request:
  - Protocol: TCP
  - Port: 8080
  - Path: /api/order/submit

Assertions:
  - Response Time < 100ms
  - Error Rate < 0.1%

我记得有一次压力测试,系统在500并发下直接OOM了。查了半天,发现是一个缓存没设置过期时间,导致内存暴涨。从那以后,我每次压测都会盯着内存曲线看。

关键指标: 吞吐量(TPS)、响应时间(P50/P95/P99)、错误率、资源使用率。

17.4 混沌工程:主动搞破坏,提前发现弱点

混沌工程,听起来很玄乎,其实就是「故意搞破坏」。在可控的环境里,模拟各种故障,看看系统能不能自我修复。

我刚开始接触混沌工程时,觉得这玩意儿有点多余。直到有一次,我们一个依赖的Redis集群挂了,整个做市系统停了10分钟。嗯,从那以后,我成了混沌工程的忠实粉丝。

常见的混沌实验:

  • 杀死一个服务实例: 看负载均衡能否自动切换
  • 模拟网络延迟: 看系统能否优雅降级
  • 让数据库主库宕机: 看能否自动切换到从库
  • 注入CPU压力: 看系统能否限流保护

我们团队用Chaos Monkey做过一个实验:随机杀死一个订单处理服务实例。结果发现,虽然系统能自动恢复,但恢复期间有少量订单丢失了。这个问题,要不是混沌工程,可能永远发现不了。

建议: 混沌工程不要一开始就搞大动作。先从「非核心服务」开始,逐步扩大范围。每次实验后,都要复盘改进。

17.5 测试策略的整体框架

说了这么多,咱们用一张图来总结一下测试策略的整体框架:

测试策略金字塔 单元测试 每个函数、每个模块独立测试 集成测试 模块间协作、数据流、异常链路 压力测试 高并发、高吞吐、资源极限 混沌工程 主动故障注入、弹性恢复、容错验证 测试范围 测试成本 越往上,测试范围越小,成本越低;越往下,测试范围越大,成本越高

从这张图可以看出来,测试策略是一个金字塔结构。底层是单元测试,覆盖最广、成本最低。越往上,测试范围越大,但成本也越高。

我个人建议的投入比例是:单元测试占60%,集成测试占25%,压力测试占10%,混沌工程占5%。当然,具体比例要看你的系统规模和风险承受能力。

核心思想: 测试不是一次性的工作,而是持续的过程。每次代码变更、每次架构调整,都要重新跑一遍测试。

好了,关于测试策略,今天就聊到这儿。记住一句话:测试不是为了证明系统没问题,而是为了发现系统有问题。越早发现,成本越低。

无相订单流研究社 微信Lucian808555