Getting Real-World Test Data from Production

Why?

To responsibly test changes before deploying them to production, we need a test environment that behaves, in as many aspects as possible, the same way as our production environment does.

Integration tests should be running in an environment that is as close to production as possible.

We want the ability to reproduce production issues in our test environment for further analysis.

If our test environment only contains engineered test data, we’re going to potentially miss or have trouble reproducing a lot of input data or program state related issues.

Program State Issues

An example of a program state related issue would be having inconsistent data in our database. This would likely lead to undesired application behavior (bugs).

If our test environment only consists of manually engineered database records, it’s possible this bug would be missed in test, making it only detectable in production.

Input Data Issues

An example of an input data issue would be a program that reads CSV files into a database has an issue processing null fields, and some input data contains null fields.

If we only use manually engineered test input data, with no null fields, we will not encounter this issue in test.

Detecting/Reproducing Program State Issues: Mirroring Existing Production State in Test

Copy Down Production Data Stores

In deterministic applications, all program state is driven by input data. That being said, it’s often not possible to replay all input events from all-time or replicate historical one-off manual data migrations performed in production. This is why we should consider copying data stores to replicate application state.

The general idea here is that we want to copy down, from production to test, the data stores our application will interact with (dynamo tables, MySQL databases, etc).

Preferably, you’d do a one-time migration from your production data store to your test data store, and then as an ongoing synchronization method, copy all future state changing events from production to test (as we will talk about in the next section).

Figure 1: Production and test environments without a method of copying production data to test
Figure 2: Depiction of how to implement production mirroring into test environment

If it is not possible to duplicate all input data that changes state into your test environment (from production), the only feasible solution may be to set up timed jobs that copy data from production to test on a periodic basis. There are many tools that support running jobs on a timer, including Jenkins and Rundeck.

Figure 3: Depiction of a cron-based data copy down

Remember, our goal here is to create a test environment that replicates our production environment as closely as possible.

Stateless Applications

If your application is stateless (does not store data), there is no need to implement a data copy down process to mirror existing application state.

Addressing Input Data Related Issues

We want to copy input events from our production environment to our test environment to both detect input data related issues as well as maintain consistency with our production application state (in stateful applications).

Essentially, we need two things:

  1. A mechanism to copy input data (HTTP requests, events, etc) from production to test. This can be automatic or on-demand with some qualifier.
  2. Integration tests that incorporate as much real-world data as possible.

Often a combination of existing program state and specific input data are required to reproduce a bug, so it’s important to address both aspects of copying down production data.

Let it Bake

Once developers have a test environment that is comparable to their production environment: the infrastructure is the mirrored, the application state is mirrored, and the data being processed is flowing through both environments, they can let their releases “bake” in test before releasing to production.

Developers can deploy new releases to the test environment, wait a specified period of time, and then compare the output of their test deployment with the output of their production deployment.

A delayed release strategy will lower operational risk by allowing developers to see how their code behaves in a production-like environment before actually deploying to production.

Potential Pitfalls

Data is Confidential

If you are working with confidential data, or other limitations prevent you from copying production data to test as-is, you’ll need to implement a data anonymization process.

Figure 4: Data anonymization applied to input data copy down

If the fields considered confidential are not processed by the program under test, it may be sufficient to have your copy down process replace those irrelevant values with dummy values without any negative consequence (e.g. if SSN is irrelevant for our app, we can just zero-fill the field when copying down to test).

However, if the fields considered confidential are processed by the program under test, we will need to do consistent replacement.

Consistent Replacement

If you have a field that is considered sensitive and cannot be copied to the test environment, but, the field is used by the application, we must do a consistent replacement when anonymizing the data. Consistent replacement is where we replace sensitive fields with a consistent dummy value, using a persisted mapping, during our anonymization process.

Figure 5: Data anonymization applied to input data copy down with persisted mappings for consistent replacement

Take, for example, an application that reads in banking records, and indexes transactions in a database by the customer’s last name. If the last name field is considered confidential information, we cannot just randomly replace the field when anonymizing, we must replace the field with a consistent replacement value every time.

If, the customers’ last name was Johnson, and we replace it with Phillips, we must store that mapping (Johnson->Phillips) to ensure every future instance of Johnson is replaced with Phillips when anonymizing future data.

The replacement mappings should be treated as sensitive production data.

Whatever data store is used to store the mappings used for consistent replacement should be very high throughput to avoid bottle-necking the system. I’d consider Redis or AWS DynamoDB for this.

Don’t forget: Your consistent replacement method should also be used when doing the initial data migration.

Example Scopes of Consistent Replacement
  • Per input file
  • Daily (clear real->dummy cache daily)
  • Permanent

Not Feasible Due to Cost or Performance

Here are some things to consider if you are running into performance barriers:

  • Limit consistent replacement scope (don’t store replacement mappings permanently if you only need them daily)
  • Multi-tenant systems: Only process a subset of tenants
  • For stateless applications: Only copy down a subset of production data

Ethereum Lottery Example Part 4: Unit Testing

Recap:

In the first 3 parts of this series (1,2,3), we covered the development of a ethereum blockchain based lottery application written in the solidity language.

pragma solidity 0.6.12;
import "./provableAPI.sol";

contract Lotto is usingProvable {
    address payable[] public entrants;
    mapping(address => uint) public balances;
    uint256 public entranceFee = 5000000000000000; //wei

    address payable public winner;

    bytes32 provableQueryId;
    event LogWinnerSelectionStarted(string message);
    event LogWinnerSelected(address winner);

    constructor () public{
        //OAR = OracleAddrResolverI(0xf1E0658Dd4218b146718ada57b962B5f44725eEA);
    }

    //this must be made public for testing
    function enter() public payable {
        require(msg.value==entranceFee, "Invalid entry fee provided.");
        require(balances[msg.sender] == 0, "User has already entered. Only one entry allowed per address.");
        require(winnerHasNotBeenSet(), "Lottery has already completed. A winner was already selected.");
        require(provableQueryHasNotRun(), "Winner selection already in progress. No entries allowed now.");

        balances[msg.sender] = msg.value;
        entrants.push(msg.sender);
    }

    function getLotteryBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function getQuantityOfEntrants() public view returns(uint count) {
        return entrants.length;
    }

    function selectWinner() public {
        require(getQuantityOfEntrants() > 0, "Requires at least one entrant to select a winner");
        require(winnerHasNotBeenSet(), "Winner has already been selected");
        require(provableQueryHasNotRun(), "Winner selection already in progress.");
        provableQueryId = provable_query("WolframAlpha", constructProvableQuery()); //TODO switch to more secure source
        emit LogWinnerSelectionStarted("Winner selection has started!" );
        //__callback function is activated
    }

    function winnerHasNotBeenSet() private view returns (bool){
        return winner == address(0);
    }

    function provableQueryHasNotRun() private view returns (bool){
        return provableQueryId == 0;
    }

    function constructProvableQuery() private view returns (string memory){
        return strConcat("random number between 0 and ", uint2str(entrants.length-1));
    }

    //provable callback for selectWinner function (this takes a while to be called)
    function __callback(bytes32 myid, string memory result) public override {
        require(msg.sender == provable_cbAddress(), "Callback invoked by unknown address");
        require(myid == provableQueryId);
        winner = entrants[parseInt(result)];
        distributeWinnings();
        emit LogWinnerSelected(winner);
    }

    function distributeWinnings() internal {
        winner.transfer(getLotteryBalance());
    }
}

(Link to source)

Most of this code should be fairly self-explanatory (I hope). If you want to know more, or wish to learn how to use the Remix IDE, check out the whole series starting from part 1 (Ethereum Lottery Example Part 1: Setting Up Environment).

Testing Tools

If we want to keep developing on this lottery application, we must get automated test coverage around our existing code to make sure we don’t introduce any regressions to existing functionality.

Truffle

Truffle is an “all-in-one” tool that provides a development blockchain environment locally and supports JavaScript based testing using Mocha and Chai.

Pros:
  • Integrates nicely with npm, Mocha and Chai giving a clear path for build standardization and dependency management (npm run test)
  • Sizable library of examples and prepackaged applications available to use and learn from (called truffle boxes: https://www.trufflesuite.com/boxes)
  • Support for advanced integration testing of contracts locally
Cons:
  • The “clean room” environment provided by Truffle caused issues when multiple test files were defined for the same contract and event polling was used (unable to parse event error)

Remix IDE Unit Testing Plugin

If you’d prefer to skip all the set-up and start writing tests immediately, Remix IDE has a built in unit testing solution called the Remix IDE Unit Testing Plugin.

Pros:
  • Integrates nicely into the Remix IDE (Useful for deploying to test chains)
  • Uses solidity, allowing contracts and tests to be written in the same language
  • Runs in the browser
Cons:
  • Not as powerful as Truffle for running complex integration testing scenarios
  • Requires learning a new testing framework (You’re likely already familiar with Mocha and Chai)
    • Some testing scenarios require tests to inherit from the contract under test, while others do not. Each of the two approaches has trade-offs that will be discussed below.
  • Remix seems to crash frequently in Firefox on Ubuntu (I will go back to using Chrome which historically seems to work best)

Examples

“Happy Path” 1 Participant Entrance Case

Single Entrant: Truffle

The motivation to breaking this test up into helper functions (with redundant assertions) is more apparent when this test is viewed in it’s complete context as a base case for more complex setups.

const truffleAssert = require('truffle-assertions');
const { waitForEvent, validEntryValue } = require('./utils');

const Lotto = artifacts.require('Lotto');

contract('Lotto', async (accounts) => {
  let lotto;

  // helpers
  async function assertContractBalance(expectedBalance) {
    const actualBalance = await lotto.getLotteryBalance.call();
    assert.equal(actualBalance, expectedBalance);
  }

  async function assertEntrantCount(expectedEntrantCount) {
    const actualEntrantCount = await lotto.getQuantityOfEntrants.call();
    assert.equal(actualEntrantCount, expectedEntrantCount);
  }

  async function enterIntoLottoAndVerifyContractState(entrant = accounts[0], expectedEntrantCount = 1) {
    await lotto.enter({ value: validEntryValue, from: entrant });
    await assertEntrantCount(expectedEntrantCount);
    await assertContractBalance(validEntryValue * expectedEntrantCount);
  }

  beforeEach(async () => {
    lotto = await Lotto.new();

    await assertContractBalance(0);
    await assertEntrantCount(0);
  });

  it('allows lottery entry', async () => {
    await enterIntoLottoAndVerifyContractState();

    await assertContractBalance(validEntryValue);
    await assertEntrantCount(1);
  });
});

(Code excerpted from LottoTruffleTest.js)

Single Entrant: Remix IDE Unit Testing Plugin: No Inheritance
pragma solidity 0.6.12;

// This import is automatically injected by Remix
import "remix_tests.sol";

// This import is required to use custom transaction context
// Although it may fail compilation in 'Solidity Compiler' plugin
// But it will work fine in 'Solidity Unit Testing' plugin
import "remix_accounts.sol";
import "./LottoMock.sol";
import "../contracts/Lotto.sol";

contract LottoEntranceTestNoInherit {
    Lotto lotto;

    function beforeEach() public {
        lotto = new Lotto();
    }

    /// #value: 5000000000000000
    function enterSuccessfullySingleEntrant() public payable {
        Assert.equal(lotto.getQuantityOfEntrants(), uint256(0), "expecting 0 entrants before entering");
        Assert.equal(lotto.getLotteryBalance(), uint256(0), "expecting 0 lottery balance before entering");

        lotto.enter{value:5000000000000000}();

        Assert.equal(lotto.getLotteryBalance(), uint256(5000000000000000), "expecting lottery balance equal to entrance fee after entering");
        Assert.equal(lotto.getQuantityOfEntrants(), uint256(1), "user should have successfully entered the lottery");
    }
}

(Excerpted from LottoRemixIDE_test.sol)

This example does not inherit from the contract under test. That is useful for making it easy to manipulate the balance of the contract under test and for testing functions from an external perspective. The downside of this is that account impersonation (testing a multiple user interaction) doesn’t work without inheriting from the contract under test,

Single Entrant: Remix IDE Unit Testing Plugin: Inheriting the Contract Under Test
pragma solidity 0.6.12;

import "remix_tests.sol";

import "remix_accounts.sol";
import "./LottoMock.sol";
import "../contracts/Lotto.sol";

contract lottoEntranceTestWithInheritance is Lotto {

    /// #value: 5000000000000000
    function enterSuccessfullySingleEntrantInheritVersion() public payable {
        Assert.equal(getQuantityOfEntrants(), uint256(0), "expecting 0 entrants before entering");
        Assert.equal(getLotteryBalance(), uint256(5000000000000000), "expecting 0 lottery balance before entering"); //this seems like an oddity with how the custom txn context is implemented with inheritance

        this.enter{value:5000000000000000}();

        Assert.equal(getLotteryBalance(), uint256(5000000000000000), "expecting lottery balance equal to entrance fee after entering");  //this seems like an oddity with how the custom txn context is implemented with inheritance
        Assert.equal(getQuantityOfEntrants(), uint256(1), "user should have successfully entered the lottery");
    }
}

(Excerpted from LottoRemixIDE_test.sol)

The important thing to note in this example is that the balance of the contract is altered during test setup by the value parameter of the custom transaction context.

Multiple Entrants Test

Multiple Entrant: Truffle
const truffleAssert = require('truffle-assertions');
const { waitForEvent, validEntryValue } = require('./utils');

const Lotto = artifacts.require('Lotto');

contract('Lotto', async (accounts) => {
  let lotto;

  // helpers
  async function assertContractBalance(expectedBalance) {
    const actualBalance = await lotto.getLotteryBalance.call();
    assert.equal(actualBalance, expectedBalance);
  }

  async function assertEntrantCount(expectedEntrantCount) {
    const actualEntrantCount = await lotto.getQuantityOfEntrants.call();
    assert.equal(actualEntrantCount, expectedEntrantCount);
  }

  async function enterIntoLottoAndVerifyContractState(entrant = accounts[0], expectedEntrantCount = 1) {
    await lotto.enter({ value: validEntryValue, from: entrant });
    await assertEntrantCount(expectedEntrantCount);
    await assertContractBalance(validEntryValue * expectedEntrantCount);
  }

  beforeEach(async () => {
    lotto = await Lotto.new();

    await assertContractBalance(0);
    await assertEntrantCount(0);
  });

  it('allows lottery entry with multiple entrants', async () => {
    await enterIntoLottoAndVerifyContractState();
    await enterIntoLottoAndVerifyContractState(accounts[1], expectedEntrantCount = 2);

    await assertContractBalance(validEntryValue * 2);
    await assertEntrantCount(2);
  });
});

(Code excerpted from LottoTruffleTest.js)

Multiple Entrant: Remix IDE Unit Testing Plugin: Inheriting the Contract Under Test

Here is where we must inherit from the contract under test to use the value parameter in the Remix Unit Testing’s custom transaction context feature. (docs)

pragma solidity 0.6.12;

import "remix_tests.sol";

import "remix_accounts.sol";
import "./LottoMock.sol";
import "../contracts/Lotto.sol";

contract LottoMultipleEntranceTest is Lotto {

    /// #sender: account-0
    /// #value: 5000000000000000
    function firstEntry() public payable {
        Assert.equal(getQuantityOfEntrants(), uint256(0), "expecting 0 entrants before entering");
        Assert.equal(msg.sender, TestsAccounts.getAccount(0), "Invalid sender");

        enter();

        Assert.equal(getQuantityOfEntrants(), uint256(1), "user should have successfully entered the lottery");
    }

    /// #value: 5000000000000000
    /// #sender: account-1
    function secondEntry() public payable {
        Assert.equal(getQuantityOfEntrants(), uint256(1), "Expecting an existing entry.");
        Assert.equal(msg.sender, TestsAccounts.getAccount(1), "Invalid sender");

        //don't call function externally to use sender mocking
        enter();

        Assert.equal(getQuantityOfEntrants(), uint256(2), "second user should have successfully entered the lottery");
    }
}

(Excerpted from LottoRemixIDE_test.sol)

Mocking in Remix Unit Testing

If you want to manipulate contract state to set up tests, create a Mock version of your contract and inherit from that in your test file. This approach won’t scale for large apps, but we can avoid having to mock complex integrations such as oracle integration. Example:

pragma solidity 0.6.12;

import "../contracts/Lotto.sol";

contract LottoMock is Lotto {
    function setWinner() public {
        winner = msg.sender;
    }
    
    function setProvableQueryId() public {
        provableQueryId = bytes32("abc");
    }
}

LottoMock.sol: Define functions to manually alter contract state for testing setup.

pragma solidity 0.6.12;

import "remix_tests.sol";

import "remix_accounts.sol";
import "./LottoMock.sol";
import "../contracts/Lotto.sol";

contract EnterWinnerAlreadySelected is LottoMock {

    // lottery already completed -> then: return money, don't enter
    /// #value: 5000000000000000
    function enterWinnerAlreadySelected() public payable {
        Assert.equal(getQuantityOfEntrants(), uint256(0), "expecting 0 entrants before entering");
        setWinner();

        try this.enter{value:5000000000000000}() {
            Assert.ok(false, 'succeed unexpected');
        } catch Error(string memory reason) {
            Assert.equal(reason, "Lottery has already completed. A winner was already selected.", "Lottery already completed. User cannot enter.");
        } catch (bytes memory ) {
            Assert.ok(false, 'failed unexpected');
        }

        Assert.equal(getQuantityOfEntrants(), uint256(0),
            "If a winner was already selected, there should not be any new entrants");
    }
}

contract EnterWinnerSelectionInProgress is LottoMock {

    // winner selection in progress -> then: return money, don't enter
    /// #value: 5000000000000000
    function enterWinnerSelectionInProgress() public payable {
        Assert.equal(getQuantityOfEntrants(), uint256(0), "expecting 0 entrants before entering");
        setProvableQueryId(); //TODO is there a better way for this

        try this.enter{value:5000000000000000}() {
            Assert.ok(false, 'succeed unexpected');
        } catch Error(string memory reason) {
            Assert.equal(reason, "Winner selection already in progress. No entries allowed now.", "Cannot enter lottery when winner selection is in progress.");
        } catch (bytes memory) {
            Assert.ok(false, 'failed unexpected');
        }

        Assert.equal(this.getQuantityOfEntrants(), uint256(0), "user should have successfully entered the lottery");
    }
}

(Excerpted from LottoRemixIDE_test.sol)

Testing Winner Selection Callback (in Truffle)

Since we’re using the Provable API as our random number oracle (see part 2 for more details), we can use the provable ethereum bridge, truffle, and solidity events to simulate the conclusion of the lottery where an oracle is called and a winner is selected.

const truffleAssert = require('truffle-assertions');
const { waitForEvent, validEntryValue } = require('./utils');

const Lotto = artifacts.require('Lotto');

contract('Lotto', async (accounts) => {
  let lotto;

  // helpers
  async function assertContractBalance(expectedBalance) {
    const actualBalance = await lotto.getLotteryBalance.call();
    assert.equal(actualBalance, expectedBalance);
  }

  async function assertEntrantCount(expectedEntrantCount) {
    const actualEntrantCount = await lotto.getQuantityOfEntrants.call();
    assert.equal(actualEntrantCount, expectedEntrantCount);
  }

  async function enterIntoLottoAndVerifyContractState(entrant = accounts[0], expectedEntrantCount = 1) {
    await lotto.enter({ value: validEntryValue, from: entrant });
    await assertEntrantCount(expectedEntrantCount);
    await assertContractBalance(validEntryValue * expectedEntrantCount);
  }

  async function selectWinnerAndWaitForCompletion() {
    const selectWinnerResult = await lotto.selectWinner();
    await truffleAssert.eventEmitted(selectWinnerResult, 'LogWinnerSelectionStarted');
    await waitForEvent('LogWinnerSelected', lotto);
  }

  beforeEach(async () => {
    lotto = await Lotto.new();

    await assertContractBalance(0);
    await assertEntrantCount(0);
  });

  it('allows winner selection with a single entrant and distributes the funds', async () => {
    await enterIntoLottoAndVerifyContractState(accounts[1]);
    const winnerBalanceBefore = await web3.eth.getBalance(accounts[1]); // after entering but before winning

    await selectWinnerAndWaitForCompletion();

    await assertContractBalance(0);
    const winnerBalanceAfter = await web3.eth.getBalance(accounts[1]);
    // balance after winning should equal balance before winning + entry fee for 1 user
    assert.equal(parseInt(winnerBalanceAfter, 10), parseInt(winnerBalanceBefore, 10) + parseInt(validEntryValue, 10),
      'Winner account balance incorrect after lottery completion.');
  });
});

(Code excerpted from LottoTruffleTest.js)

/* eslint no-await-in-loop: "off" */

const Web3 = require('web3');

const sleep = (ms) => new Promise((res) => setTimeout(res, ms));

module.exports.waitForEvent = async (eventName, contract) => {
  let events = await contract.getPastEvents(eventName, { fromBlock: 0, toBlock: 'latest' });
  let secondCounter = 0;
  while (events.length < 1) {
    console.log(`waiting for event ${secondCounter}`);
    await sleep(1000);
    secondCounter += 1;
    events = await contract.getPastEvents(eventName, { fromBlock: 0, toBlock: 'latest' });
    if (secondCounter > 30) {
      assert(false, `Timed out waiting for event: ${eventName}`);
    }
  }
};

module.exports.validEntryValue = Web3.utils.toWei('5000000', 'gwei');

utils.js

In this test, we kick off winner selection and allow the provable bridge to respond via our __callback function. We’re able to assert the callback was invoked by listening for the LogWinnerSelected event. Note the use of the emit keyword in our contract under test Lotto.sol.

For usage instructions, check out the solidity-lottery readme. Check out the provable trufflebox for other similar examples.

Full Source Code + Instructions to Run

All truffle examples

All Remix IDE Unit Testing examples

Instructions for running

Useful Resources

Ethereum Lottery Example Part 3: Collecting an Entrance Fee

See Part 2 Here

Code Change: https://github.com/samuelfrench/solidity-lottery/commit/96c1763400c3b0f8603695ee4d4e4ef537f98df8

pragma solidity 0.6.12;
import "github.com/provable-things/ethereum-api/provableAPI_0.6.sol";

contract Lotto is usingProvable {
    address[] public entrants;
    mapping(address => uint) public balances;
    
    address public winner;
    bytes32 provableQueryId;
    
    function enter() external payable {
        if(balances[msg.sender] == 0 && msg.value==5000){
            balances[msg.sender] = msg.value;
            entrants.push(msg.sender);
        } //else you have not paid the entry fee or have already entered
    }
    
    function getLotteryBalance() external returns (uint256) {
       return address(this).balance;
    }
    
    function selectWinner() public {
        if(winnerHasNotBeenSet() && provableQueryHasNotRun()){
            provableQueryId = provable_query("WolframAlpha", constructProvableQuery());
        }
    }
    
    function winnerHasNotBeenSet() private view returns (bool){
        return winner == address(0);
    }
    
    function provableQueryHasNotRun() private view returns (bool){
        return provableQueryId == 0;
    }
    
    function constructProvableQuery() private view returns (string memory){
        return strConcat("random number between 0 and ", uint2str(entrants.length-1));
    }
    
    //provable callback for selectWinner function
    function __callback(bytes32 myid, string memory result) public override {
        if(myid != provableQueryId) revert();
        winner = entrants[parseInt(result)];
    }
}

Changelog:

  1. Add balances mapping to have a constant-time way to look up entrants
  2. Make enter a payable function to accept an entrance fee
    • Check entrance fee
    • Check balance to see if they’ve entered before
  3. Add getLotteryBalance for debugging
  4. Update solidity version (not shown in commit)
    1. Add override modifier to provable callback function (updating the language version requires it)

Notes:

  1. I need to start using separate PRs to make it easy to isolate changes between previous postings
  2. Initially it was unclear to me how to send a value with a function call, use the value field by the contract deploy button before invoking a payable function

Evaluating Project Risk Based on Domain (How much testing do I need to launch?)

In some contexts, investing heavily in testing is essential. For example, when processing financial transactions at a bank you’d want to have really solid integration tests.

These tests could be a mix of engineered data, re-playing of real-world data that has been manually audited, and maybe even a business rules engine built to double check and alert for any large day-over-day anomalies in processing (a banks’ total deposits increasing 50% day-over-day would be a red flag).

At the same said bank, there is a marketing effort to serve customized banners to the customer’s browser during online banking sessions. Ultimately, the business decides that this software is not critical to the businesses’ operations. The team could then decide to launch and iterate very quickly, only bothering to maintain a critical-path integration test.

Always consider: If the software you develop has bugs, or a customer receives a less than optimal experience, what is the impact on the businesses’:

  • Operations
    • Are internal operations degraded?
    • Are sales being affected?
  • Reputation
    • How will customers’ trust for this company be affected?
    • Will internal customers lose trust with our team?
    • How will customers (internal and external) have their opinion of the company shifted?

Consider these factors when to determine what your minimum testing requirement is.

I generally error on the side of caution as I suspect many small defects or less than optimal experiences could cumulatively lead to a small, but perceptible harm to the companies’ reputation in the customer’s mind.

Ultimately, the more tests you have, the fewer defects you’ll have, the less manual testing you’ll have to do, the less documentation you’ll have to write, and you’ll end up with a more maintainable product (in most cases).

Under external pressures, like business deadlines, or given a desire to A/B test a new feature, we, as developers must determine what is the minimal amount of testing that could be applied is while still maintaining professional standards.

Ethereum Lottery Example Part 2: Participant Aggregation and Winner Selection

Part 1 of this series: Ethereum Lottery Example Part 1: Setting Up Environment

Develop Functionality to Randomly Select a Winner

Before we jump into handling the monetary or scheduling part of things, let’s break this project down by first implementing and manually testing a contract that is able to aggregate a list of lottery entrants, and then randomly select a winner.

Random Number Selection

Solidarity does not provide a random number generator.

We cannot use recent transaction hashes to generate a random number, as there is risk of interference by miners. This blog article explains the concepts in-depth and proposes an alternative solution to using a third-party oracle.

Using an Oracle

We will use an outside Oracle, Provable, for generating a random number. To put it simply, oracles allow blockchain apps to access external APIs.

We chose to use Provable for this example because it will support our application development for free both within the Javascript VM and on the Ropsten test network.

To ease development, Provable doesn’t charge a contract for its first request of data done using the default gas parameters. Successive requests will require the contract to pay the Provable fee and the ether necessary to pay for the callback transaction. Both are automatically taken from the contract balance. If the contract doesn’t have enough funds in his balance, the request will fail and Provable won’t return any data.

https://docs.provable.xyz/#ethereum-quick-start

This works for us, because we only need our contract to select the winner once, at the conclusion of the lottery.

Step 1: Active Provable Plugin for Remix IDE

We need to activate the Provable Plugin Module within Remix IDE. This will allow us to use the Provable API within our Javascript VM deployment environment.

Review Part 1 of this series if you’re not sure how to activate a plugin module in Remix IDE.

Step 2: Write and Test Solidarity Code

pragma solidity >= 0.5.0 < 0.6.0;
import "github.com/provable-things/ethereum-api/provableAPI_0.5.sol";

contract Lotto is usingProvable {
    address[] public entrants;
    address public winner;
    bytes32 provableQueryId;
    
    function enter() public {
        entrants.push(msg.sender);
    }
    
    function selectWinner() public {
        if(winnerHasNotBeenSet() && provableQueryHasNotRun()){
            provableQueryId = provable_query("WolframAlpha", constructProvableQuery());
        }
    }
    
    function winnerHasNotBeenSet() private view returns (bool){
        return winner == address(0);
    }
    
    function provableQueryHasNotRun() private view returns (bool){
        return provableQueryId == 0;
    }
    
    function constructProvableQuery() private view returns (string memory){
        return strConcat("random number between 0 and ", uint2str(entrants.length-1));
    }
    
    //provable callback for selectWinner function
    function __callback(bytes32 myid, string memory result) public {
        if(myid != provableQueryId) revert();
        winner = entrants[parseInt(result)];
    }
}

Here is a basic solidity contract that allows us to enter into the lottery, and then later, randomly select a winner.

To select a winner, we use our Provable Integration to generate a random number, corresponding to our winner.

Using WolframAlpha as a datasource to generate secure random numbers is not good practice in production. If a programmer was to convert this proof of concept to production ready code, they would want to substitute a different Provable data source and validate the result.

There are missing features here. Features such as automating the conclusion of the lottery, and actually collecting from and distributing money to participants have not been implemented. We will add more functionality in future articles.

Step 3: Test Locally

If you have trouble deploying locally, please review Part 1: Step 3 of this series. If you’re not sure how to interact with the contract, move on to Step 4 of this article and watch the video.

Remember: We activated the Provable Plugin Module for Remix IDE in Step 1. Clicking the provable tab will allow you to see debug information for your program’s interaction with the provable API.

Figure 1: Using Provable interface in Remix IDE

Step 4: Test on Testnet

  1. Install Metamask for your browser (create a new wallet)
  2. In the upper right, click the “My Accounts” circle and select “Create Account” to create a second test account
  3. Switch to Ropsten Test Network
  4. Using the Metamask browser plugin, switch between your two accounts on the ether faucet and so that both test accounts have a positive Ether balance on the Ropsten Test Network
  5. Return to the Remix IDE instance, and refresh the page to cause it to reload
  6. Navigate to the Deploy & Run Transactions tab in the Remix IDE instance
  7. Select Injected Web3 as your environment.
  8. When prompted, connect both your test accounts in the Metamask dialog
  9. Select your contract (Lotto.sol)
  10. Click the “Deploy” button
  11. You can now interact with the contract!

Notes on interacting with the contract in testnet:

  1. Change your account in Metamask, Remix will only display the active account
  2. the selectWinner function may take a 10-15 seconds for the callback to be called, just be patient
https://youtu.be/n8KSSXhLZmo
Video 1: Interacting with contract on Ethereum Ropsten Test Network (in real-time, it’s a slow video). In the video, I show how the entrants array is populated and a winner is selected.

Follow-Up

Now that we have basic functionality working, there’s a lot of enhancements we need to add before we have a working lottery. I will be taking these on in future articles. This is not an all-inclusive list.

  • Add functionality to collect and distribute Ether payments
  • Use a scheduling service to automatically invoke the winner selection functionality
  • Only allow certain addresses to invoke the winner selection functionality
  • Prevent entering the lottery after a winner is selected
  • Add testing

Programmer’s Note

I am writing this article series as a way to better learn Blockchain programming, and it should not be considered as a source of “best practice”. I’m still learning.

If you spot any issues or have any suggestions, please leave a comment or send me an email.

Resources

Legal Disclaimer

This tutorial was intended to be used as an educational demonstration of Ethereum and related technologies. We picked a lottery application for our example because we feel it is a game most of our readers will be familiar with while being simple to implement.

Do not run this code in any production settings without contacting a lawyer first for advice. Lotteries are typically regulated in all jurisdictions.

There are no implied guarantees about the quality of this example. Code samples in this article are provided on an as-is basis and could (likely) contain bugs.

Update 9/6/21

  1. Updated code sample to reflect new location of provable import and specify version
  2. Ropsten test network is too slow to use apparently. I was able to run this application successfully on the Korvan testnet

Ethereum Lottery Example Part 1: Setting Up Environment

Recommended Prerequisites

To get the most out of this example series, it’s best to have a general understanding of:

  1. Blockchain Basics
  2. The Ethereum Virtual Machine

Step 1: Open Remix

https://remix.ethereum.org/

Remix is a web based IDE allow us to develop and execute the Ethereum contract we are developing using the Solidity language.

I recommend using Google Chrome as your browser when working in Remix. Brave seemed to crash often.

Step 2: Activate Remix Plugin Modules

We’re going to use a plugin module called Deploy & Run Transactions.

Go to the plugin manager in the Remix IDE to install this plugin.

Figure 1: Plugin manager after installing Deploy & Run Transactions plugin module

Before we can deploy our contracts, we need to be able to compile them. We will install the Solidarity Compiler module for that.

Figure 2: Plugin manager after installing both the Deploy & Run Transactions plugin module and the Solidarity Compiler module.

Step 3: Deploy Default Contract to Memory

To validate our environment is set up correctly, let’s deploy the default sample storage contract to an in-memory blockchain before we proceed with development of our lottery contract.

https://youtu.be/GwvdxVYmG8o
Figure 3: Demonstration of running default contract in Remix IDE

Follow-up

The next article in this series will demonstrate a simple smart contract that both aggregates participants and uses third party oracle to securely generate a random number to determine a winner.

Ethereum Lottery Example Part 2: Participant Aggregation and Winner Selection

Integration Testing: MockServer

The Need to Mock External Resources

When designing comprehensive integration tests to run in a test environment, one common blocker is the need to mock the behavior of an external API that is outside the control of your team.

Sometimes external vendors do not maintain a working test environment for us to test our code against.

MockServer

MockServer is an open source tool that will allow us to easily mock external services.

Example: Using MockServer to Mock Time API with Kotlin App in Docker Container

Demonstration Client

For our example application, we wrote a simple web-server (Hello.kt), using Javalin, that is able to query a public time server API (http://worldtimeapi.org/api/ip) and parse the Unix time from the response object.

package hello
import io.javalin.Javalin
import khttp.get
import java.io.FileInputStream
import java.util.*

fun main(args : Array<String>) {
    val props = Properties()
    props.load(FileInputStream("mockserverdemo.properties"))

    startWebServer(props)
}

private fun startWebServer(props: Properties) {
    val app = Javalin.create().start(8080)
    app.get("/test") { ctx -> ctx.result(getCurrentUnixTime(props.getProperty("test.timeApiUrl")).toString()) }
    app.get("/prod") { ctx -> ctx.result(getCurrentUnixTime(props.getProperty("prod.timeApiUrl")).toString()) }
}

private fun getCurrentUnixTime(queryUri: String): Long {
    return get(queryUri).jsonObject.getLong("unixtime")
}

We need a Dockerfile to specify how to run the sample application in a docker container (source):

FROM java:8
WORKDIR /
ADD target/mockserver-demo-1.0-SNAPSHOT-jar-with-dependencies.jar mockserver-demo-1.0-SNAPSHOT-jar-with-dependencies.jar
ADD mockserverdemo.properties mockserverdemo.properties
EXPOSE 8080
CMD java -jar mockserver-demo-1.0-SNAPSHOT-jar-with-dependencies.jar

Mocking Time API for Test Environment

Imagine that our time API is not available in our test environment. If that was the case, we can set up a simple MockServer instance to replace the time API in our test environment.

First, let’s create a file mockserverconfig.json that will store our mock server behavior. (docs)

[
  {
    "httpRequest": {
      "path": "/api/ip"
    },
    "httpResponse": {
      "body": "{\"abbreviation\":\"CDT\",\"client_ip\":\"99.156.81.3\",\"datetime\":\"2020-08-06T07:28:26.631433-05:00\",\"day_of_week\":4,\"day_of_year\":219,\"dst\":true,\"dst_from\":\"2020-03-08T08:00:00+00:00\",\"dst_offset\":3600,\"dst_until\":\"2020-11-01T07:00:00+00:00\",\"raw_offset\":-21600,\"timezone\":\"America/Chicago\",\"unixtime\":1596716906,\"utc_datetime\":\"2020-08-06T12:28:26.631433+00:00\",\"utc_offset\":\"-05:00\",\"week_number\":32}"
    }
  }
]

Note that the hard-coded JSON response in this file is the same schema as is returned by http://worldtimeapi.org/api/ip.

Configure Docker Compose

Next, we need to create a file docker-compose.yml to use with Docker Compose. This configuration specifies how to run our two containers, the demo client application container (called app) and our mockServer container (mockserver docs).

version: "3.8"
services:
  app:
    build: .
    ports:
      - 8080:8080
  mockServer:
    image: mockserver/mockserver:mockserver-5.11.1
    ports:
      - 1080:1080
    environment:
      MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties
      MOCKSERVER_INITIALIZATION_JSON_PATH: /config/mockserverconfig.json
    volumes:
      - type: bind
        source: .
        target: /config

Docker will download a pre-built MockServer container and use our properties file mockserverconfig.json.

A logical future enhancement to this demonstration would be to conditionally load the MockServer container (only load it in the test environment, not in production).

Add Properties File

You may have noticed we have two properties specified in our client (Hello.kt), test.timeApiUrl and prod.timeApiUrl:

  • prod.timeApiUrl is our real, functioning time api endpoint on the internet
  • test.timeApiUrl is our static mock endpoint provided by the MockServer container

We can place them in a single file, mockserverdemo.properties, to simplify this demonstration:

prod.timeApiUrl=http://worldtimeapi.org/api/ip
test.timeApiUrl=http://host.docker.internal:1080/api/ip

Running and Testing

Finally, let’s build and run our application using the following command:

mvn clean package && docker-compose build && docker-compose up

Now, using the endpoints we configured, we can test our different time providers (the real one and the mock one).

If we call:

GET http://localhost:8080/prod

We will get a dynamically increasing value. This is because we are hitting the real time-api endpoint, and time is elapsing.

If we call:

GET http://localhost:8080/test

We get a constant value, as we are hitting our MockServer instance which is configured to return a JSON constant.

Full Source Code for Demo

Useful Links and Resources Used

Extract Method and Inline Variable in IntelliJ

Introduction

This blog post will show, using videos, how to perform basic refactorings in IntelliJ. I’m using the IntelliJ 2020 community edition and OBS Studio to create these videos.

The philosophy contained in these articles will largely be drawing from my favorite book on refactoring: Refactoring: Improving the Design of Existing Code by Martin Fowler.

Prerequisite: Test Coverage

There should exist (or you should write) comprehensive test coverage on all code within the scope of our refactor. This is to help ensure that nothing, functionally, is changed during refactoring.

If you need help getting test coverage on your legacy code, check out my other blog articles such as Suppressing Static Initializers with Mockito + Powermock.

Identifying the Need for Extract Method

A common pattern for junior developers is to place a lot of code in a single method, but logically break it up with comments and log statements. This tells us that there is one method responsible for multiple operations (not ideal) and alerts us that the class should probably be refactored.

Here we have a simplified example, Demo.java

import java.util.Map;

public class Demo {
    public String getFirstName(String domain, String username){

        // step 1, generate connection string
        System.out.println("Generating connection string");
        String connectionString = Constants.BASE_DB_URL + domain;

        // step 2, get mapping of usernames to first names
        DbQueryDummy dbQueryDummy = new DbQueryDummy(connectionString);
        Map<String, String> dbUsernameToFirstName = dbQueryDummy.getDbUsernameToFirstNameMap();

        return dbUsernameToFirstName.get(username);
    }
}

Refactored Version

With two extract method operations, and three inline variable operations, we can refactor this code to the following:

public class Demo {

    public String getFirstName(String domain, String username){
        return getFirstNameFromDB(username, getConnectionString(domain));
    }

    private String getFirstNameFromDB(String username, String connectionString) {
        return new DbQueryDummy(connectionString).getDbUsernameToFirstNameMap().get(username);
    }

    private String getConnectionString(String domain) {
        System.out.println("Generating connection string");
        return Constants.BASE_DB_URL + domain;
    }
}

In my opinion, this refactored code is better because we’ve:

  • Eliminated the temporary variables
  • Split the getFirstName method into two logically discrete parts
  • Alleviated the need for explaining comments; We have nicely named methods instead

If this was real world code, I’d love to refactor DbQueryDummy to remove the chained method call in our getFirstNameFromDB method, but that’s not important for the purposes of this article.

Behind the Magic: IntelliJ Refactoring Tools

To perform similar refactorings: I highly recommend becoming familiar with IntelliJ’s code refactoring tools.

Refactoring in your IDE does help eliminate silly mistakes, but it isn’t perfect. Using an IDE doesn’t negate the need for comprehensive testing around the code being refactored.

https://www.youtube.com/watch?v=QsPdRJnWlV0&feature=youtu.be

Suppressing Static Initializers with Mockito + Powermock

Often as developers, we need to add unit testing to legacy code while being unable to make non-trivial changes to the code under test.

In legacy code, logging and other resources are often set up in a static initializer. That code won’t execute nicely in a unit testing environment (could be using JNDI or something else that doesn’t work nicely with unit tests without mocking).

We can’t just add mock behavior to our class instance in the unit test, because the static initializer is run as soon as the class is accessed.

If we’re using Mockito with Powermock the solution to this problem is simple: we need to suppress the static initializer from executing.

Let’s take a look at a simple example of suppressing a static initializer. We have a class ClassWithStaticInit, where we’re assigning a string field in our static initializer to “abc”.

public class ClassWithStaticInit {
    final static String field;
    
    static {
        //this block could contain undesirable logging setup that doesn't work in a test environment
        field = "abc";
    }

    public String getField(){
        return field;
    }
}

In the following test, we suppress the static initializer for ClassWithStaticInit using the @SuppressStaticInitializationFor annotation. You can see the return value of the method unit.getField() is null, because we have suppressed the static initializer of ClassWithStaticInit, preventing the field from being set.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor({"ClassWithStaticInit"})
@PrepareForTest({ClassWithStaticInit.class})
public class SuppressStaticInitTest {

    @InjectMocks
    ClassWithStaticInit unit = new ClassWithStaticInit();

    @Test
    public void testSuppressingStaticInitializer(){
        assertEquals(unit.getField(), null);
    }
}

Now, if we don’t suppress the static initializer, we can see that the static block has executed, assigning the value “abc” to field.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassWithStaticInit.class})
public class DoNotSuppressStaticInitTest {

    @InjectMocks
    ClassWithStaticInit unit = new ClassWithStaticInit();

    @Test
    public void testSuppressingStaticInitializer(){
        assertEquals(unit.getField(), "abc");
    }
}

pom.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.french</groupId>
    <artifactId>MockStaticInitilizerMockito</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.surefire.version>2.22.2</maven.surefire.version>
        <junit.version>4.13</junit.version>
        <mockito.version>2.28.2</mockito.version>
        <powermock.version>2.0.7</powermock.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.version}</version>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-core</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Note: From my readings, it appears Powermock only supports JUnit 4, not JUnit 5 as a reasonable person would expect.

Full Code on GitHub

Testing vs. Alerting Part I

If you want to evaluate a testing plan, you must also consider your alerting plan.

Alerting and testing are complimentary, both serve to identify defects. Testing typically serves to identify defects before code is deployed to production, while alerting typically notifies developers of an issue with a running system.

You need to consider both testing and alerting to create an effective defect mitigation plan.

Start by asking yourself a few important questions:

  1. What is the businesses’ tolerance for defects in production of this system?
  2. Can we easily rectify production issues with this system postmortem (after being alerted), or will it cause non-trivial damage to business operations and reputation?
  3. Are developers capable and willing to do on-call fixes to production systems? How much ongoing cost is there in training?

Once you’ve identified your tolerance for defects in production (and ability to fix them), you can better evaluate what preventative measures should live as real-time alerting, and what measures should live as pre-deployment tests.

Often, I find that using alerting to catch errors is a magnitude less of a time investment compared to developing comprehensive integration testing to catch the same defects. The downside is that resolving the alerts still requires developer time and manual effort.

In my opinion, alerting on production systems is more fundamental than automated testing, but both should play some role in designing a defect mitigation plan.

The point is: You can’t design a good testing plan without having an alerting plan.