March 17, 2021

By Mike Berry

Tari is going green

BDD Testing with Cucumber

Tari is going green!

No, we’re not switching to Proof of Stake. (LOL - as if that would make a difference).

But we are using Cucumber for Behavior Driven Development (BDD).

You may have noticed a lot of PR’s and commits with the word “cucumber” in them. Tari uses Cucumber, and specifically Cucumber.js to easily specify test scenarios in plain text. The tests can be found under the integration_tests/features folder. The tests spin up base nodes, merge mining proxies and miners in a local, low difficulty network and interact with them via GRPC, simulating various scenarios that might happen on testnet.

The plain text method of specifying tests makes it easy to read, write and verify correctness of the tests.

Here’s a simple example that tests submitting blocks:


Scenario: Mining example
  Given I have 3 seed nodes
  And I have a base node NODE_A connected to all seed nodes
  When I mine 20 blocks on NODE_A
  Then all nodes are at height 20

In Cucumber tests, each line is a step, and will start with one of Given, When or Then. And can also be used to make the test easier to read, in which case it uses the keyword from the previous step. The Given keyword is used to set up the scenario, When is used to describe an action being performed, and Then is used to test whether what we expected to happen, happened. (Learn more about Gherkin syntax)

In the above example we first set up 3 seed nodes. Every scenario runs in its own isolated local network, so in this case, we start by creating 3 nodes. When the test runs, it will run this Javascript definition found in integration_tests/features/support/steps.js:

Given('I have {int} seed nodes', async function (n) {
   ...
});

Note: You don’t need to know Javascript to write tests as long as you’re using existing steps definitions, and there are a lot of those already defined.

  ...
  And I have a base node NODE_A connected to all seed nodes
  When I mine 20 blocks on NODE_A
  ...

In the next two steps we create a base node and name it NODE_A and then mine 20 blocks. The difficulty for this network is capped with a low maximum that allows us to create blocks very quickly. When the test runs, 20 blocks will be created and submitted to NODE_A.

  ...
  Then all nodes are at height 20

The last step we check that network behaved as we expected it to. We’re expecting that the blocks were propagated to all four nodes on the network. If any of the nodes is not at height 20, the test will fail. For those interested, here’s the simplified Javascript step definition that runs:

Then('all nodes are at height {int}', async function (height) {
    await this.forEachClientAsync(async (client, name) => {
        const currTip = await client.getTipHeight();
        expect(currTip).to.equal(height);
    })
});

Instructions on running the tests are available in the README in the integration_tests folder.

The tests verify many scenarios, including transaction propagation, re-orgs, block propagation and syncing, and even sending thousands of transactions.

There’s not much more to it, and this is a really simple entry point to contribute. If you get stuck, pop into the #tari-dev IRC on freenode and ask a question.