This is the 5th post in the Starting Ethereum focus series.Other posts include:
- Starting with Ethereum - Setup
- Starting with Ethereum - Writing a contract
- Starting with Ethereum - Deploying and running a contract
- Starting with Ethereum - Smart contracts
- Starting with Ethereum - Industrialization (this post)
Last week, we finally developed a contract providing some value in the form of a rough-around-the-edges voting application.
There’s one huge issue, though: there are no tests! As a professional developer, this is just not acceptable. Also, there’s no framework to ease greenfield developments, no automated deployment process, etc. To sum it up, Ethereum comes with no tools aimed at state-of-the-art software development out-of-the-box.
Since this is a huge issue, there’s third-party tooling available in the form of the Truffle framework.
- Built-in smart contract compilation, linking, deployment and binary management
- Automated contract testing for rapid development
- Scriptable deployment & migrations framework
- Network management for deploying to both public & private networks
- Access to hundreds of external packages
- Interactive console for direct contract communication
- External script runner that executes scripts within a Truffle environment
- Built for speed
http://truffleframework.com/
In this post, we’ll focus on testing and deployment.
Setup
Some steps are required before proper starting.
Installing Truffle
Truffle comes into the form of a npm package.
npm install -g truffle
Too bad if you’re allergic to JavaScript, because the rest of the post contains a lot of it.
Initializing a project
- Create a dedicated folder for the project
- Inside it, initialize the Truffle template
mkdir referendum
cd referendum
truffle init
Once executed, the following structure will be present at the project folder root:
Folder/file | Content | ||
---|---|---|---|
|
Smart contracts, written in Solidity. Add your own contracts there.
|
||
|
Deployment scripts, written in JavaScript |
||
|
Test files, written either in Solidity or in JavaScript. Add your own tests there. |
||
|
Truffle configuration file for advanced usage. |
Testing
Truffle allows to test contracts both through Solidity and JavaScript files. From a 10,000 feet view, Solidity tests are akin to integration tests while JavaScript are more like end-to-end tests.
Both need to be targeted at a contract deployed on an Ethereum network. |
Creating Solidity tests
I tried to first write my tests in Solidity, because I’m not a JavaScript fan… and felt the pain:
- It requires contracts and test methods to match a pattern - like with JUnit before v4.
- There’s no mocking.
- And testing for exceptions requires a huge hack.
At that point, I was fed up and ran back to JavaScript pretty fast.
Creating JavaScript tests
Truffle doesn’t reinvent the wheel: under the cover, it reuses existing frameworks, Mocha for testing and Chai for assertions.
There are a few requirements for tests. They should:
- be placed in the
test
folder - that’s pretty obvious - have a standard
.js
extension - follow the standard Mocha test structure, replacing the
describe()
function with the customcontract()
function
Here’s an example of such a test for the Referendum
contract :
'use strict';
let VotingToken = artifacts.require('VotingToken'); (1)
let ReferendumContract = artifacts.require('ReferendumContract'); (1)
contract('ReferendumContract', () => {
const totalSupply = 16;
const votesCount = totalSupply - 1;
let token;
let contract;
it('should be properly initialized', async () => {
token = await VotingToken.new('DUMMY', votesCount); (2)
let question = 'Why?';
contract = await ReferendumContract.new(question, votesCount, token.address); (2)
assert.equal(await contract.getQuestion.call(), question); (3)
assert.equal(await contract.isOpen.call(), true); (3)
});
});
Interesting bits in the above snippet are the following:
1 | artifacts.require() tells which deployed contracts the script requires. |
2 | new() deploys such a contract |
3 | When reading data, using call() instead of the real method doesn’t cost any Ether.
This should be preferred over calling the "real" underlying method |
The above snippet leverages syntax and features from ECMAScript 6 (2015!), despite all available online samples written with older versions.
In particular, it takes advantage of It’s advised to use those features, as it makes the code so much more readable. |
Executing tests
The easiest way to start executing tests is the following:
truffle develop (1)
test (2)
1 | Creates a brand new Ethereum network and fires up the Truffle development console |
2 | Launch all tests situated in the test folder |
Initializing a new blockchain should give out the following output:
Truffle Develop started at http://localhost:9545/ Accounts: (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57 (1) 0xf17f52151ebef6c7334fad080c5704d77216b732 (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544 (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5 (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5 (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de Private Keys: (0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3 (1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f (2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1 (3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c (4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418 (5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63 (6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8 (7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7 (8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4 (9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5 Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
This is the result from passing the tests:
Using network 'develop'. Contract: ReferendumContract ✓ should be properly initialized (128ms) ✓ should have no votes upfront ✓ should return proper results and burn voting token when casting votes (61ms) ✓ should close vote when all votes have been cast (61ms) Contract: VotingToken ✓ should have 5 tokens initialized in the first account (46ms) ✓ transfer should decrease owner account balance without impact on total supply (74ms) ✓ burn should check if owner balance has enough tokens ✓ burn should decrease total supply and decrease owner account balance (43ms)
Alternatively, one can use truffle console
to connect to an existing Ethereum network.
In that case, an existing Ethereum client should be running and accepting RPC connections:
geth --rinkeby --syncmode fast --cache 1024 --ipcpath ~/Library/Ethereum/geth.ipc --rpc
truffle console
test
Remember to first unlock your account in a separate process:
geth attach
personal.unlockAccount(personal.listAccounts[0], 'password', 10000);
Client binding should be configured in the truffle.js
|
Deploying
Deployment is handled by the scripts located in the migrations
folder.
Such scripts are numbered.
They are meant to be run only once on a specific blockchain, so that next run won’t execute them again (unless specifically configured to do that).
This is implemented on the blockchain itself, through the Migrations.sol
contract, created during project initialization.
There’s a script named 1_initial_migration.js
, configured to deploy it, as the first deployment.
The deployment script for the previous VotingToken
and ReferendumContract
looks like:
var referendum = artifacts.require("ReferendumContract"); (1)
var token = artifacts.require("VotingToken"); (1)
module.exports = (deployer) => { (2)
deployer.deploy(token, "DUMMY", 5).then(() => { (3)
deployer.deploy(referendum, "Are you ok?", 6, token.address); (4)
});
};
1 | As for testing, this tells which contracts will be required |
2 | The documentation states:
"All migrations must export a function via the module.exports syntax.
The deployer object is your main interface for staging deployment tasks." |
3 | deployer.deploy() obviously deploys the contract.
It accepts as the first argument the contract object returned by the artifacts.require() , and as following parameters the contract constructor arguments. |
4 | The referendum contract requires the token contract.
Hence, deployment of the former is chained through a callback using the later’s address field. |
To launch those scripts, execute the following command:
migrate
This produces the following output:
Using network 'development'. Running migration: 1_initial_migration.js Deploying Migrations... ... 0xe596d8eae73239195fa509bd6d271643f3077c23ff0d89b6cfdbd10c2ee990a4 Migrations: 0x0984883bb7f65b3ab11e84c10bf2cc97a8583fb1 Saving successful migration to network... ... 0xa7302dd463b1f05aea48bcf4beeb267ce73b64cd365981a163af16a567e2748f Saving artifacts... Running migration: 2_referendum_migration.js Deploying VotingToken... ... 0xd759398b331029053fa333d71eaa818e74b4614b0050ccca52801233fc59076b VotingToken: 0x4bc1ab343c69944d2aba0e7fa3c77ac7d4ed4577 Saving successful migration to network... Deploying ReferendumContract... ... 0x86bd810a3b5f8c0fd2b582e1b49ab9bc5bffe8e47eb3776cb5838d91e2b480ce ... 0xf0fb9ba91b8f1da15af837efc2287d2df6101b0d6562f049ac9d0a5261abe667 Saving artifacts... ReferendumContract: 0x3e94dfb44f1e121ed5c0b883b3ea473981d3e014
One should be already connected to an Ethereum network, either through truffle development or truffle console .
|
Conclusion
At this point, you should be able to test and deploy your contracts in an industrialized way.
It’s now up to you to start your own journey on the Ethereum blockchain.