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
asserts a value is true immediatly.t.AssertSync
assert a condition will become true.t.AssertAsync
asserts a condition will become true and can be parallelized with other Assert calls.
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.
s.Setup
executed before the test.s.Cleanup
executed after the 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}