Truffle
用於執行和編寫測試的 Truffle 替代品
Truffle 為乙太坊智能合約編寫測試提供了許多便利。好處包括不需要像 Ganache 這樣的單獨鏈流程,連結和部署庫合約的複雜自動化,如 SafeMath。但是,控制是逆向的,必須使用 truffle 命令執行測試,並且必須遵循 Mocha/Chai 模式。
有沒有鬆露的替代品,特別是
- 將使用像 Jest 這樣的現代和標準測試執行器,而不是自定義包裝器命令
- 與 TypeScript 一起玩得很好
- 輕鬆設置和拆除程序內區塊鏈
- 支持自動編譯、連結和部署複雜合約,如 SafeMath 庫
我為 Jest 找到了這個例子,但它是未完成的項目。Ganache 和 Jest 還有一個,但非常簡化,不支持連結合約。
我發現openzeppelin-test-environment解決了我的問題。它允許臨時乙太坊區塊鏈設置、合約部署等相對容易。
下面是我原來的 Truffle + TypeScript 測試翻譯成 OpenZeppelin + Jest + power-assert。
有一個測試
import assert = require('assert'); import { accounts, contract } from '@openzeppelin/test-environment'; import { BN, // Big Number support constants, // Common constants, like the zero address and largest integers } from '@openzeppelin/test-helpers'; // https://etherscan.io/address/0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7#readContract const TOKEN_1ST_TOTAL_SUPPLY = new BN('93468683899196345527500000'); // Ethereum accounts used in these tests const [ deployer, // Deploys the smart contract owner, // Owns the initial supply user2 // Random dude who wants play with tokens ] = accounts; // Loads a compiled contract using OpenZeppelin test-environment const Dawn = contract.fromArtifact('Dawn'); beforeEach(() => { // No setup }); afterEach(() => { // No setup }); test('The supply should match original token', async () => { const token = await Dawn.new(owner, { from: deployer }); const supply = await token.totalSupply(); // Big number does not have power-assert support yet - https://github.com/power-assert-js/power-assert/issues/124 assert(supply.toString() == TOKEN_1ST_TOTAL_SUPPLY.toString()); }); test("Token should allow transfer", async () => { const token = await Dawn.new(owner, { from: deployer }); const amount = new BN("1") * new BN("1e18"); // Transfer 1 whole token await token.transfer(user2, amount, { from: owner }); const balanceAfter = await token.balanceOf(user2); assert(balanceAfter.toString() == amount.toString()); }); test("Token tranfers are disabled after pause", async () => { const token = await Dawn.new(owner, { from: deployer }); const amount = new BN("1") * new BN("1e18"); // Transfer 1 whole token // Pause await token.pause({ from: owner }); assert(await token.paused()); // Transfer tokens fails after the pause assert.rejects(async () => { await token.transfer(user2, amount, { from: owner }); }); }); test("Token tranfers can be paused by the owner only", async () => { const token = await Dawn.new(owner, { from: deployer }); const amount = new BN("1") * new BN("1e18"); // Transfer 1 whole token // Transfer tokens fails after the pause assert.rejects(async () => { await token.pause({ from: user2 }); }); }); test("Token cannot be send to 0x0 null address by accident", async () => { const token = await Dawn.new(owner, { from: deployer }); const amount = new BN("1") * new BN("1e18"); // Transfer 1 whole token assert.rejects(async () => { await token.transfer(constants.ZERO_ADDRESS, amount, { from: owner }); }); });
原始松露測試(Mocha + power-assert)
const Dawn = artifacts.require("Dawn"); var assert = require('assert'); // Power assert https://github.com/power-assert-js/espower-typescript // https://etherscan.io/address/0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7#readContract const TOKEN_1ST_TOTAL_SUPPLY = web3.utils.toBN('93468683899196345527500000'); contract("Dawn", ([deployer, user1, user2]) => { const tokenOwner = user1; it("should have total supply of 1ST token after deploy", async () => { const dawn = await Dawn.new(tokenOwner, { from: deployer }); const supply = await dawn.totalSupply(); assert(supply.toString() == TOKEN_1ST_TOTAL_SUPPLY.toString()); }); it("should allow transfer", async () => { const dawn = await Dawn.new(tokenOwner, { from: deployer }); const amount = web3.utils.toWei("1", "ether"); // 1 full token // Transfer tokens await dawn.transfer(user2, amount, { from: tokenOwner }); const balanceAfter = await dawn.balanceOf(user2); assert(balanceAfter.toString() == amount.toString()); }); it("should not allow transfers after pause", async () => { const dawn = await Dawn.new(tokenOwner, { from: deployer }); const amount = web3.utils.toWei("1", "ether"); // 1 full token // Pause await dawn.pause({ from: tokenOwner }); assert(await dawn.paused()); // Transfer tokens fails assert.rejects(async () => { await dawn.transfer(user2, amount, { from: user1 }); }); }); it("should not allow pause by a random person", async () => { const dawn = await Dawn.new(tokenOwner, { from: deployer }); // Transfer tokens fails assert.rejects(async () => { await dawn.pause({ from: user2 }); }); }); });
我的 package.json
{ "name": "dawntoken", "version": "1.0.0", "description": "", "main": "truffle.js", "directories": { "test": "test" }, "scripts": { "generate": "truffle compile && typechain --target truffle './build/**/*.json'", "test": "jest", "tsc": "tsc --noEmit" }, "author": "", "license": "ISC", "devDependencies": { "@openzeppelin/test-environment": "^0.1.3", "@openzeppelin/test-helpers": "^0.5.4", "@types/jest": "^25.1.3", "@types/power-assert": "^1.5.3", "babel-jest": "^25.1.0", "babel-preset-power-assert": "^3.0.0", "espower-typescript": "^9.0.2", "jest": "^25.1.0", "power-assert": "^1.6.1", "ts-jest": "^25.2.1", "ts-node": "^8.6.2", "typescript": "^3.8.3", "babel-core": "^6.26.3" }, "dependencies": { "@truffle/hdwallet-provider": "^1.0.32", "bignumber.js": "^9.0.0", "dotenv": "^8.2.0", "ganache-core": "^2.10.2", "openzeppelin-solidity": "^2.5.0", "solc": "^0.6.3", "truffle": "^5.1.16", "web3": "^1.2.6" }, "jest": { "verbose": true, "preset": "ts-jest", "testMatch": ["**/tests/*.ts"], "testEnvironment": "node", "globals": { "ts-jest": { "babelConfig": { "presets": [ "power-assert" ] } } } } }