[b]ash [u]niversal [t]esting
I wanted to be able to test a number of different projects with a testing tool that provides simple installation, i.e. not a lot of dependencies or setup involved, portability, configurability, and the ability to test anything that exposes a shell interface.
Due to its use of gsed
and gstat
on OS X (installed via homebrew), on any system that uses GNU sed or GNU stat
these programs should be aliased. sed
should be aliased as gsed
and stat
should be aliased as gstat
).
but
is written in Bash 5, but works in Bash 4 as well.
- Bash
>4
gnu sed
gnu stat
jq
brew install bash gnu-sed gnu-stat jq
- node
>=6
npm
yarn
Add (or update) an .npmrc
:
@urzo:registry=https://npm.pkg.github.com
run this command to update your user npm configuration:
echo "@urzo:registry=https://npm.pkg.github.com" >> ~/.npmrc
This allows the use of the GitHub package registry with npm
and yarn
.
Using yarn
symlinks but
to /usr/local/bin/but which should more than likely be available in your PATH
.
yarn global add @urzo/but
The atomic unit of but
is the test. A test in but
is a bash function that is prefixed with test
.
The test name or description is derived from the function name so function names should be readable,
descriptive, and delimited by an underscore (_
).
The tests status, i.e. "passed" or "failed", is determined by examining the return code of the function, i.e.
a return code of 0 means the test has passed, and a non-zero return code means the test has failed.
NOTE: test status can be negated with the test prefix ntest
instead of the standard test
.
test_should_pass() {
return 0
}
or
ntest_should_fail() {
return 1
}
The molecular unit of but
is the suite. A suite is a bash script that is:
- a non-executable
- contains no shebang
- has a file extension of
.but
- contains 1 or more tests
In other words a but
suite is just a file that contains functions that adhere to the conventions prescribed by but
.
self.but
test_should_pass() {
return 0
}
ntest_should_fail() {
return 1
}
Suites in but
are just a collection of bash scripts that contain bash functions. It must be a directory. The suites
directory may also contain the .butrc
configuration.
$ tests/
.butrc
a.but
b.but
Configuring but
requires a JSON configuration document with the name .butrc
.
A .butrc
must have the name
, and root
keys. If the but
command is run and a BUT__CONFIG_SRC
environment variable
is present but
will use the .butrc
that the BUT__CONFIG_SRC
environment variable points to,
otherwise if the current working directory contains a .butrc
it will use that .butrc
.
In addition but
provides the option to configure temporary files via the tmp
key, and environment variables for all suites
via the env
key.
**NOTE: if a .env
file is present in the directory but
is run from it will source that .env
file.
.butrc
{
"name": "my_tests",
"tmp": {
"A_TMP_FILE": "/tmp/a_tmp_file.txt"
},
"env": {
"MY_ENV_VAR": "env_var_value"
},
"root": "/path/to/test/directory"
}
The name of the test suites. Injected into scripts as the
BUT__INSTANCE_NAME
environment variable.
An object with values that point to temporary files created during tests. The keys are injected as environment variables, and the values are automatically removed when tests are completed. Use this configuration option when using temporary files in your tests.
but
will automatically cleanup those temp files.
.butrc
{
"tmp": {
"A_TMP_FILE": "/tmp/a_tmp_file.txt"
}
}
test.but
test_should_have_tmp_file_with_specific_text() {
echo "specific text" > "${A_TMP_FILE}"
if [[ "$(< ${A_TMP_FILE})" == "specific text" ]]; then return 0; fi
}
An object whose key/value pairs are available as environment variables in all suites.
.butrc
{
"env": {
"MY_ENV_VAR": "env_var_value"
}
}
test.but
test_should_have_environment_variale() {
if [[ "${MY_ENV_VAR}" == "env_var_value" ]]; then return 0; fi
}
A path pointing to the directory of tests
Each test is defined by a bash function prefixed with test
or ntest
, and saved on disk as file
with the extension .but
. Tests in a suite MUST NOT depend on each other as there is no guaranteed run order.
- MUST NOT be executable
- MUST NOT have a shebang line
- MUST contain at least one test function, i.e. bash function prefixes with
test
orntest
- MUST be prefixed with
test
or withntest
- MUST be delimited with an underscore
_
- SHOULD begin with
should
, e.g.test_should_do_foo
- MUST execute at least 1 command
- SHOULD NOT use
|| true
, this defeats the purpose - MUST NOT depend on other test functions
but
supports running command hooks before and after each test suite, and before and after each test.
but
suites can define a function name __before__
. This function is called before all tests in the suite.
__before__() {
echo "I run before all tests in this suite"
}
but
suites can define a function name __after__
. This function is called after all in the suite.
__after__() {
echo "I run after all tests in this suite"
}
but
test files can define a function name __before_each__
. This function is called before each test.
__before_each__() {
echo "I run before each test in this suite"
}
but
test files can define a function name __after_each__
. This function is called after each test.
__after_each__() {
echo "I run after each test in this suite"
}
yes | but
When the but
command is called it:
- Checks for the existence of a lockfile
- Writes a lockfile if one does not exist
- Validates the configuration file
- Prepares the necessary test parameters from the configuration file
- Prepares the tests
- Execute the tests
- Evaluate test output
but
is interactive and prompts for a y
character to execute the tests. Use yes | but
in automation or scripting.
- TAP (default)
- simplistic TAP output, does not support comments, directives, or YAML debug output.
-t or --tap
flags if thetap
npm module is installedbut
will pipe it's output through thetap
program.
- JSON
- output JSON documents for each test
-j
or--json
flags
- debug
- disables tap and JSON output if used
- very verbose debug output
--verbose
flag
yes | but
TAP version 13
1..8
ok 1 Should fail
ok 2 Should have env vars
ok 3 Should have exact instance name
ok 4 Should have non empty instance name
ok 5 Should have non empty tmp file environment variables
ok 6 Should have run after hook
ok 7 Should have run before hook
ok 8 Should pass
Failed 0/8 tests, 100.00% okay
Copyright 2019
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.