2个版本 (1个稳定版)
1.2.0 | 2024年5月17日 |
---|---|
1.2.0-dev | 2024年3月20日 |
#237 in 测试
每月128次下载
6MB
127K SLoC
scrypto-test
来自Radix DLT项目的Scrypto蓝图测试库。
lib.rs
:
这个crate是Scrypto-Test的实现,一个针对Scrypto的单元测试框架,它采用基于调用的方法而不是基于事务的方法进行测试,允许Scrypto开发者编写看起来和感觉像Scrypto的测试。Scrypto-Test不是LedgerSimulator提供的基于事务的测试的替代品,它只是一个补充,为Scrypto开发者提供另一种测试代码的方式,其中Scrypto-Test可以被视为单元测试框架,而LedgerSimulator可以被视为集成测试框架。
为什么
我们已经有了一种测试Scrypto蓝图的方法,即scrypto_test::prelude::LedgerSimulator,它本质上是一个内存中的账本,我们可以对其运行事务,获取交易回执,并基于TransactionReceipt确定蓝图或组件的行为是否符合我们的预期。这种方法经过实践检验,并已被证明有效,正如radix_engine_tests crate中的成千上万的测试所证明的那样,这清楚地表明基于事务的方法是有效的。然而,它存在一些问题,尤其是在我们考虑测试框架的目标受众:DeFi开发者时。
在当前的(基于事务)模型中,编写本应直接进行的测试涉及到大量的样板代码。以一个例子来说,为了测试向 Radiswap 提供X和Y资源是否能产生Z个池单位,测试作者需要
- 创建这两个资源用于测试。
- 决定这些资源是否应该仅按需铸币,或者这些资源的供应是否应该存储在某个账户中,该账户将在测试中用于提取。
- 如果某个账户将持有这些资源,则作者需要创建该账户。
- 根据需要将指令拆分为多个清单,例如在一个指令依赖于先前指令输出的情况下。
- 确保先前事务的执行确实成功,并从收据中提取所需信息,无论是通过工作台更改、余额更改还是其他方式。
- 管理并确保事务结束时工作台空无资源,且将资源存入的账户签署了交易。
上述列表中的大多数如果不是全部,都不是开发者希望测试的核心内容,回想一下,他们希望测试的是提供X和Y资源是否能返回Z个池单位。然而,他们花了大部分时间思考完全不同的问题。因此,不仅存在大量的样板代码,而且对于本应概念上简单且易于编写的测试,还存在大量的心智负担。正如您从上面的描述中可以看到,编写测试的大部分时间并不是花在编写测试上,而是花在初始化和创建环境以及管理副作用上,只是然后通过向某个节点ID调用方法的形式编写一个简单的测试。
Scrypto-Test 模型
这个模型与编写测试的事务模型不同,因为我们完全没有事务指令、处理器和工作台。实际上,在这个模型中根本不存在与事务相关的任何内容。相反,存在一个TestEnvironment
结构,每个测试可以实例化该结构的实例。每个TestEnvironment
实例都有一个子状态存储、跟踪和内核。在此基础上,TestEnvironment
实现了ClientApi
特质。可以认为TestEnvironment
是一个自包含的 Radix 引擎实例,它通过ClientApi
公开,并包含了一些其他高级辅助方法,因为它包含了引擎的所有层。这意味着
- 一个
TestEnvironment
实例是 Radix 引擎和内核的自包含实例,它们通过ClientApi
公开。 - 由于
TestEnvironment
实现了ClientApi
,它可以作为 Scrypto 中的 ScryptoEnv 和本地的 SystemService 的替代品。这意味着可以在测试中使用在 radix_native_sdk crate 中看到的简单接口。 - 如果每个测试都有自己的
TestEnvironment
实例(如果需要,它们会实例化它),那么测试就没有共享依赖,并且是隔离的。 - 基于事务模型的最大挑战在于处理瞬时节点。更具体地说,如果我们想确保某个调用返回的桶X包含Y个资源,我们应该如何做?遗憾的是,没有简单的方法可以做到这一点。在这个模型中,如果我们发起一个调用并获取一个桶,这个桶没有工作台可以进入,我们有一个可以调用amount()并对其断言的正确的桶对象。因此,这种方法使得对瞬时节点进行断言变得更加容易。
一旦实例化了TestEnvironment
,我们就会得到一个具有两个调用帧的内核
- 根调用帧:我们有一个根调用帧,以确保与堆栈中其他部分使用内核的方式保持一致,其中始终存在一个根调用帧。实例化完成后,根调用帧被推入内核之前调用帧的堆栈中。
- 测试调用帧:这是在整个测试过程中将使用的调用帧。这个调用帧的功能与任何其他调用帧完全一样,它可以拥有节点,从其他调用帧接收消息,等等。例如,如果我们对某个节点的某个方法进行调用并返回一个桶,这个桶现在由这个调用帧拥有并可见。由于这是一个我们拥有的节点,并且
TestEnvironment
有一个堆,我们现在可以调用resource_address()和amount()等方法。
依赖项
~47–83MB
~1.5M SLoC