title | actions | requireLogin | material | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
僵尸战斗 |
|
true |
|
哇哦!前几章的信息量有点大哦,但很多都是基础内容。
所以,全部的场景都讲完了吗?还没有哦,压轴的部分肯定会放在最后啦。
我们创建了一个僵尸游戏,那么最精彩的部分是僵尸之间的战斗,对吧?
这个测试非常简单,包括以下步骤:
- 第一步,我们将创建两个新僵尸 —— 一个 Alice 的,一个 Bob 的。
- 第二步,Alice 将以 Bob 的
zombieId
作为参数在她的僵尸上运行attack
。 - 最后,为了使测试通过,我们将检查
result.receipt.status
是否等于true
。
假设我已经快速编写了所有这些逻辑,将其封装在一个 it()
函数中,并运行了 truffle test
测试。
然后,输出会像这样:
Contract: CryptoZombies
✓ should be able to create a new zombie (102ms)
✓ should not allow two zombies (321ms)
✓ should return the correct owner (333ms)
1) zombies should be able to attack another zombie
with the single-step transfer scenario
✓ should transfer a zombie (307ms)
with the two-step transfer scenario
✓ should approve and then transfer a zombie when the approved address calls transferFrom (357ms)
5 passing (7s)
1 failing
1) Contract: CryptoZombies
zombies should be able to attack another zombie:
Error: Returned error: VM Exception while processing transaction: revert
哦,测试失败了
为什么呢?
要搞清楚是怎么回事。首先,我们来仔细看看 createRandomZombie()
背后的代码:
function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
目前为止都没问题。继续,看下 _createZombie()
:
function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);
emit NewZombie(id, _name, _dna);
}
看到问题了吗?
测试失败的原因是因为我们在游戏中增加了一个冷却时间,使得僵尸在攻击(或进食)后必须等待1天才能再次攻击。
没有这个的话,僵尸每天可以无数次攻击和增殖,这将让游戏很弱智。。
现在我们该怎么办呢…… 等一天吗?
幸好,我们不必等那么久。事实上,根本就不需要等。因为 Ganache 提供了一种通过两个辅助功能及时前行的方法:
evm_increaseTime
: 增加下一个区块的时间。evm_mine
: 挖一个新区块.
你甚至不需要 Tardis 或 DeLorean 来进行这种时间旅行。
让我来解释下这些函数是如何运行的:
-
每次挖一个新区块时,矿工都会向它添加一个时间戳。假设在第5个区块中挖到了生成僵尸的事务。
-
接下来,我们调用
evm_increaseTime
,但由于区块链是不可变的,所以不可能修改现有区块。所以,当合约检查时间时,它不会增加。 -
如果我们运行
evm_mine
,那么第6个区块就会被挖出(并加上时间戳),这意味着,当我们让僵尸投入战斗时,智能合约将“看到”一天已经过去了。
综上所述,我们可以通过时间旅行来修正我们的测试,具体以下:
await web3.currentProvider.sendAsync({
jsonrpc: "2.0",
method: "evm_increaseTime",
params: [86400], // there are 86400 seconds in a day
id: new Date().getTime()
}, () => { });
web3.currentProvider.send({
jsonrpc: '2.0',
method: 'evm_mine',
params: [],
id: new Date().getTime()
});
嗯,这段代码不错,但是我们不会将这个逻辑添加到我们的 CryptoZombies.js
文件中。
我们已经将所有内容移动到一个名为 helpers/time.js
的新文件中了。要增加时间,只需调用:time.increaseTime(86400);
嗯,还不够完美。毕竟,鬼才知道一天有多少秒呢。
所以我们添加了另一个名为 days
的 辅助函数,它以希望增加时间的天数作为参数。你可以这样来调用这个函数:await time.increase(time.duration.days(1))
注意:很明显,时间旅行在主网或任何由矿工保护的现有测试链上都是不可用的。如果有人可以改变现实世界中时间的运作方式,那岂不就乱套了!对于测试智能合约,时间旅行可是程序员相当重要的一项技能。
我们继续填充了那版失败的测试。
- 往下滚动,看下我们给你的留言。接下来,如上所示,通过运行
await time.increase
来修复测试用例。。
准备就绪。开始运行 truffle test
:
Contract: CryptoZombies
✓ should be able to create a new zombie (119ms)
✓ should not allow two zombies (112ms)
✓ should return the correct owner (109ms)
✓ zombies should be able to attack another zombie (475ms)
with the single-step transfer scenario
✓ should transfer a zombie (235ms)
with the two-step transfer scenario
✓ should approve and then transfer a zombie when the owner calls transferForm (181ms)
✓ should approve and then transfer a zombie when the approved address calls transferForm (152ms)
好了!这就是本章最后一步。