In the OOP universum, there is a lot of information on how to desgin and refactor code to make it unit testing friendly. But I wonder, how to apply/translate those principles/practices (make mocking easier etc.) to shell scripting, which is obviously different programming.
I have to tackle a very huge code base; many executable and non-executable procedures, large functions, big global state, many environment variables, and everywhere (unnecessary) interprocess communication and file handling through redirection/pipelines and (unnecessary) use of external utilities.
How to refactor shell code (or design it in the beginning) to be able to make "good" automated unit tests with a framework like bats and a mocking-plugin?
Testable code is code of high quality. It's cohesive and loosely coupled. It's well encapsulated and in charge of its own state. In short, it's singularly defined in its own proper place so that it's straightforward to maintain and extend.
$? is the exit status of the most recently-executed command; by convention, 0 means success and anything else indicates failure. That line is testing whether the grep command succeeded. The grep manpage states: The exit status is 0 if selected lines are found, and 1 if not found.
Good question!
IMHO shellscripts often just call other programms to get stuff done, like cp
, mv
, tar
, rsync
, ... even for expressions bash is using the binary test
if you use [ and ] (eg. if [ -f $file ]; then; fi
).
Having that in mind, think about that stuff that really happens just in the bash script: Call that programm with three arguments. So you could write a unit-tests, which checks if the bash script calls the desired programm and uses the right arguments and checks the return values / exit codes from the programm.
You definitly don't want to put things in unit-tests for you shell script, which is effectivly done by another programm (e.g. check if rsync
really copied files from machine A to machine B).
Just my two cents
Unit-testing is for findings bugs in the isolated code. Typical shell code, however, is dominated by interactions with other executables or the operating system. The type of problems that lies in interactions in shell code goes in the direction of, am I calling the right executables in the right order with the arguments in the right order with properly formatted argument values, and are the outputs in the form I expect them to be etc. To test all this, you should not apply unit-testing, but integration testing instead.
However, there is shell code that is suitable for unit-testing. This is, for example, code performing computations within the shell, or string manipulations. I would even consider shell code with calls to certain fundamental tools like basename
as suitable for unit-testing (interpreting such tools as being part of the 'standard library' if you like).
How to make those code parts in a shell that are suitable for being unit-tested actually testable with unit-testing? One of the most useful approaches in my experience is to separate interactions from computations. That is, try to put the computational parts in separate shell functions to be tested, or extract the interaction dominated parts in separate shell functions. That saves you a lot of mocking effort.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With