BDD based testing framework for POSIX compatible shell scripts.
Table of Contents
Describe 'sample' # Example group block
Describe 'bc command'
add() { echo "$1 + $2" | bc; }
Example 'perform addition' # Example block
When call add 2 3 # Evaluation
The output should eq 5 # Expectation
End
End
Describe 'implemented by shell function'
. ./mylib.sh # add() function defined
Example 'perform addition'
When call add 2 3
The output should eq 5
End
End
End- Support POSIX compatible shell (dash, bash, ksh, busybox, etc...)
- BDD style syntax
- The specfile is a valid shell script language syntax
- Pure shell script implementation
- Minimum Dependencies (Use only a few POSIX compliant command)
- Nestable groups with scope like lexical scope
- Before / After hooks
- Skip / Pending
- Mocking and stubbing (temporary function override)
- Built-in simple task runner
- Modern reporting (colorize, failure line number)
- Extensible architecture (custom matcher, custom formatter, etc...)
- shellspec is tested by shellspec
dash, bash, ksh, mksh, pdksh, zsh, posh, yash, busybox (ash)
Tested Platforms:
- Linux (ubuntu, debian, alpine)
- Windows 10 (WSL, cygwin, Git Bash)
- macOS Mojave
- Solaris 11
Confirmed version:
- ash 0.3.8 (debian 3.0)
- dash 0.5.3 (debian 4.0)
- busybox ash 1.1.3 (debian 4.0)
- bash 2.03 (debian 2.2)
- zsh 3.1.9 (debian 2.2)
- pdksh 5.2.14 (debian 2.2)
- mksh 28 (debian 4.0)
- ksh93 93q (debian 3.1)
- ksh88 0.5.11 (solaris 11)
- posh 0.3.14 (debian 3.1)
- yash 2.30 (debian 7)
shellspec is implemented in a pure shell script, so what you need is the target shell and few POSIX compliant command.
Currently used external command:
date, mkdir, rm, mv (recommends: printf, ps, readlink, time)
Just get the shellspec and create a symlink in your PATH!
For example
$ cd /SOME/WHERE/TO/INSTALL
$ wget https://github.com/ko1nksm/shellspec/archive/{VERSION}.tar.gz
$ tar xzvf shellspec-{VERSION}.tar.gz
$ ln -s /SOME/WHERE/TO/INSTALL/shellspec-{VERSION}/shellspec /EXECUTABLE/PATH/
# (e.g. /EXECUTABLE/PATH/ = /usr/local/bin/, $HOME/bin/)or create shellspec instead of create a symlink (If you does not have readlink)
$ cat<<'HERE'>/EXECUTABLE/PATH/shellspec
#!/bin/sh
exec /SOME/WHERE/TO/INSTALL/shellspec-{VERSION}/shellspec "$@"
HERE
$ chmod +x /EXECUTABLE/PATH/shellspecJust create your project directory and run shellspec --init to setup to your project
# Create your project directory
$ mkdir <your-project-directory>
$ cd <your-project-directory>
# Initialize
$ shellspec --init
create .shellspec
create spec/spec_helper.sh
# Write your first specfile (of course you can use your favorite editor)
$ cat<<'HERE'>spec/hello_spec.sh
Describe 'hello.sh'
. lib/hello.sh
Example 'hello'
When call hello shellspec
The output should equal 'Hello shellspec!'
End
End
HERE
# Create lib/hello.sh
$ mkdir lib
$ touch lib/hello.sh
# It goes fail because hello function not implemented.
$ shellspec
# Write hello function (of course you can use your favorite editor)
$ cat<<'HERE'>lib/hello.sh
hello() {
echo "Hello ${1}!"
}
HERE
# It goes success!
$ shellspecSee sample directory, and run shellspec sample with in shellspec directory.
You can write structured Example by below DSL.
| DSL | Description |
|---|---|
| Describe | Define a block for Example group. Example group is nestable. |
| Context | Synonym for Describe. |
| Example | Define a block for Example. write your example. |
| Specify | Synonym for Example. |
| End | End of Example group/Example block. |
| Todo | Same as empty example, but not a block. One-liner syntax that it means to be implementation. |
The specfile is a valid as shell script syntax, but performs translation process to implements the scope and line number etc.
Each example group block and example block are translate to sub shell. Therefore, changes inside the block do not affect the outside of the block.
In other words, it can realize local variables and local functions in the specfile. This is very useful for describing a structured Spec.
If you are interested in how to translate, use the --translate option.
You want to temporary skip Describe, Context, Example, Specify block,
add prefixing x and modify to xDescribe, xContext, xExample, xSpecify.
You can define hooks called before/after running example.
| DSL | Description |
|---|---|
| Before | Define a hook called before running example. |
| After | Define a hook called after running example. |
Currentry, shellspec is not provide special function for mocking/stubbing. But redefine shell function can override existing shell function or external command. It can use as substitute as mocking/stubbing.
Remember to Describe, Context, Example, Specify block running in subshell.
When going out of the block will restore overridden function.
An example block is a place to describe the specification. It constitute by maximum of one Evaluation and multiple Expectations.
Defines the action for verification. Maximum of one Evaluation for each Example.
When call echo hello world
<- evaluation type ->
| DSL | Description |
|---|---|
| When | Define a evaluation. |
| evaluation type | Description |
|---|---|
| call | Run shell function or external command. |
| invoke | Run shell function or external command in subshell. |
| run | Run external command. |
Normally you will use call. invoke is similar to call but run in subshell.
invoke usefull for override function in evaluation only and trap exit.
Defines the verifications.
| DSL | Description |
|---|---|
| The | Define The statement. |
| It | Define It statement. |
This is the most basic Expectation. Evaluate the subject is the expected value.
The output should equal 4
<- subject -> <- matcher -> <- expected value ->
modifier is modify subject before expectation.
The line 2 of output should equal 4
<- modofier -> <- subject ->
modifier is chainable.
The word 1 of line 2 of output should equal 4
<- multiple modifier -> <- subject ->
If modifier's first argument is number, you can use names for ordinal numbers.
The second line of output should equal 4
<- modofier -> <- subject ->
It statement is syntax sugar for The statement. Use to avoid top-heavy sentences.
Note: Unlike rspec, It is not alias of Example.
The following two sentences are the same meaning.
The word 1 of line 2 of output should equal 4
It should equal 4 the word 1 of line 2 of output
shellspec supports language chains like chai.js.
Language chains only improve readability. It does not any effect the execution of Expectation.
- a
- an
- as
- the
The following two sentences are the same meaning.
The first word of second line of output should valid number
The first word of the second line of output should valid as a number
| Subject | Description |
|---|---|
| output stdout |
Use the stdout of Evaluation as subject. |
| error stderr |
Use the stderr of Evaluation as subject. |
| status exit status |
Use the exit status of Evaluation as subject. |
funciton <NAME> |
Use the stdout of the execute function as the subject. |
path <PATH>file <PATH>dir <PATH> |
Use the (alias resolved) path as the subject. |
value <VALUE>string <VALUE> |
Use the value of the variable as the subject. |
variable <NAME> |
Use the value of the variable as the subject. |
| Modifier | Description |
|---|---|
line <NUMBER> |
The specified line of the subject. |
| lines | The number of lines of the subject. |
word <NUMBER> |
The specified word of the subject. |
| contents | The contents of the file (current subject should be a file). |
| length | The length of the subject. |
exit status (the subject expect exit status)
| Matcher | Description |
|---|---|
| be success | The exit status should be success. |
| be failure | The exit status should be success. |
stat (the subject expect file)
| Matcher | Description |
|---|---|
| be exist | The file should be exist. |
| be file | The file should be a file. |
| be directory | The file should be a directory. |
| be empty | The file should be an empty. |
| be symlink | The file should be a symlink. |
| be pipe | The file should be a pipe. |
| be socket | The file should be a socket. |
| be readable | The file should be a readable. |
| be writable | The file should be a writable. |
| be executable | The file should be an executable. |
| be block_device | The file should be a block device. |
| be charactor_device | The file should be a charactor device. |
| has setgid | The file should has setgid. |
| has setuid | The file should has setuid. |
valid
| Matcher | Description |
|---|---|
| be valid number | The subject should be valid as a number. |
| be valid funcname | The subject should be valid as a funcname. |
variable (the subject expect variable)
| Matcher | Description |
|---|---|
| be defined | The variable should be defined (set). |
| be undefined | The variable should be undefined (unset). |
| be blank | The variable should be blank (unset or zero length string). |
| be present | The variable should be present (non-zero length string). |
string
| Matcher | Description |
|---|---|
start with <STRING> |
The subject should start with <STRING> |
end with <STRING> |
The subject should end with <STRING> |
equal <STRING> |
The subject should equal <STRING> |
include <STRING> |
The subject should include <STRING> |
match <PATTERN> |
The subject should match <PATTERN> |
other
| Matcher | Description |
|---|---|
satisfy <FUNCTION> [ARGUMENTS...] |
The subject should satisfy <FUNCTION> |
| DSL | Description |
|---|---|
| Set | Set value to the variable. |
| Unset | Unset the variable. |
| Path File Dir |
Define path alias. |
| Debug | Output debug message. |
You can define short path name for long path for readability.
for example
Example 'not use path alias'
The file "/etc/hosts" should be exist
End
Example 'use path alias'
File hosts="/etc/hosts"
The file hosts should be exist
End
You can skip or pending current execution block.
| DSL | Description |
|---|---|
Skip <REASON> |
Skip current block. |
Skip if <REASON> <FUNCTION> [ARGUMENTS...] |
Skip current block with conditional. |
Pending <REASON> |
Pending current block. |
To change default options for shellspec command, create options file.
Read files in the order the bellows and overrides options.
$XDG_CONFIG_HOME/shellspec/options$HOME/.shellspec./.shellspec./.shellspec-local(Do not manage by VCS)
You can run the task with --task option.
| Name | Description | Value |
|---|---|---|
| SHELLSPEC_ROOT | shellspec root directory | If not specified, it detect automatically. (If missing readlink, it may fail.) |
| SHELLSPEC_LIB | shellspec lib directory | $SHELLSPEC_ROOT/lib if not specified. |
| SHELLSPEC_LIBEXEC | shellspec libexec directory | $SHELLSPEC_ROOT/libexec if not specified. |
| SHELLSPEC_TMPDIR | Temporary directory used by shellspec | $TMPDIR or /tmp if not specified. |
| SHELLSPEC_SPECDIR | Specs Directory | spec directory under the current directory. |
| SHELLSPEC_LOAD_PATH | Load path of library | $SHELLSPEC_SPECDIR:$SHELLSPEC_LIB:$SHELLSPEC_LIB/formatters |
The spec_helper.sh loaded by --require spec_helper option.
This file use to preparation for running examples, define custom matchers, and etc.
This directory use to create file for custom matchers, tasks and etc.
If exists spec/banner file, shows banner when shellspec command executed.
To disable shows banner with --no-banner option.
0.7.0
- Added lines modifier.
0.6.0
- Added match matcher.
0.5.0
- Initial public release.