End-to-End tests with Venom
What is Venom?
Venom is an Open-Source tool for writing automated end-to-end and integration tests. It is published by OVH, a French cloud company. It is built on top of a popular Go framework called Cobra (hence the name). It aims to be easy and simple to use. It doesn’t require you to write your testCases in Python/Java/JavaScript like many other e2e testing tools. You declare your test cases using a simple .yaml
file that resembles CI job declarations. It is written in go and can be easily extendable.
Writing testCases
Writing tests in Venom is simple. It uses the concept of executors
, which are the underlying code that gets executed for a given testCase. In the example below, the executor
is specified with the type
keyword.
name: Title of TestSuite
testcases:
- name: example of a GET http testcase
steps:
- type: http # This test is using the http executor.
method: GET
url: https://some-url.com/products
assertions:
- result.statuscode ShouldEqual 200
- result.timeseconds ShouldBeLessThan 1
- result.body ShouldContainSubstring Owner
Variables, arguments & helpers
Variables can be defined on the testsuite level. To use the same example as above:
name: Title of TestSuite
vars:
baseUrl: 'https://some-url.com'
testcases:
- name: example of a GET http testcase
steps:
- type: http # This test is using the http executor.
method: GET
url: '{{.baseUrl}}/products' # using the testsuit-level environment variable
assertions:
- result.statuscode ShouldEqual 200
- result.timeseconds ShouldBeLessThan 1
- result.body ShouldContainSubstring Owner
They can also be specified in the command line, using the --var
flag, like such:
venom run --var="baseUrl=https://some-url.com"
Or by defining them in a file, and using the flag --var-from-file
:
venom run --var-from-file="stagingEnv.yaml"
You can also use normal environment variables, as long as you prefix them with VENOM_VAR_
. As a fallback strategy, if venom can’t find a given environment variable, it will try to find one in the environment variables. For example:
VENOM_VAR_baseUrl = https://some-url.com
Venom also comes with several environment variable helpers, such as upper
, lower
, camelcase
, toJSON
, and so on. A full list can be found in the project’s documentation. To use it, you need to pipe the value to the helper. For example:
{{.myvar | upper}} # will transform the value of `myvar` to uppercase
{{.myvar | camelcase }} # here something like 'new value' becomes 'newValue'
Venom also contains a few builtin variables where you can get things like the name of the file ({{.venom.testsuite.filename}}
)
It also allows you to extract the output of one test into a variable, so you can use in the subsequent testcases.
Test executors
The http executor in the example above, as the name says, makes HTTP requests.
Venom comes with support for many types of executors out of the box (and I would expect the list to grow over time):
Executor | description |
---|---|
amqp | Publish/subscribe to AMQP 1.0 compatible broker (QPID, ActiveMQ, etc) |
dbfixtures | Load fixtures into MySQL and PostgreSQL databases. Uses testfixtures |
exec | This is the default executor, executes a script |
grpc | Executes a GRPC Request |
http | Executes a HTTP Request |
imap | Used to test if mail is received from your application |
kafka | Used to use read/write on a Kafka topic |
mqtt | Used to read and write MQTT topics |
rabbitmq | Used to publish/subscribe on a RabbitMQ |
readfile | Executor that can read a file. This can be useful when testing files generated by your software |
redis | Execute commands into Redis |
smtp | Used for sending SMTP emails |
sql | Execute SQL queries into databases (MySQL, PostgresQL, and Oracle) |
ssh | Execute a script on a remote server via SSH |
web | This can be used for browser testing, it navigates to a web page and you can assert certain behaviors |
Under the hood, executors implement an Executor interface
:
// Executor execute a testStep.
type Executor interface {
// Run run a Test Step
Run(ctx context.Content, TestStep) (interface{}, error)
}
A generic example of a custom executor looks like this:
// Name of executor
const Name = "myexecutor"
// New returns a new Executor
func New() venom.Executor {
return &Executor{}
}
// Executor struct
type Executor struct {
Command string `json:"command,omitempty" yaml:"command,omitempty"`
}
// Result represents a step result
type Result struct {
Code int `json:"code,omitempty" yaml:"code,omitempty"`
Command string `json:"command,omitempty" yaml:"command,omitempty"`
Systemout string `json:"systemout,omitempty" yaml:"systemout,omitempty"` // put in testcase.Systemout by venom if present
Systemerr string `json:"systemerr,omitempty" yaml:"systemerr,omitempty"` // put in testcase.Systemerr by venom if present
}
// GetDefaultAssertions return default assertions for this executor
// Optional
func (Executor) GetDefaultAssertions() *venom.StepAssertions {
return &venom.StepAssertions{Assertions: []venom.Assertion{"result.code ShouldEqual 0"}}
}
// Run execute TestStep
func (Executor) Run(ctx context.Context, step venom.TestStep) (interface{}, error) {
// transform step to Executor Instance
var e Executor
if err := mapstructure.Decode(step, &e); err != nil {
return nil, err
}
systemout := "foo"
ouputCode := 0
// prepare result
r := Result{
Code: ouputCode, // return Output Code
Command: e.Command, // return Command executed
Systemout: systemout, // return Output string
}
return r, nil
}
But the beauty of Venom is that you don’t need to know any of this unless you are writing your own executor. If you find yourself repeating the same testCase logic, you can also create user-defined executors:
executor: hello
input:
myarg: {}
steps:
- script: echo "{\"hello\":\"{{.input.myarg}}\"}"
assertions:
- result.code ShouldEqual 0
output:
display:
hello: "{{.result.systemoutjson.hello}}"
all: "{{.result.systemoutjson}}"
Now, by defining a re-usable hello
executor (that is using the default exec
executor behind the scenes), you can re-use it in other testCases:
name: testsuite with the `hello` executor
testcases:
- name: example test
steps:
- type: hello # this is the custom executor that we've declared above.
myarg: World
assertions:
- result.display.hello ShouldContainSubstring World
- result.alljson.hello ShouldContainSubstring World
Configuring with .venomrc
Finally, instead of providing arguments from the command line, you can create a .venomrc
file in your project and set the configuration there. Venom will read those values before running.
variables:
- foo=bar
variables_files:
- my_var_file.yaml
stop_on_failure: true
format: xml
output_dir: output
lib_dir: lib
verbosity: 3