Skip to content

Latest commit

 

History

History
195 lines (153 loc) · 8.28 KB

08.md

File metadata and controls

195 lines (153 loc) · 8.28 KB
title actions requireLogin material
转移僵尸
checkAnswer
hints
true
editor
language startingCode answer
javascript
test/CryptoZombies.js test/helpers/utils.js
const CryptoZombies = artifacts.require("CryptoZombies"); const utils = require("./helpers/utils"); const zombieNames = ["Zombie 1", "Zombie 2"]; contract("CryptoZombies", (accounts) => { let [alice, bob] = accounts; let contractInstance; beforeEach(async () => { contractInstance = await CryptoZombies.new(); }); it("should be able to create a new zombie", async () => { const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice}); assert.equal(result.receipt.status, true); assert.equal(result.logs[0].args.name,zombieNames[0]); }) it("should not allow two zombies", async () => { await contractInstance.createRandomZombie(zombieNames[0], {from: alice}); await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice})); }) // start here })
async function shouldThrow(promise) { try { await promise; assert(true); } catch (err) { return; } assert(false, "The contract did not throw."); } module.exports = { shouldThrow, };
const CryptoZombies = artifacts.require("CryptoZombies"); const utils = require("./helpers/utils"); const zombieNames = ["Zombie 1", "Zombie 2"]; contract("CryptoZombies", (accounts) => { let [alice, bob] = accounts; let contractInstance; beforeEach(async () => { contractInstance = await CryptoZombies.new(); }); it("should be able to create a new zombie", async () => { const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice}); assert.equal(result.receipt.status, true); assert.equal(result.logs[0].args.name,zombieNames[0]); }) it("should not allow two zombies", async () => { await contractInstance.createRandomZombie(zombieNames[0], {from: alice}); await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice})); }) xcontext("with the single-step transfer scenario", async () => { it("should transfer a zombie", async () => { // TODO: Test the single-step transfer scenario. }) }) xcontext("with the two-step transfer scenario", async () => { it("should approve and then transfer a zombie when the approved address calls transferForm", async () => { // TODO: Test the two-step scenario. The approved address calls transferFrom }) it("should approve and then transfer a zombie when the owner calls transferForm", async () => { // TODO: Test the two-step scenario. The owner calls transferFrom }) }) })

提问 —— 假如 Alice 想把她的僵尸发送给 Bob. 我们要对其测试吗?

当然!

如果你一直有上前面的课,那么你应该知道,我们的僵尸继承自 ERC721ERC721 规范有两种不同的方法来转移代币:

(1)

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

第一种方法是 Alice(其主人)调用 transferFrom,她的 address 作为 _from 参数,Bob 的 address 作为 _to 参数,以及她想要转移的 zombieId

(2)

function approve(address _approved, uint256 _tokenId) external payable;

然后

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

第二种方法是, Alice 首先用 Bob 的地址和 zombieId 调用 approve 。然后,合约存储了批准 Bob 带走僵尸的信息。接下来,当 Alice 或 Bob 调用 transferFrom 时,合约检查该 msg.sender 是否等于 Alice 或 Bob 的地址。如果是,它将把僵尸转移给 Bob。

我们将这两种转移僵尸的方式称为“场景”。为了测试每个场景,我们需要创建两个不同的测试组,并为它们有意义的描述。

为什么给它们分组?我们只有不多几个测试啊……

是的,现在我们的逻辑很简单,但情况并不总是这样。第二种场景(即 transferFrom 之前的 approve)依然需要至少两个测试:

  • 首先,我们必须检查 Alice 自己是否能够转移僵尸。

  • 其次,我们还要检查是否允许 Bob 运行 transferFrom

此外,以后你可能想添加需要不同测试的其他功能。我们认为最好是从一开始就设置一个可扩展结构 😉。让其他人更容易理解你的代码,也让过了很久之后的你自己更容易理解。

注意:如果后来你与其他程序员一起合作,你会发现他们更有可能遵循你在初始代码中制定的任何约定。如果你想做成一个大项目,有效的合作是你需要的关键技能之一。有了这些能帮助你尽早实现的良好习惯,你的程序员生涯会更轻松、也会走得更远。

context 函数

对测试进行分组, Truffle 提供了一个叫做 context 的函数。来看看如何使用它来更好地组织我们的代码:

context("with the single-step transfer scenario", async () => {
    it("should transfer a zombie", async () => {
      // TODO: Test the single-step transfer scenario.
    })
})

context("with the two-step transfer scenario", async () => {
    it("should approve and then transfer a zombie when the approved address calls transferForm", async () => {
      // TODO: Test the two-step scenario.  The approved address calls transferFrom
    })
    it("should approve and then transfer a zombie when the owner calls transferForm", async () => {
        // TODO: Test the two-step scenario.  The owner calls transferFrom
     })
})

如果我们把这个添加到我们的 CryptoZombies.js 文件,然后运行 truffle test,输出会像这样:

Contract: CryptoZombies
    ✓ should be able to create a new zombie (100ms)
    ✓ should not allow two zombies (251ms)
    with the single-step transfer scenario
      ✓ should transfer a zombie
    with the two-step transfer scenario
      ✓ should approve and then transfer a zombie when the owner calls transferForm
      ✓ should approve and then transfer a zombie when the approved address calls transferForm


  5 passing (2s)

呃...

再看一下 —— 上面的输出有个问题。看起来所有的测试都通过了,这显然是错误的,因为我们根本就还没有写它们!!

幸好,有一个简单的解决方案 —— 如果我们在 context() 函数前面加上一个 x,像这样:xcontext(),那么 Truffle 将跳过那些测试。

注意:x 也可以放在 it() 函数前面。当编写完这些函数的测试时,不要忘记删除所有的 x 哦!

现在,我们来运行 truffle test。输出会像这样:

Contract: CryptoZombies
    ✓ should be able to create a new zombie (199ms)
    ✓ should not allow two zombies (175ms)
    with the single-step transfer scenario
      - should transfer a zombie
    with the two-step transfer scenario
      - should approve and then transfer a zombie when the owner calls transferForm
      - should approve and then transfer a zombie when the approved address calls transferForm


  2 passing (827ms)
  3 pending

其中“-”表示使用了“x”标记跳过的测试。

很厉害,对吧?现在你可以一边运行测试,一边标记出不久之后需要编写测试的空函数。

实战演习

  1. 复制/粘贴上面的代码。

  2. 我们暂时先 跳过 context 新函数。

我们的测试只是空 shell,为了实现它们需要编写大量的逻辑。在接下来的章节中,我们将分段讲解。