Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write unit testable bash shell code?

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?

like image 948
D630 Avatar asked Feb 05 '19 07:02

D630


People also ask

What is unit testable code?

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.

What is $? == 0 in shell script?

$? 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.


Video Answer


2 Answers

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

like image 103
Mirko Steiner Avatar answered Oct 19 '22 03:10

Mirko Steiner


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.

like image 30
Dirk Herrmann Avatar answered Oct 19 '22 02:10

Dirk Herrmann