8.2 用Rust来测试 (Testing with Rust)

A common use of Sway is for writing contracts or scripts that exist as part of a wider Rust application. In order to test the interaction between our Sway code and our Rust code we can add integration testing.

Sway 的一个常见用途是编写作为更广泛 Rust 应用的,部分存在的合约或脚本。为了测试我们的 Sway 代码和 Rust 代码之间的交互,我们可以添加集成测试。

添加 Rust 集成测试 (Adding Rust Integration Testing)

To add Rust integration testing to a Forc project we can use the sway-test-rs cargo generate template. This template makes it easy for Sway devs to add the boilerplate required when setting up their Rust integration testing.

要将 Rust 集成测试添加到 Forc 项目,我们可以使用 sway-test-rs cargo生成模板 .此模板使 Sway 开发人员可以轻松添加设置 Rust 集成测试时,所需的样板文件。

Let's add a Rust integration test to the fresh project we created in the introduction.

让我们把我们在介绍中创建的新项目 添加到 Rust 集成测试。

1. 进入项目 (Enter the project)

To recap, here's what our empty project looks like:

回顾一下,这是我们的空项目的样子:

$ cd my-fuel-project
$ tree .
├── Forc.toml
└── src
    └── main.sw

2. 安装 cargo generate (Install cargo generate)

We're going to add a Rust integration test harness using a cargo generate template. Let's make sure we have the cargo generate command installed!

我们将使用 cargo generate 模板添加 Rust 集成测试工具。让我们确保我们安装了 cargo generate 命令!

cargo install cargo-generate

Note: You can learn more about cargo generate by visiting its repository. 注意:您可以通过访问其存储库 了解更多关于cargo生成的信息。

3. 生成测试工具 (Generate the test harness)

Let's generate the default test harness with the following:

让我们使用以下内容生成默认测试工具:

cargo generate --init fuellabs/sway templates/sway-test-rs --name my-fuel-project --force

--force forces your --name input to retain your desired casing for the {{project-name}} placeholder in the template. Otherwise, cargo-generate automatically converts it to kebab-case. With --force, this means that both my_fuel_project and my-fuel-project are valid project names, depending on your needs.

--force 强制您的 --name 输入保留模板中 {{project-name}} 占位符所需的大​​小写。否则,cargo-generate 会自动将其转换为 kebab-case。使用 --force,这意味着 my_fuel_projectmy-fuel-project 都是有效的项目名称,具体取决于您的需要。

If all goes well, the output should look as follows:

如果一切顺利,输出应如下所示:

⚠️   Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway
🤷   Project Name : my-fuel-project
🔧   Destination: /home/user/path/to/my-fuel-project ...
🔧   Generating template ...
[1/3]   Done: Cargo.toml
[2/3]   Done: tests/harness.rs
[3/3]   Done: tests
🔧   Moving generated files into: `/home/user/path/to/my-fuel-project`...
✨   Done! New project created /home/user/path/to/my-fuel-project

Let's have a look at the result:

让我们看看结果:

$ tree .
├── Cargo.toml
├── Forc.toml
├── src
│   └── main.sw
└── tests
    └── harness.rs

We have two new files!

我们有两个新文件!

  • The Cargo.toml is the manifest for our new test harness and specifies the required dependencies including fuels the Fuel Rust SDK. Cargo.toml 是我们新测试工具的清单,并指定所需的依赖,包括fuels Fuel Rust SDK。

  • The tests/harness.rs contains some boilerplate test code to get us started, though doesn't call any contract methods just yet. tests/harness.rs 包含一些让我们开始d的样板测试代码,但还没有调用任何合约方法。

4. 构建forc项目 (Build the forc project)

Before running the tests, we need to build our contract so that the necessary ABI, storage and bytecode artifacts are available. We can do so with forc build:

在运行测试之前,我们需要构建合约,使必要的 ABI、存储和字节码工件可用。可以使用forc build来做到这一点:

$ forc build
  Creating a new `Forc.lock` file. (Cause: lock file did not exist)
    Adding core
    Adding std git+https://github.com/fuellabs/sway?tag=v0.24.5#e695606d8884a18664f6231681333a784e623bc9
   Created new lock file at /home/user/path/to/my-fuel-project/Forc.lock
  Compiled library "core".
  Compiled library "std".
  Compiled contract "my-fuel-project".
  Bytecode size is 60 bytes.

At this point, our project should look like the following:

此时,我们的项目应该如下所示:

$ tree
├── Cargo.toml
├── Forc.lock
├── Forc.toml
├── out
│   └── debug
│       ├── my-fuel-project-abi.json
│       ├── my-fuel-project.bin
│       └── my-fuel-project-storage_slots.json
├── src
│   └── main.sw
└── tests
    └── harness.rs

We now have an out directory with our required JSON files!

我们现在有一个包含所需 JSON 文件的 out 目录!

Note: This step may no longer be required in the future as we plan to enable the integration testing to automatically build the artifacts as necessary so that files like the ABI JSON are always up to date. 注意:将来可能不再需要此步骤,因为我们计划启用集成测试,进而根据需要自动构建工件,以便 ABI JSON 等文件始终保持最新。

5. 构建并运行测试 (Build and run the tests)

Now we're ready to build and run the default integration test.

现在我们已准备好构建并运行默认集成测试。

$ cargo test
    Updating crates.io index
   Compiling version_check v0.9.4
   Compiling proc-macro2 v1.0.46
   Compiling quote v1.0.21
   ...
   Compiling fuels v0.24.0
   Compiling my-fuel-project v0.1.0 (/home/user/path/to/my-fuel-project)
    Finished test [unoptimized + debuginfo] target(s) in 1m 03s
     Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)

running 1 test
test can_get_contract_id ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s

Note: The first time we run cargo test, cargo will spend some time fetching and building the dependencies for Fuel's Rust SDK. This might take a while, but only the first time! 注意:我们第一次运行 cargo test 时,cargo 将花费一些时间来获取和构建 Fuel 的 Rust SDK 的依赖。这可能需要一段时间,但一次就好

If all went well, we should see some output that looks like the above!

如果一切顺利,我们应该会看到类似上面的输出!

编写测试 (Writing Tests)

Now that we've learned how to setup Rust integration testing in our project, let's try to write some of our own tests!

现在我们已经了解了如何在项目中设置 Rust 集成测试,让我们尝试编写一些我们自己的测试!

First, let's update our contract code with a simple counter example:

首先,让我们用一个简单的反例更新我们的合约代码:

contract;

abi TestContract {
    #[storage(write)]
    fn initialize_counter(value: u64) -> u64;

    #[storage(read, write)]
    fn increment_counter(amount: u64) -> u64;
}

storage {
    counter: u64 = 0,
}

impl TestContract for Contract {
    #[storage(write)]
    fn initialize_counter(value: u64) -> u64 {
        storage.counter.write(value);
        value
    }

    #[storage(read, write)]
    fn increment_counter(amount: u64) -> u64 {
        let incremented = storage.counter.read() + amount;
        storage.counter.write(incremented);
        incremented
    }
}

To test our initialize_counter and increment_counter contract methods from the Rust test harness, we could update our tests/harness.rs file with the following:

为了从 Rust 测试工具中测试我们的 initialize_counterincrement_counter 合约方法,我们可以使用以下内容更新我们的 tests/harness.rs 文件:

use fuels::{prelude::*, tx::ContractId};

// Load abi from json
abigen!(TestContract, "out/debug/my-fuel-project-abi.json");

async fn get_contract_instance() -> (TestContract, ContractId) {
    // Launch a local network and deploy the contract
    let mut wallets = launch_custom_provider_and_get_wallets(
        WalletsConfig::new(
            Some(1),             /* Single wallet */
            Some(1),             /* Single coin (UTXO) */
            Some(1_000_000_000), /* Amount per coin */
        ),
        None,
    )
    .await;
    let wallet = wallets.pop().unwrap();

    let id = Contract::deploy(
        "./out/debug/my-fuel-project.bin",
        &wallet,
        TxParameters::default(),
        StorageConfiguration::with_storage_path(Some(
            "./out/debug/my-fuel-project-storage_slots.json".to_string(),
        )),
    )
    .await
    .unwrap();

    let instance = TestContract::new(id.to_string(), wallet);

    (instance, id.into())
}

#[tokio::test]
async fn initialize_and_increment() {
    let (contract_instance, _id) = get_contract_instance().await;
    // Now you have an instance of your contract you can use to test each function

    let result = contract_instance
        .methods()
        .initialize_counter(42)
        .call()
        .await
        .unwrap();

    assert_eq!(42, result.value);

    // Call `increment_counter()` method in our deployed contract.
    let result = contract_instance
        .methods()
        .increment_counter(10)
        .call()
        .await
        .unwrap();

    assert_eq!(52, result.value);
}

Let's build our project once more and run the test:

让我们再次构建我们的项目并运行测试:

forc build
$ cargo test
   Compiling my-fuel-project v0.1.0 (/home/mindtree/programming/sway/my-fuel-project)
    Finished test [unoptimized + debuginfo] target(s) in 11.61s
     Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)

running 1 test
test initialize_and_increment ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.25s

When cargo runs our test, our test uses the SDK to spin up a local in-memory Fuel network, deploy our contract to it, and call the contract methods via the ABI.

当 cargo 运行我们的测试时,我们的测试使用 SDK 启动本地内存中的 Fuel 网络,将我们的合约部署到它,并通过 ABI 调用合约方法。

You can add as many functions decorated with #[tokio::test] as you like, and cargo test will automatically test each of them!

您可以根据需要添加任意数量的用#[tokio::test] 修饰的函数,cargo test 将自动测试它们中的每一个!

Last updated