测试的组织结构
我们把测试主要分成两类:单元测试和集成测试。单元测试小而专注,每次测试一个模块,可以测试私有函数。虽然 Cairo 还没有实现公有/私有函数/字段的概念,但像这样组织代码是一个很好的习惯。集成测试像其他任何外部代码一样使用您的代码,只使用公共接口,并在每个测试中可能使用多个模块。
为了保证你的库能够按照你的预期运行,从独立和整体的角度编写这两类测试都是非常重要的。
单元测试
单元测试的目的是在与其他部分隔离的环境中测试每一个单元的代码,以便于快速而准确地验证某个单元的代码功能是否符合预期。单元测试与他们要测试的代码共同存放在位于 src 目录下相同的文件中。
规范是在每个文件中创建包含测试函数的 tests 模块,并使用 cfg(test)
标注模块。
测试模块和#[cfg(test)]
测试模块的 #[cfg(test)]
注解告诉 Cairo 只在执行scarb cairo-test
时才编译和运行测试代码,而在运行 cairo-run
时不这么做。这在只希望构建库的时候可以节省编译时间,并且因为它们并没有包含测试,所以能减少编译产生的文件的大小。与之对应的集成测试因为位于另一个文件夹,所以它们并不需要 #[cfg(test)]
注解。然而单元测试位于与源码相同的文件中,所以你需要使用 #[cfg(test)]
来指定他们不应该被包含进编译结果中。
回顾一下,当我们在本章第一节创建新的adder
项目时,我们写了这个测试:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert(result == 4, 'result is not 4'); } } }
cfg
属性代表配置,告诉Cairo只有在给定的配置选项的情况下才应该包含下面的项目。在本例中,配置选项是test
,它由Cairo提供,用于编译和运行测试。通过使用cfg
属性,Cairo只有在我们用scarb cairo-test
主动运行测试时才会编译我们的测试代码。这包括任何可能在这个模块中的辅助函数,以及用#[test]
标注的函数。
集成测试
集成测试对于你需要测试的库来说完全是外部的。同其他使用库的代码一样使用库文件,也就是说它们只能调用一部分库中的公有 API。集成测试的目的是测试库的多个部分能否一起正常工作。一些单独能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。为了创建集成测试,你需要先创建一个 tests
目录。
tests
目录
adder
├── Scarb.toml
├── src
│ ├── lib.cairo
│ ├── tests
│ │ └── integration_test.cairo
│ └── tests.cairo
#![allow(unused)] fn main() { #[cfg(test)] mod tests; fn it_adds_two(a: u8, b: u8) -> u8 { a + b } }
#![allow(unused)] fn main() { #[cfg(tests)] mod integration_tests; }
将示例9-11中的代码输入到 src/tests/integration_test.cairo 文件:
#![allow(unused)] fn main() { use adder::it_adds_two; #[test] #[available_gas(2000000)] fn internal() { assert(it_adds_two(2, 2) == 4, 'internal_adder failed'); } }
我们需要在每个测试文件的作用域中引入被测试的函数。出于这个原因,我们在代码的顶部添加了use adder::it_adds_two
,在单元测试中我们不需要这个。
然后,为了运行我们所有的集成测试,我们可以添加一个过滤器来仅运行路径包含“integration_tests”的测试。
$ scarb test -f integration_tests
Running cairo-test adder
testing adder ...
running 1 tests
test adder::tests::integration_tests::internal ... ok (gas usage est.: 3770)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 filtered out;
测试的结果与我们之前看到的相同:每个测试一行。