Introduction

shellUnit is a unit-test framework for bash-style and csh-style shells. It provides a way to separate the tests from the scripts, test functions main procedure and code blocks from a shell script.

A set os exaples will we shown now. This document is automatically generated by the mkdoc.sh script in the documents directory so commands and outputs match.

Sample

A test script is a plain shell script with a few characteristics. Test functions are declared csh-style and start with 'test', also, to be recogniced by automatic directory testing, its extension must be '.shu'

#sample-01.shu
testSimpleTest () {
    #Add test here
    #Some shells (bash) don't like empty functions
    #so we add an echo here
    echo "Test!"
}
Running

To execute a test file we use the 'shu' command. It needs at least one parameter, indicating either a test file:

# shu sample-01.shu

TEST: SimpleTest
-----------------------------------
 All test completed in 1ms
 Warning: No asserts run!
 All assert(s) passed.
-----------------------------------

Or a directory containing various .shu files

# shu ./sample-dir/

TEST: AnotherSimpleTest
-----------------------------------
 All test completed in 1ms
 Warning: No asserts run!
 All assert(s) passed.
-----------------------------------
TEST: SimpleTest
-----------------------------------
 All test completed in 1ms
 Warning: No asserts run!
 All assert(s) passed.
-----------------------------------
Targetting

To provide a target we use the 'shu_loadTarget' function which as a parameter has the path to the shell script to be tested. At pressent, loading a target executes its 'main' procedure (all commands not in a function) so be careful with that.

#sample-02.shu
shu_loadTarget ./target.sh

testASample () {
  OUTPUT=`aSample`
  assertEquals $OUTPUT "SAMPLE"
}

The sample target file:

aSample () {
  echo "SAMPLE"
}

The output of shu ./sample-02/

TEST: ASample
 Assert @5 succeeded. (1ms)
-----------------------------------
 All test completed in 1ms
 All assert(s) passed.
-----------------------------------

The test asserts that 'a_function' will echo the string 'TEST'. shellUnit provides many assertions for different situations. here is a test using all the 'basic' asserts.

#basic-asserts.shu

#This script will test the the basic asserts

testTrue () {
    assertTrue "[ a = a ]"
}

testFalse () {
    assertFalse "[ a = b ]"
}

testEquals () {
    assertEquals 2 2
    assertEquals "a" "a"
    assertEquals "a" a
    assertEquals "2" 2
}

testNotEquals () {
    assertNotEquals 1 2
    assertNotEquals "A" "B"
    assertNotEquals "3" 4
    assertNotEquals "A" Z
}

testGreaterThan () {
    assertGreaterThan 3 2
    assertGreaterThan "5" 1
}

testNotGreaterThan () {
    assertNotGreaterThan 3 5
    assertNotGreaterThan "3" 7
    assertNotGreaterThan 3 3
}

testLessThan () {
    assertLessThan 3 5
    assertLessThan "3" 5
}

testNotLessThan () {
    assertNotLessThan 5 4
    assertNotLessThan "5" 4
    assertNotLessThan 5 5
}

testMatches () {
    assertMatches "[0-9]-[A-Z]" "1234-ASDF"
}

testNotMatches () {
    assertNotMatches "[0-9]-[A-Z]" "ASDF-1234"
}

The result of executing this test file is:

TEST: True
 Assert @5 succeeded. (1ms)
TEST: False
 Assert @9 succeeded. (1ms)
TEST: Equals
 Assert @13 succeeded. (1ms)
 Assert @14 succeeded. (2ms)
 Assert @15 succeeded. (2ms)
 Assert @16 succeeded. (1ms)
TEST: NotEquals
 Assert @20 succeeded. (1ms)
 Assert @21 succeeded. (2ms)
 Assert @22 succeeded. (3ms)
 Assert @23 succeeded. (2ms)
TEST: GreaterThan
 Assert @27 succeeded. (1ms)
 Assert @28 succeeded. (2ms)
TEST: NotGreaterThan
 Assert @32 succeeded. (1ms)
 Assert @33 succeeded. (2ms)
 Assert @34 succeeded. (1ms)
TEST: LessThan
 Assert @38 succeeded. (0ms)
 Assert @39 succeeded. (2ms)
TEST: NotLessThan
 Assert @43 succeeded. (0ms)
 Assert @44 succeeded. (2ms)
 Assert @45 succeeded. (2ms)
TEST: Matches
 Assert @49 succeeded. (2ms)
TEST: NotMatches
 Assert @53 succeeded. (2ms)
-----------------------------------
 All test completed in 57ms
 All assert(s) passed.
-----------------------------------

shellUnit works as a preprocessor for test files. This means that to provide meaningful messages and dispatch tests it reads a test file and its targets and produces a single shell script which is executed. Because of that, some variables won't be accesible from a shell script, most notably the return-value variable$?. To access the return value, the SHU_RV variable is provided.

#return-sample.shu

addition () {
  RV=`expr "$1" + "$2"`
  return $RV
}

testReturnValue () {
  addition 10 5
  assertEquals 15 $SHU_RV
  addition 100 0
  assertEquals 203 $SHU_RV
}

Executing the test will yield the following output. Notice that an assert, wether failed or not, will print the line it is located preceded by an '@', and if failed, a reason why will be provided. Also, any function not starting with 'test' will be ignored by shellUnit, this way we can make self-contained tests if wanted.

TEST: ReturnValue
 Assert @9 succeeded. (3ms)
 Assert @11 failed. (2ms) Reason: expected '203' but found '100'
-----------------------------------
 All test completed in 5ms
 1 asserts failed.
-----------------------------------

For testing the 'main' function, as if we called the script from the shell, shellUnit provided the 'shu_main' function, which will behave like the shell script.

testMain () {
    shu_main "A parameter" "another param"
    #Asserts below this line...
}
Results

shellUnit results can be formatted in different ways, the results shown so far being the default output formatter. So far, all samples were using the default 'pretty' formatter.

The 'raw' output formatter present the test result as CSV. Raw output can be activated by passing the --raw option to shellUnit. This format is intended to be used by other programs as its input. It provides various information about the tests and asserts being run.

An example of the raw output, using the basic asserts files:

# shu --raw basic-asserts.shu

SHU,TEST,1395323159388,basic-asserts.shu,True
SHU,ASSERT,basic-asserts.shu,True,5,1395323159388,1395323159389,OK,
SHU,TEST,1395323159392,basic-asserts.shu,False
SHU,ASSERT,basic-asserts.shu,False,9,1395323159392,1395323159393,OK,
SHU,TEST,1395323159395,basic-asserts.shu,Equals
SHU,ASSERT,basic-asserts.shu,Equals,13,1395323159395,1395323159396,OK,
SHU,ASSERT,basic-asserts.shu,Equals,14,1395323159396,1395323159398,OK,
SHU,ASSERT,basic-asserts.shu,Equals,15,1395323159398,1395323159399,OK,
SHU,ASSERT,basic-asserts.shu,Equals,16,1395323159399,1395323159401,OK,
SHU,TEST,1395323159403,basic-asserts.shu,NotEquals
SHU,ASSERT,basic-asserts.shu,NotEquals,20,1395323159403,1395323159404,OK,
SHU,ASSERT,basic-asserts.shu,NotEquals,21,1395323159404,1395323159406,OK,
SHU,ASSERT,basic-asserts.shu,NotEquals,22,1395323159406,1395323159408,OK,
SHU,ASSERT,basic-asserts.shu,NotEquals,23,1395323159408,1395323159409,OK,
SHU,TEST,1395323159412,basic-asserts.shu,GreaterThan
SHU,ASSERT,basic-asserts.shu,GreaterThan,27,1395323159412,1395323159412,OK,
SHU,ASSERT,basic-asserts.shu,GreaterThan,28,1395323159412,1395323159414,OK,
SHU,TEST,1395323159416,basic-asserts.shu,NotGreaterThan
SHU,ASSERT,basic-asserts.shu,NotGreaterThan,32,1395323159416,1395323159417,OK,
SHU,ASSERT,basic-asserts.shu,NotGreaterThan,33,1395323159417,1395323159419,OK,
SHU,ASSERT,basic-asserts.shu,NotGreaterThan,34,1395323159419,1395323159421,OK,
SHU,TEST,1395323159424,basic-asserts.shu,LessThan
SHU,ASSERT,basic-asserts.shu,LessThan,38,1395323159424,1395323159425,OK,
SHU,ASSERT,basic-asserts.shu,LessThan,39,1395323159425,1395323159426,OK,
SHU,TEST,1395323159429,basic-asserts.shu,NotLessThan
SHU,ASSERT,basic-asserts.shu,NotLessThan,43,1395323159429,1395323159430,OK,
SHU,ASSERT,basic-asserts.shu,NotLessThan,44,1395323159430,1395323159432,OK,
SHU,ASSERT,basic-asserts.shu,NotLessThan,45,1395323159432,1395323159433,OK,
SHU,TEST,1395323159436,basic-asserts.shu,Matches
SHU,ASSERT,basic-asserts.shu,Matches,49,1395323159436,1395323159438,OK,
SHU,TEST,1395323159441,basic-asserts.shu,NotMatches
SHU,ASSERT,basic-asserts.shu,NotMatches,53,1395323159441,1395323159443,OK,

The CSV format is not yet frozen so no information on what each column is will be provided until then.

Setup/Teardown

Test scripts may optionally have a 'setup' and 'teardown' function, which will be executed before and after every test respectively.

#sample_test.shu

setup () {
    #Test setup goes here:
}

teardown () {
    #Test teardown goes here
}

testOne () {
    #A test
}

testTwo () {
    #Another test
}
Groups
Parametrizing