This project is stored on code.google.com as http://code.google.com/p/shunit2/. +All releases as of 2.1.4 and full source are available there. Documentation is +included as part of the source and each release. Source code is stored in +Subversion and can be accessed using the following information.
+Browse the code in a web browser:
+Check out the code locally
++$ svn checkout http://shunit2.googlecode.com/svn/trunk/ shflags-read-only ++
DEPRECATED
+This project is stored on SourceForge as http://sf.net/projects/shunit2. The +source code is stored in Subversion and can be accessed using the following +information.
+Check out the code locally
++$ svn co https://shunit2.svn.sourceforge.net/svnroot/shunit2/trunk/source/2.1 shunit2 ++
Browse the code in a web browser:
+ +For these steps, it is assumed we are working with release 2.0.0.
+Steps:
+This should be pretty self explanatory. Use one of the release notes from a +previous release as an example.
+The versions of the various platforms and shells are included when the +master unit test script is run, or when bin/gen_test_results.sh is +used. To determine the versions of the installed shells by hand, use the +lib/versions script.
+Alternatively, do the following:
+Shell | +OS | +Notes | +
---|---|---|
bash | ++ | $ bash --version | +
dash | +Linux | +$ dpkg -l |grep dash | +
ksh | ++ | $ ksh --version +-or- +$ echo 'echo $KSH_VERSION' |ksh | +
Cygwin | +see pdksh | +|
Solaris | +$ strings /usr/bin/ksh |grep 'Version' | +|
pdksh | ++ | $ strings /bin/pdksh |grep 'PD KSH' | +
Cygwin | +look in the downloaded Cygwin directory | +|
sh | +Solaris | +not possible | +
zsh | ++ | $ zsh --version | +
Edit src/shell/shunit2 and change the version number in the comment, as well +as in the SHUNIT_VERSION variable.
+Make sure that any remaining changes get put into the CHANGES-X.X.txt file.
+Finish writing the RELEASE_NOTES-X.X.X.txt. If necessary, run it +through the fmt command to make it pretty (hopefully it is already).
++$ fmt -w 80 RELEASE_NOTES-2.0.0.txt >RELEASE_NOTES-2.0.0.txt.new +$ mv RELEASE_NOTES-2.0.0.txt.new RELEASE_NOTES-2.0.0.txt ++
We want to have an up-to-date version of the documentation in the release, so +we'd better build it.
++$ pwd +.../shunit2/source/2.1 +$ cd doc +$ RST2HTML_OPTS='--stylesheet-path=rst2html.css' +$ rst2html ${RST2HTML_OPTS} shunit2.txt >shunit2.html +$ rst2html ${RST2HTML_OPTS} README.txt >README.html ++
This step is pretty self-explanatory
++$ pwd +.../shunit2/source/2.0 +$ svn ci -m "finalizing release" ++
+$ pwd +.../shunit2/source +$ ls +2.0 2.1 +$ svn cp -m "Release 2.0.0" 2.0 https://shunit2.googlecode.com/svn/tags/source/2.0.0 ++
+$ pwd +.../shunit2/builds +$ svn export https://shunit2.googlecode.com/svn/tags/source/2.0.0 shunit2-2.0.0 ++
+$ tar cfz ../releases/shunit2-2.0.0.tgz shunit2-2.0.0 ++
+$ cd ../releases +$ gpg --default-key kate.ward@forestent.com --detach-sign shunit2-2.0.0.tgz ++
Again, pretty self-explanatory. Make sure to copy the GPG signature file. Once +that is done, make sure to tag the website so we can go back in time if needed.
++$ pwd +.../shunit2 +$ ls +source website +$ svn cp -m "Release 2.0.0" \ +website https://shunit2.googlecode.com/svn/tags/website/20060916 ++
Now, update the website. It too is held in Subversion, so ssh into the web +server and use svn up to grab the latest version.
+shUnit2 is a xUnit unit test framework for Bourne based shell scripts, and it +is designed to work in a similar manner to JUnit, PyUnit, etc.. If you have +ever had the desire to write a unit test for a shell script, shUnit2 can do the +job.
+Table of Contents
+shUnit2 was originally developed to provide a consistent testing solution for +log4sh, a shell based logging framework similar to log4j. During the +development of that product, a repeated problem of having things work just fine +under one shell (/bin/bash on Linux to be specific), and then not working +under another shell (/bin/sh on Solaris) kept coming up. Although several +simple tests were run, they were not adequate and did not catch some corner +cases. The decision was finally made to write a proper unit test framework after +multiple brown-bag releases were made. Research was done to look for an +existing product that met the testing requirements, but no adequate product was +found.
+Tested Operating Systems (varies over time)
+Tested Shells
+See the appropriate Release Notes for this release +(doc/RELEASE_NOTES-X.X.X.txt) for the list of actual versions tested.
+A list of contributors to shUnit2 can be found in the source archive in +doc/contributors.txt. Many thanks go out to all those who have contributed +to make this a better tool.
+shUnit2 is the original product of many hours of work by Kate Ward, the primary +author of the code. For other products by her, look up log4sh or shFlags, or +visit her website at http://forestent.com/.
+Feedback is most certainly welcome for this document. Send your additions, +comments and criticisms to the shunit2-users@google.com mailing list.
+This section will give a very quick start to running unit tests with shUnit2. +More information is located in later sections.
+Here is a quick sample script to show how easy it is to write a unit test in +shell. Note: the script as it stands expects that you are running it from the +``examples`` directory.
++#! /bin/sh +# file: examples/equality_test.sh + +testEquality() +{ + assertEquals 1 1 +} + +# load shunit2 +. ../src/shell/shunit2 ++
Running the unit test should give results similar to the following.
++testEquality + +Ran 1 test. + +OK ++
W00t! You've just run your first successful unit test. So, what just happened? +Quite a bit really, and it all happened simply by sourcing the shunit2 +library. The basic functionality for the script above goes like this:
+We should now try adding a test that fails. Change your unit test to look like +this.
++#! /bin/sh +# file: examples/party_test.sh + +testEquality() +{ + assertEquals 1 1 +} + +testPartyLikeItIs1999() +{ + year=`date '+%Y'` + assertEquals "It's not 1999 :-(" \ + '1999' "${year}" +} + +# load shunit2 +. ../src/shell/shunit2 ++
So, what did you get? I guess it told you that this isn't 1999. Bummer, eh? +Hopefully, you noticed a couple of things that were different about the second +test. First, we added an optional message that the user will see if the assert +fails. Second, we did comparisons of strings instead of integers as in the first +test. It doesn't matter whether you are testing for equality of strings or +integers. Both work equally well with shUnit2.
+Hopefully, this is enough to get you started with unit testing. If you want a +ton more examples, take a look at the tests provided with log4sh or shFlags. +Both provide excellent examples of more advanced usage. shUnit2 was after all +written to help with the unit testing problems that log4sh had.
+Any string values passed should be properly quoted -- they should must be +surrounded by single-quote (') or double-quote (") characters -- so that the +shell will properly parse them.
+Asserts that a given shell test condition is true. The condition can be as +simple as a shell true value (the value 0 -- equivalent to +${SHUNIT_TRUE}), or a more sophisticated shell conditional expression. The +message is optional, and must be quoted.
+A sophisticated shell conditional expression is equivalent to what the if +or while shell built-ins would use (more specifically, what the test +command would use). Testing for example whether some value is greater than +another value can be done this way.
++assertTrue "[ 34 -gt 23 ]" ++
Testing for the ability to read a file can also be done. This particular test +will fail.
++assertTrue 'test failed' "[ -r /some/non-existant/file' ]" ++
As the expressions are standard shell test expressions, it is possible to +string multiple expressions together with -a and -o in the standard +fashion. This test will succeed as the entire expression evaluates to true.
++assertTrue 'test failed' '[ 1 -eq 1 -a 2 -eq 2 ]' ++
One word of warning: be very careful with your quoting as shell is not the +most forgiving of bad quoting, and things will fail in strange ways.
+Asserts that a given shell test condition is false. The condition can be +as simple as a shell false value (the value 1 -- equivalent to +${SHUNIT_FALSE}), or a more sophisticated shell conditional expression. +The message is optional, and must be quoted.
+For examples of more sophisticated expressions, see ``assertTrue``.
+Just to clarify, failures do not test the various arguments against one +another. Failures simply fail, optionally with a message, and that is all they +do. If you need to test arguments against one another, use asserts.
+If all failures do is fail, why might one use them? There are times when you may +have some very complicated logic that you need to test, and the simple asserts +provided are simply not adequate. You can do your own validation of the code, +use an assertTrue ${SHUNIT_TRUE} if your own tests succeeded, and use a +failure to record a failure.
+Fails the test immediately, reporting that the unexpected and actual +values are not equal to one another. The message is optional, and must be +quoted.
+Note: no actual comparison of unexpected and actual is done.
+Fails the test immediately, reporting that the expected and actual values +are the same. The message is optional, and must be quoted.
+Note: no actual comparison of expected and actual is done.
+Fails the test immediately, reporting that the expected and actual values +are not the same. The message is optional, and must be quoted.
+Note: no actual comparison of expected and actual is done.
+This function can be be optionally overridden by the user in their test suite.
+If this function exists, it will be called once before any tests are run. It +is useful to prepare a common environment for all tests.
+This function can be be optionally overridden by the user in their test suite.
+If this function exists, it will be called once after all tests are completed. +It is useful to clean up the environment after all tests.
+This function can be be optionally overridden by the user in their test suite.
+If this function exists, it will be called before each test is run. It is +useful to reset the environment before each test.
+This function can be be optionally overridden by the user in their test suite.
+If this function exists, it will be called after each test completes. It is +useful to clean up the environment after each test.
+The default behavior of shUnit2 is that all tests will be found dynamically. If +you have a specific set of tests you want to run, or you don't want to use the +standard naming scheme of prefixing your tests with test, these functions +are for you. Most users will never use them though.
+This function can be optionally overridden by the user in their test suite.
+If this function exists, it will be called when shunit2 is sourced. If it +does not exist, shUnit2 will search the parent script for all functions +beginning with the word test, and they will be added dynamically to the +test suite.
+This section covers several advanced usage topics.
+There are several constants provided by shUnit2 as variables that might be of +use to you.
+Predefined
+SHUNIT_VERSION | +The version of shUnit2 you are running. | +
SHUNIT_TRUE | +Standard shell true value (the integer value 0). | +
SHUNIT_FALSE | +Standard shell false value (the integer value 1). | +
SHUNIT_ERROR | +The integer value 2. | +
SHUNIT_TMPDIR | +Path to temporary directory that will be automatically +cleaned up upon exit of shUnit2. | +
User defined
+SHUNIT_PARENT | +The filename of the shell script containing the tests. This +is needed specifically for Zsh support. | +
The constants values SHUNIT_TRUE, SHUNIT_FALSE, and SHUNIT_ERROR are +returned from nearly every function to indicate the success or failure of the +function. Additionally the variable flags_error is filled with a detailed +error message if any function returns with a SHUNIT_ERROR value.
+If you include lots of assert statements in an individual test function, it can +become difficult to determine exactly which assert was thrown unless your +messages are unique. To help somewhat, line numbers can be included in the +assert messages. To enable this, a special shell "macro" must be used rather +than the standard assert calls. Shell doesn't actually have macros; the name is +used here as the operation is similar to a standard macro.
+For example, to include line numbers for a assertEquals() function call, +replace the assertEquals() with ${_ASSERT_EQUALS_}.
+Example -- Asserts with and without line numbers
++#! /bin/sh +# file: examples/lineno_test.sh + +testLineNo() +{ + # this assert will have line numbers included (e.g. "ASSERT:[123] ...") + echo "ae: ${_ASSERT_EQUALS_}" + ${_ASSERT_EQUALS_} 'not equal' 1 2 + + # this assert will not have line numbers included (e.g. "ASSERT: ...") + assertEquals 'not equal' 1 2 +} + +# load shunit2 +. ../src/shell/shunit2 ++
Notes:
+Due to how shell parses command-line arguments, all strings used with macros +should be quoted twice. Namely, single-quotes must be converted to +single-double-quotes, and vice-versa. If the string being passed is +absolutely for sure not empty, the extra quoting is not necessary.
+Normal assertEquals call.
++assertEquals 'some message' 'x' '' ++
Macro _ASSERT_EQUALS_ call. Note the extra quoting around the message +and the null value.
++_ASSERT_EQUALS_ '"some message"' 'x' '""' ++
Line numbers are not supported in all shells. If a shell does not support +them, no errors will be thrown. Supported shells include: bash (>=3.0), +ksh, pdksh, and zsh.
+There are times where the test code you have written is just not applicable to +the system you are running on. This section describes how to skip these tests +but maintain the total test count.
+Probably the easiest example would be shell code that is meant to run under the +bash shell, but the unit test is running under the Bourne shell. There are +things that just won't work. The following test code demonstrates two sample +functions, one that will be run under any shell, and the another that will run +only under the bash shell.
+Example -- math include
++# available as examples/math.inc + +add_generic() +{ + num_a=$1 + num_b=$2 + + expr $1 + $2 +} + +add_bash() +{ + num_a=$1 + num_b=$2 + + echo $(($1 + $2)) +} ++
And here is a corresponding unit test that correctly skips the add_bash() +function when the unit test is not running under the bash shell.
+Example -- math unit test
++#! /bin/sh +# available as examples/math_test.sh + +testAdding() +{ + result=`add_generic 1 2` + assertEquals \ + "the result of '${result}' was wrong" \ + 3 "${result}" + + # disable non-generic tests + [ -z "${BASH_VERSION:-}" ] && startSkipping + + result=`add_bash 1 2` + assertEquals \ + "the result of '${result}' was wrong" \ + 3 "${result}" +} + +oneTimeSetUp() +{ + # load include to test + . ./math.inc +} + +# load and run shUnit2 +. ../src/shell/shunit2 ++
Running the above test under the bash shell will result in the following +output.
++$ /bin/bash math_test.sh +testAdding + +Ran 1 test. + +OK ++
But, running the test under any other Unix shell will result in the following +output.
++$ /bin/ksh math_test.sh +testAdding + +Ran 1 test. + +OK (skipped=1) ++
As you can see, the total number of tests has not changed, but the report +indicates that some tests were skipped.
+Skipping can be controlled with the following functions: startSkipping(), +stopSkipping(), and isSkipping(). Once skipping is enabled, it will +remain enabled until the end of the current test function call, after which +skipping is disabled.
+For help, please send requests to either the shunit2-users@googlegroups.com +mailing list (archives available on the web at +http://groups.google.com/group/shunit2-users) or directly to +Kate Ward <kate dot ward at forestent dot com>.
+For compatibility with Zsh, there is one requirement that must be met -- the +shwordsplit option must be set. There are three ways to accomplish this.
+In the unit-test script, add the following shell code snippet before sourcing +the shunit2 library.
++setopt shwordsplit ++
When invoking zsh from either the command-line or as a script with +#!, add the -y parameter.
++#! /bin/zsh -y ++
When invoking zsh from the command-line, add -o shwordsplit -- as +parameters before the script name.
++$ zsh -o shwordsplit -- some_script ++