首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何正确地编写可靠的单元测试?

如何正确地编写可靠的单元测试?
EN

Ethereum用户
提问于 2021-05-30 17:57:50
回答 3查看 3.4K关注 0票数 4

我想为我的代码编写可维护和可读的单元测试。要求:

  1. 测试不同的演员。例如一份银行合同和三个客户与之互动。
  2. 测试错误。在合同工作中需要()或断言()的测试。
  3. 编写许多/长的测试,而不会遇到气体/内存问题。

问:有人能告诉我如何使用单元测试框架的内置特性正确地完成这个任务吗?例如在混合测试、松露测试或任何其他框架中。

这是我想要编写的测试代码的一个例子:

代码语言:javascript
复制
contract BankTests {
    function test() public {
        Bank bank = new Bank(); // a bank that only allows two customers
        TestActor customer1 = new TestActor{value: 1 ether}(); // has 1 ether
        TestActor customer2 = new TestActor{value: 1 ether}(); // has 1 ether
        TestActor customer3 = new TestActor{value: 1 ether}(); // has 1 ether

        switchToActor(bank, customer1); // from now on customer1 interacts with the bank
        Assert.equal(bank.balance(), 0);
        bank.deposit{value: 0.5 ether}();
        Assert.equal(bank.balance(), 0.5 ether);
        bank.deposit{value: 0.25 ether}();
        Assert.equal(bank.balance(), 0.75 ether);

        switchToActor(bank, customer2); // from now on customer2 interacts with the bank
        Assert.equal(bank.balance(), 0);
        bank.deposit{value: 0.1 ether}();
        Assert.equal(bank.balance(), 0.1 ether);

        switchToActor(bank, customer3); // from now on customer3 interacts with the bank
        try bank.deposit{value: 0.5 ether}() {
            Assert.equal(true, false); // must not succeed; bank only allows 2 customers
        } catch Error(string memory reason) {
            Assert.equal(reason, "Bank already has two customers.");
        } catch {
            Assert.equal(true, false); // wrong error; we expect a reason
        }

        resetActor(); // no current actor anymore
        Assert.equal(address(customer1).balance, 0.25 ether);
        Assert.equal(address(customer2).balance, 0.9 ether);
        Assert.equal(address(customer3).balance, 1 ether);
    }
}

我没有设法达到我的三个要求与混合测试和松露测试与他们的内置功能。所以我写了我自己的框架,在混合测试和松露测试中运行。如果有更好的方法,我很乐意再次删除这些代码。这里是我的方法的一个完整例子:

代码语言:javascript
复制
// The contract we want to test (made up for this question):
contract Bank {
    mapping(address => uint256) internal customers;
    uint8 internal customerCount;

    function deposit() public payable {
        require(msg.value > 0, "No money provided.");
        require(customers[msg.sender] > 0 || customerCount < 2,
                "Bank already has two customers.");
        customers[msg.sender] += msg.value;
        if(customers[msg.sender] == msg.value) {
            customerCount++;
        }
    }

    function balance() public view returns (uint256) {
        return customers[msg.sender];
    }
}

// One unit test:
// initialBalance and #value to get 3 ether from remix-tests or truffle-test.
contract BankTests is TestContract {
    uint64 public initialBalance = 3 ether;

    /// #value: 3000000000000000000
    function test() public payable {
        TestBank bank = new TestBank();
        TestActor customer1 = new TestActor{value: 1 ether}();
        TestActor customer2 = new TestActor{value: 1 ether}();
        TestActor customer3 = new TestActor{value: 1 ether}();

        switchToActor(bank, customer1);
        Assert.equal(bank.balance(), 0, "1a failed");
        bank.deposit{value: 0.5 ether}();
        Assert.equal(bank.balance(), 0.5 ether, "1b failed");
        bank.deposit{value: 0.25 ether}();
        Assert.equal(bank.balance(), 0.75 ether, "1c failed");

        switchToActor(bank, customer2);
        Assert.equal(bank.balance(), 0, "2a failed");
        bank.deposit{value: 0.1 ether}();
        Assert.equal(bank.balance(), 0.1 ether, "2b failed");

        switchToActor(bank, customer3);
        try bank.deposit{value: 0.5 ether}() {
            Assert.equal(true, false, "3a failed");
        } catch Error(string memory reason) {
            Assert.equal(
                reason,
                "Bank already has two customers.",
                "3b failed"
            );
        } catch {
            Assert.equal(true, false, "3c failed");
        }

        resetActor(bank);
        Assert.equal(address(customer1).balance, 0.25 ether, "4a failed");
        Assert.equal(address(customer2).balance, 0.9 ether, "4b failed");
        Assert.equal(address(customer3).balance, 1 ether, "4c failed");
    }
}

// Base class of test contracts to enable actor switching:
contract TestContract {
    function switchToActor(TestBank bank, TestActor actor) internal {
        bank.switchToActor{value: address(this).balance}(actor);
    }

    function resetActor(TestBank bank) internal {
        bank.resetActor{value: address(this).balance}();
    }

    receive() external payable {} // accept payments
}

// An actor contract to hold money and interact with other contracts:
contract TestActor {
    constructor() payable {}

    function sendAllMoneyTo(address payable recipient) public {
        recipient.transfer(address(this).balance);
    }

    receive() external payable {} // accept payments
}

// Subclass of bank contract to enable actor switching:
contract TestBank is Bank {
    address payable private currentActualCustomerAddr;

    function switchToActor(TestActor actualCustomer) public payable {
        resetActor();
        address payable proxyCustomerAddr = payable(msg.sender);
        address payable actualCustomerAddr = payable(address(actualCustomer));
        customers[proxyCustomerAddr] = customers[actualCustomerAddr];
        actualCustomer.sendAllMoneyTo(proxyCustomerAddr);
        currentActualCustomerAddr = actualCustomerAddr;
    }

    function resetActor() public payable {
        address payable proxyCustomerAddr = payable(msg.sender);
        if(currentActualCustomerAddr != address(0)) {
            customers[currentActualCustomerAddr] = customers[proxyCustomerAddr];
            currentActualCustomerAddr.transfer(msg.value);
        }
        currentActualCustomerAddr = payable(address(0));
    }
}

测试合同(BankTests)始终是客户与银行合同的实际交互。当请求切换到另一个参与者时,测试契约获得该参与者的余额,并且银行合同数据被修改,以便测试契约获得参与者状态。这不是很漂亮,但允许-- IMHO --编写可读和可维护的测试。

以这种方式编写多个测试,很快就会遇到“超出气体限制”或“内存不足”的错误:

  • 为了“解决”内存不足的错误,我编写了自己的测试运行程序,每次使用混合测试运行一个测试协议(混合测试将加载所有测试契约/文件)。
  • 为了“解决”超过错误的气体限制,我改变了混合测试的JavaScript源,使其有20倍高的气体限制(丑陋的攻击)。我没有找到一个方法来实现同样的松露测试。

总结一下:我建立了一个我希望没有必要的解决办法。我希望有人给我一个更好的方法来实现同样的目标。如果这是不可能的,那么我希望这个问题能够帮助处于相同情况下的人们更快地开始他们的测试框架。

EN

回答 3

Ethereum用户

发布于 2021-05-30 20:15:51

我对此还比较陌生,但从我所见的所有关于块菌和单元测试的教程来看,它们都倾向于使用Mocha和柴测试框架。我建议查看这两个库。网上有很多关于他们的教程。

看看DAPP大学的这篇教程。他很好地解释了如何使用库进行测试。(单元测试开始于47分钟左右)

https://youtu.be/CgXQC4dbGUE?t=2838

有几个在那里,所以可以随意使用另一个。在我看过的所有教程中,我都不记得曾经见过有人将单元测试直接放入可靠的智能契约中。通常,它们要么是javascript文件,要么是与智能契约交互的python文件。

通过将您的测试代码移到.js或.py文件中,您应该可以大大降低您的气体费用。(我认为这也有助于解决内存问题,但就像我说的那样,我对此非常陌生,从未见过有人试图在可靠的范围内执行单元测试。)

票数 0
EN

Ethereum用户

发布于 2022-07-28 11:50:38

我目前正在学习这个FreeCodeCamp教程,它有几个单元测试和分阶段测试的示例。他们用的是安全帽,柴族,以太,瓦夫勒和摩卡。强烈推荐。非常有效的教学。https://youtu.be/gyMwXuJrbJQ

票数 -1
EN

Ethereum用户

发布于 2022-09-22 05:16:57

对于Remix测试,您需要使用注释中的项来切换帐户并在测试函数中从它们发送eth。

代码语言:javascript
复制
    /// #value: 1000000000000000000
    /// #sender: account-2
    function f() public payable {
        contract.g{value:1000000000000000000}()
    }
票数 -1
EN
页面原文内容由Ethereum提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://ethereum.stackexchange.com/questions/99955

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档