suggested changes

pull/5370/head
aniket-engg 5 years ago
parent 343cb0442c
commit ae1e7825b7
  1. 25
      docs/unittesting.md
  2. 67
      docs/unittesting_examples.md

@ -45,38 +45,45 @@ Remix facilitates users with various types of customizations to test a contract
**1. Custom Compiler Context** **1. Custom Compiler Context**
`Solidity Unit Testing` refers `Solidity Compiler` plugin for compiler configurations. One can provide customized inputs for `Compiler`, `EVM Version` & `Enable Optimization`. `Solidity Unit Testing` refers `Solidity Compiler` plugin for compiler configurations. One can provide customized inputs for `Compiler`, `EVM Version` & `Enable Optimization` and these will be the configuration settings used for contract compilation before running unit tests.
![](images/a-unit-testing-custom-compiler-config.png) ![](images/a-unit-testing-custom-compiler-config.png)
**2. Custom Transaction Context** **2. Custom Transaction Context**
For a contract method interaction, prime parameters of transaction are `from` address, `value` & `gas`. Usually, we need to test method's behaviour under different values of these parameters. For a contract method interaction, prime parameters of transaction are `from` address, `value` & `gas`. Usually, we need to test a method's behaviour under different values of these parameters.
Remix provides the functionality of custom `msg.sender` & `msg.value` of transaction using method devdoc like: Remix provides the functionality of custom `msg.sender` & `msg.value` of transaction using method devdoc like:
``` ```
/// #sender: account-0 /// #sender: account-0
/// #value: 10 /// #value: 10
function checkSenderIs0AndValueis10 () public payable{ function checkSenderIs0AndValueis10 () public payable {
Assert.equal(msg.sender, TestsAccounts.getAccount(0), "wrong sender in checkSenderIs0AndValueis10"); Assert.equal(msg.sender, TestsAccounts.getAccount(0), "wrong sender in checkSenderIs0AndValueis10");
Assert.equal(msg.value, 10, "wrong value in checkSenderIs0AndValueis10"); Assert.equal(msg.value, 10, "wrong value in checkSenderIs0AndValueis10");
} }
``` ```
Things to keep in mind while using custom transaction context:
**Note:** To use custom `msg.sender` functionality, `remix_accounts.sol` should be imported in your test file. 1. Parameters must be defined in devdoc of related method
2. Each parameter key should be prefixed with a hash (**#**) and end with a colon following a space (**: **) like `#sender: ` & `#value: `
3. For now, customization is available for parameters `sender` & `value` only
4. Sender is `from` address of a transaction which is accessed using `msg.sender` inside a contract method. It should be defined in a fixed format as '**account-**<account_index>'
5. `<account_index>` varies from `0-2` before remix-ide release v0.10.0 and `0-9` afterwards
6. `remix_accounts.sol` must be imported in your test file to use custom `sender`
7. Value is `value` sent along with a transaction in `wei` which is accessed using `msg.value` inside a contract method. It should be a number.
Complete example can be seen in [examples](./unittesting_examples) section. Regarding `gas`, Remix estimates the required gas for each transaction internally. Still if a contract deployment fails with `Out-of-Gas` error, it tries to redeploy it by doubling the gas. Deployment failing with double gas will show error: ```contract deployment failed after trying twice: The contract code couldn't be stored, please check your gas limit```
Regarding `gas`, Remix estimate the required gas for each transaction internally. Still if a contract deployment fails with `Out-of-Gas` error, it tries to redeploy it by doubling the gas. Deployment failing with double gas will show error: `contract deployment failed after trying twice: The contract code couldn\'t be stored, please check your gas limit` Various test examples can be seen in [examples](./unittesting_examples) section.
Points to remember Points to remember
------------------ ------------------
* A test contract can not have a method with parameters. Having one such method will show error: `Method 'methodname' can not have parameters inside a test contract` * A test contract cannot have a method with parameters. Having one such method will show error: `Method 'methodname' can not have parameters inside a test contract`
* No. of test accounts are `3` before remix-ide release v0.10.0 and `10` afterwards * Number of test accounts are `3` before remix-ide release v0.10.0 and `10` afterwards
* A test file which imports `remix_accounts.sol` might not compile successfully with `Solidity Compiler` plugin but it will work fine with Solidity Unit Testing plugin * A test file which imports `remix_accounts.sol` might not compile successfully with `Solidity Compiler` plugin but it will work fine with Solidity Unit Testing plugin.
Remix-tests Remix-tests
---------------------- ----------------------

@ -88,65 +88,74 @@ Test contract/program: `Sender_test.sol`
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix import "remix_tests.sol"; // this import is automatically injected by Remix
import "remix_accounts.sol"; import "remix_accounts.sol";
import "./Sender.sol"; import "./sender.sol";
// Inherit 'Sender' contract // Inherit 'Sender' contract
contract SenderTest is Sender { contract SenderTest is Sender {
address account0; /// Define variables referring to different accounts
address account1; address acc0;
address account2; address acc1;
address acc2;
/// Initiate accounts variable /// Initiate accounts variable
function beforeAll() public { function beforeAll() public {
account0 = TestsAccounts.getAccount(0); acc0 = TestsAccounts.getAccount(0);
account1 = TestsAccounts.getAccount(1); acc1 = TestsAccounts.getAccount(1);
account2 = TestsAccounts.getAccount(2); acc2 = TestsAccounts.getAccount(2);
} }
/// Test if initial owner is set correctly /// Test if initial owner is set correctly
function testInitialOwner() public { function testInitialOwner() public {
// account-0 is default account, so current owner should be account0 // account at zero index (account-0) is default account, so current owner should be acc0
Assert.equal(getOwner(), account0, 'owner should be account-0'); Assert.equal(getOwner(), acc0, 'owner should be acc0');
} }
/// Update owner. This method will be called by account0 as there is not custom sender appointed /// Update owner first time
/// This method will be called by default account(account-0) as there is no custom sender defined
function updateOwnerOnce() public { function updateOwnerOnce() public {
// check method call is as expected // check method caller is as expected
Assert.ok(msg.sender == account0, 'caller should default account i.e. account0'); Assert.ok(msg.sender == acc0, 'caller should be default account i.e. acc0');
// update owner address to account1 // update owner address to acc1
updateOwner(account1); updateOwner(acc1);
// check if owner is set to expected account // check if owner is set to expected account
Assert.equal(getOwner(), account1, 'owner should be updated to account1'); Assert.equal(getOwner(), acc1, 'owner should be updated to acc1');
} }
/// Update owner again by defining custom sender /// Update owner again by defining custom sender
/// #sender: account-1 /// #sender: account-1 (sender is account at index '1')
function updateOwnerOnceAgain() public { function updateOwnerOnceAgain() public {
// check if caller is custom and is as expected // check if caller is custom and is as expected
Assert.ok(msg.sender == account1, 'caller should be custom account i.e. account1'); Assert.ok(msg.sender == acc1, 'caller should be custom account i.e. acc1');
// update owner address to account2. This will be successful because account1 is current owner & caller both // update owner address to acc2. This will be successful because acc1 is current owner & caller both
updateOwner(account2); updateOwner(acc2);
// check if owner is set to expected account i.e. account2 // check if owner is set to expected account i.e. account2
Assert.equal(getOwner(), account2, 'owner should be updated to account2'); Assert.equal(getOwner(), acc2, 'owner should be updated to acc2');
} }
} }
``` ```
### 3. Testing method execution ### 3. Testing method execution
With Solidity, one can verify the changes made by a method in storage by accessing those variables out of a contract. But while testing whether method execution was successful and if execution failed, what was the reason behind it, can also be an importnat case. With Solidity, one can directly verify the changes made by a method in storage by retrieving those variables from a contract. But testing for a successful method execution takes some strategy. Well that is not entirely true, when a test is successful - it is usually obvious why it passed. However, when a test fails, it is essential to understand why it failed.
Solidity introduced `try-catch` statement in version 0.6.0 which helps a lot to solve this purpose. Previously, this could be achieved using low-level call to some extent. Here is the example to test such a scenario:
To help in such cases, Solidity introduced the `try-catch` statement in version `0.6.0`. Previously, we had to use low-level calls to track down what was going on.
Here is an example test file that use both **try-catch** blocks and **low level calls**:
Contract/Program to be tested: `AttendanceRegister.sol` Contract/Program to be tested: `AttendanceRegister.sol`
``` ```
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
contract AttendanceRegister { contract AttendanceRegister {
struct Student{ struct Student{
string name; string name;
uint class; uint class;
} }
event Added(string name, uint class, uint time);
mapping(uint => Student) public register; // roll number => student details event Added(string name, uint class, uint time);
mapping(uint => Student) public register; // roll number => student details
function add(uint rollNumber, string memory name, uint class) public returns (uint256){ function add(uint rollNumber, string memory name, uint class) public returns (uint256){
require(class > 0 && class <= 12, "Invalid class"); require(class > 0 && class <= 12, "Invalid class");
require(register[rollNumber].class == 0, "Roll number not available"); require(register[rollNumber].class == 0, "Roll number not available");
@ -168,6 +177,7 @@ Test contract/program: `AttendanceRegister_test.sol`
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./AttendanceRegister.sol"; import "./AttendanceRegister.sol";
contract AttendanceRegisterTest { contract AttendanceRegisterTest {
AttendanceRegister ar; AttendanceRegister ar;
@ -278,6 +288,7 @@ Test contract/program: `Value_test.sol`
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; import "remix_tests.sol";
import "./Value.sol"; import "./Value.sol";
contract ValueTest{ contract ValueTest{
Value v; Value v;

Loading…
Cancel
Save