Zardoz.

I am ZARDOZ

ZARDOZ

A Golang testing framework for testing asyncronous dependencies.

Table of Contents

Installation

Pull the module into your GOPATH using

1go get github.com/apiercey/zardoz

Usage

ZARDOZ can be imported like so:

1import z "github.com/APiercey/zardoz"

ZARDOZ comes with three assertions for testing asyncronous dependencies. Test blocks begin with a Describe block, which provides a test suite.

1func main() {
2  z.Describe("Example External System", func(s *z.Suite) {
3      s.Test("can turn lights on", test_light_turn_on)
4  })
5}

Test

A test can be written using anonymous functions or by passing in the function name to the describe block. The function will receive a Test struct, which provides assertions.

Examples:

1func test_lego_car_turns_left(t *z.Test) {
2    execute_turn_left_command()
3
4    t.AssertSync(func() bool {
5        return evaluate_lego_car_turned_left()
6    }, 500)
7}
8
9s.Test("can turn left", test_lego_car_turns_left)

or simply

1s.Test("can turn left", func (t *z.Test) {
2    execute_turn_left_command()
3
4    t.AssertSync(func() bool {
5        return evaluate_lego_car_turned_left()
6    }, 500)
7})

Assertions

ZARDOZ provides three assertions:

t.Assert

Simple assertions like in other unit testing frameworks.

Examples

1t.Assert(true) // passes
2t.Assert(false) // fails
3t.Assert(some_func_that_returns_boolean()) // depends on the returned value

t.AssertSync

Asserts something will eventually true. It expects two values: a predict function that compares for a condition and a timeout of how long it should attempt to assert this condition. Timeout values are in milliseconds.

If the predict should return false it will try again.

Multiple t.AssertSync assertions result will assert their conditions syncronously.

Examples

1t.AssertSync(func() {
2  return true
3}, 1_000) 
4
5t.AssertSync(func() {
6  return maybe_returns_true()
7}, 1_000) 
8
9t.AssertSync(maybe_returns_true, 1_000) 

In the example above, the test will take a maximum time of 3000ms to complete if all assertions are false.

t.AssertAsync

Asserts something will eventually be true. It expects two values: a predict function that compares for a condition and a timeout of how long it should attempt to assert this condition. Timeout values are in milliseconds.

If the predict should return false it will try again.

Multiple t.AssertSync assertions result will assert their conditions asyncronously and can be used to observe conditions which take place within the same time frame.

Examples

1t.AssertAsync(func() {
2  return true
3}, 1_000) 
4
5t.AssertAsync(func() {
6  return maybe_returns_true()
7}, 1_000) 
8
9t.AssertAsync(maybe_returns_true, 1_000) 

In the example above, the test will take a maximum time of roughly 1000ms to complete if all assertions are false.

Working Example

Below is an example of testing IoT device using the MQTT protocol. The suite will send messaeges to a device and expects an asynchronous response.

 1package main
 2
 3import "fmt"
 4import z "github.com/APiercey/zardoz"
 5import mqtt "github.com/eclipse/paho.mqtt.golang"
 6
 7// Setup code for completness
 8var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
 9    fmt.Printf("Unexpected connection lost: %v", err)
10}
11
12// Setup code for completness
13func setupMqttClient(clientID string) mqtt.Client {
14    var broker = "localhost"
15    var port = 1883
16
17    opts := mqtt.NewClientOptions()
18    opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, port))
19    opts.SetClientID("test_runner")
20    opts.SetDefaultPublishHandler(func(client mqtt.Client, msg mqtt.Message) {
21    })
22    opts.OnConnectionLost = connectLostHandler
23    client := mqtt.NewClient(opts)
24
25    if token := client.Connect(); token.Wait() && token.Error() != nil {
26        panic(token.Error())
27    }
28
29    return client
30}
31
32func test_falls_asleep_on_message(t *z.Test) {
33    expect_to_fall_asleep := false
34
35    client.Subscribe("iot-device/output", 1, func(c mqtt.Client, m mqtt.Message) {
36        if string(m.Payload()) == "ASLEEP" {
37          expect_to_fall_asleep = true
38        }
39    }).Wait()
40
41    client.Publish("iot-device/input", 0, false, "SLEEP")
42
43    t.AssertSync(func() bool { return expect_to_fall_asleep }, 500)
44}
45
46func test_wakes_up_on_messages(t *z.Test) {
47    expect_to_be_awake := false
48    expect_to_download_config := false
49
50    client.Subscribe("iot-device/output", 1, func(c mqtt.Client, m mqtt.Message) {
51        if string(m.Payload()) == "WOKEN_UP" {
52          expect_to_be_awake = true
53        }
54        
55        if string(m.Payload()) == "CONFIG_DOWNLOADED" {
56          expect_to_download_config = true
57        }
58    }).Wait()
59
60    client.Publish("iot-device/input", 0, false, "WAKEUP")
61
62    // Both assertions should be true within 500ms
63    t.AssertAsync(func() bool { return expect_to_be_awake }, 500)
64    t.AssertAsync(func() bool { return expect_to_download_config }, 500)
65}
66
67func test_receives_ping(t *z.Test) {
68    expected_response_received := false
69
70    client.Subscribe("iot-device/output", 1, func(c mqtt.Client, m mqtt.Message) {
71        expected_response_received = string(m.Payload()) == "PING"
72    }).Wait()
73
74    t.AssertSync(func() bool {
75        return expected_response_received
76    }, 1_000)
77}
78
79func main() {
80    z.Describe("IoT Device Commands", func(s *z.Suite) {
81        s.Test("wakes up on message", test_wakes_up_on_messages)
82        s.Test("falls asleep on message", test_falls_asleep_on_message)
83    })
84
85    z.Describe("Diagnostics", func(s *z.Suite) {
86        s.Test("receives PING", test_receives_ping)
87    })
88}

Reading results

After every test ZARDOZ will provide a summary of the number of tests ran, passes, and failes. When tests fail, ZARDOZ will provide a preview of the offending assertion and as well as the path and line number under the summary.

Example below:

 1$ go run *.go
 2
 3Running...
 4FF
 5
 6Failures:
 7
 8  1) Example External System can turn on
 9
10    Never returned true after 500ms when evaluating:
11    t.AssertSync(func() bool {
12        return expected_response_received
13    }, 500)
14}
15  2) Example External System can turn off
16
17    Never returned true after 500ms when evaluating:
18    t.AssertSync(func() bool {
19        return expected_response_received
20    }, 500)
21}
22
23Assertion failed /Users/example-user/.go/src/example-tests/v1/main.go:18
24Assertion failed /Users/example-user/.go/src/example-tests/v1/main.go:32
25
262 tests ran with 0 passes and 2 failures.
27
28Running...
29F
30
31Failures:
32
33  1) Information about external system
34
35    Never returned true after 15000ms when evaluating:
36    t.AssertSync(func() bool {
37        return expected_response_received
38    }, 15_000)
39}
40
41Assertion failed /Users/example-user/.go/src/example-tests/v1/main.go:44
42
431 tests ran with 0 passes and 1 failures.

Setup and Cleanup

ZARDOZ allows you to run setup code or cleanup code, if you so wish. These are executed once per test.

Examples

 1func main() {
 2  z.Describe("Example External System", func(s *z.Suite) {
 3      s.Setup(func () {
 4          setup_my_dependency()
 5      })
 6      
 7      // or
 8      
 9      s.Setup(setup_my_dependency)
10      
11      s.Test("can handle commands", test_some_command)
12      
13      s.Cleanup(func () {
14          cleanup_my_dependency()
15      })
16      
17      // or
18      
19      s.Cleanup(cleanup_my_dependency)
20      
21  })
22}