Testing module¶
To write unit tests for Rell code, use test modules. A test module is defined using the @test annotation:
@test module;
function test_foo() {
assert_equals(2 + 2, 4);
}
function test_bar() {
assert_equals(2 + 2, 5);
}
All functions in a test module that start with test_
(and a function called exactly test
) are test functions and
will be executed when the test module is run.
To run a test module, use the command-line interpreter:
rell.sh -d my_src_directory --test my_test_module
or right-klick on the run.xml file and run as Rell Unit Test
Each test function will be executed independently of others, and a summary will be printed in the end:
TEST RESULTS:
my_test_module:test_foo OK
my_test_module:test_bar FAILED
SUMMARY: 1 FAILED / 1 PASSED / 2 TOTAL
***** FAILED *****
Transactions in test module¶
Instead of writing tests in a frontend, we write the transactions in rell like this:
rell.test.block
- a block, contains a list of transactions
rell.test.tx
- a transaction, has a list of operations and a list of signers
rell.test.op
- an operation call, which is a (mount) name and a list of arguments (each argument is a gtv)
Keys used for signing transactions can also be created like this:
rell.test.keypairs.{bob, alice, trudy}: rell.test.keypair
- test keypairsrell.test.privkeys.{bob, alice, trudy}: byte_array
- same as rell.test.keypairs.X.privrell.test.pubkeys.{bob, alice, trudy}: byte_array
- same as rell.test.keypairs.X.pub
Example of a operation signed with the alice keypair:
rell.test.tx().op(main.exampleOp(parameter1,parameter2)).sign(rell.test.keypairs.alice).run();
And if if nop is necessary for making a transaction unique, one can use ´ŕell.test.nop() to implement it.
rell.test.nop(x: integer): rell.test.op
rell.test.nop(x: text): rell.test.op
rell.test.nop(x: byte_array): rell.test.op
Creates a “nop” operation with a specific argument value.
Building and running a block¶
operation foo(x: integer) { ... }
operation bar(s: text) { ... }
...
val tx1 = rell.test.tx()
.op(foo(123)) // operation call returns rell.test.op
.op(bar('ABC')) // now the transaction has two operations
.sign(rell.test.keypairs.bob) // signing with the "Bob" test keypair
;
val tx2 = rell.test.tx()
.op(bar('XYZ'))
.sign(rell.test.keypairs.bob)
.sign(rell.test.keypairs.alice) // tx2 is signed with both "Bob" and "Alice" keypairs
;
rell.test.block()
.tx(tx1)
.tx(tx2)
.run() // execute the block consisting of two transactions: tx1 and tx2
;
If we our module has the “_test” suffix it will become a test module
for the module that bears the same name without the suffix. For example,
if our modules name is program
, the test module would be called
program_test
.
Production and test modules¶
Production module (file data.rell):¶
module;
entity user {
name;
}
operation add_user(name) {
create user(name);
}
Test module (file data_test.rell):¶
@test module;
import data;
function test_add_user() {
assert_equals(data.user@*{}(.name), list<text>());
val tx = rell.test.tx(data.add_user('Bob'));
assert_equals(data.user@*{}(.name), list<text>());
tx.run();
assert_equals(data.user@*{}(.name), ['Bob']);
}
Functions of rell.test.block¶
rell.test.block()
- create an empty block builderrell.test.block(tx: rell.test.tx, ...)
- create a block builder with some transaction(s)rell.test.block(txs: list<rell.test.tx>)
- samerell.test.block(op: rell.test.op, ...)
- create a block builder with one transaction with some operation(s)rell.test.block(ops: list<rell.test.op>)
- same.tx(tx: rell.test.tx, ...)
- add some transaction(s) to the block.tx(txs: list<rell.test.tx>)
- same.tx(op: rell.test.op, ...)
- add one transaction with some operation(s) to the block.tx(ops: list<rell.test.op>)
- same.copy(): rell.test.block
- returns a copy of this block builder object.run()
- run the block.run_must_fail()
- same as .run(), but throws exception on success, not on failureFunctions of rell.test.tx:¶
rell.test.tx()
- create an empty transaction builderrell.test.tx(op: rell.test.op, ...)
- create a transaction builder with some operation(s)rell.test.tx(ops: list<rell.test.op>)
- same.op(op: rell.test.op, ...)
- add some operation(s) to this transaction builder.op(ops: list<rell.test.op>)
- same.nop()
- same as .op(rell.test.nop()).nop(x: integer)
- same as .op(rell.test.nop(x)).nop(x: text)
- same.nop(x: byte_array)
- same.sign(keypair: rell.test.keypair, ...)
- add some signer keypair(s).sign(keypairs: list<rell.test.keypair>)
- same.sign(privkey: byte_array, ...)
- add some signer private key(s) (a private key must be 32 bytes).sign(privkeys: list<byte_arrays>)
- same.copy(): rell.test.tx
- returns a copy of this transaction builder object.run()
- runs a block containing this single transaction.run_must_fail()
- same as .run(), but throws exception on success, not on failureFunctions of rell.test.op:¶
rell.test.op(name: text, arg: gtv, ...)
- creates an operation call object with a given name and argumentsrell.test.op(name: text, args: list<gtv>)
- same.tx(): rell.test.tx
- creates a transaction builder object containing this operation.sign(...): rell.test.tx
- equivalent of .tx().sign(…).run()
- equivalent of .tx().run().run_must_fail()
- eqiovalent of .tx().run_must_fail()Functions assert_* for unit tests
Other functions:¶
assert_equals(actual: T, expected: T)
- fail (throw an exception) if two values are not equalassert_not_equals(actual: T, expected: T)
- fail if the values are equalassert_true(actual: boolean)
- assert that the value is “true”assert_false(actual: boolean)
- assert that the value is “false”assert_null(actual: T?)
- assert that the value is nullassert_not_null(actual: T?)
- assert that the value is not nullassert_lt(actual: T, expected: T)
- assert less than (actual < expected)assert_gt(actual: T, expected: T)
- assert greater than (actual > expected)assert_le(actual: T, expected: T)
- assert less or equal (actual <= expected)assert_ge(actual: T, expected: T)
- assert greater or equal (actual >= expected)assert_gt_lt(actual: T, min: T, max: T)
- assert (actual > min) and (actual < max)assert_gt_le(actual: T, min: T, max: T)
- assert (actual > min) and (actual <= max)assert_ge_lt(actual: T, min: T, max: T)
- assert (actual >= min) and (actual < max)assert_ge_le(actual: T, min: T, max: T)
- assert (actual >= min) and (actual <= max)Same functions are also available in the rell.test namespace.
Running unit tests via run.xml¶
With the help of run.xml we can run tests with module arguments included(constants we define in the run xml file).
To define a test in xml, we will use the test
tag like this:
<run>
<nodes>
<config src="node-config.properties" />
<test-config src="node-config-test.properties" />
</nodes>
<chains>
<chain name="foo" iid="1">
<config height="0">
<app module="foo.app" />
</config>
<test module="foo.tests" />
</chain>
<test module="lib.tests" />
</chains>
</run>
We will run the test like this in the terminal: ./multirun.sh -d rell/src --test rell/config/run.xml
Example¶
Here is an example of a test module implemented for the Chroma-Chat example that can be found in the Example Projects section.
@test module;
import main;
function test_init(){
assert_equals((main.user@*{}(.username)).size(),0);
assert_equals((main.balance@*{}(.user)).size(),0);
rell.test.tx().op(main.init(rell.test.pubkeys.alice)).run();
assert_equals(main.user@*{}(.username).size(),1);
assert_equals(main.balance@{.user == main.user@{ .pubkey == rell.test.pubkeys.alice}}(.amount),1000000);
}
function test_register_user(){
rell.test.tx().op(main.init(rell.test.pubkeys.alice)).run();
assert_equals(main.user@*{}(.username).size(),1);
rell.test.tx().op(main.register_user(rell.test.pubkeys.alice,rell.test.pubkeys.bob,"bob",100)).sign(rell.test.keypairs.alice).run();
assert_equals(main.user@*{}(.username).size(),2);
assert_equals(main.user@{.pubkey == rell.test.pubkeys.bob}(.username),"bob");
assert_equals(main.balance@{.user == main.user@{.pubkey == rell.test.pubkeys.bob}}(.amount),100);
}
function test_blocks(){
val tx1 = rell.test.tx().op(main.init(rell.test.pubkeys.alice));
val tx2 = rell.test.tx().op(main.register_user(rell.test.pubkeys.alice,rell.test.pubkeys.bob,"bob",100)).sign(rell.test.keypairs.alice);
val tx3 = rell.test.tx().op(main.create_channel(rell.test.pubkeys.alice,"channel 1")).sign(rell.test.keypairs.alice);
rell.test.block().tx(tx1).tx(tx2).tx(tx3).run();
val tx4 = rell.test.tx().op(main.add_channel_member(rell.test.pubkeys.alice,"channel 1","bob")).sign(rell.test.keypairs.alice);
rell.test.block().tx(tx4).run();
}