Shell Script unit testing: how to execute a complex utility layout

I am involved in unit testing some legacy shell scripts.

In the real world, scripts are often used to call utilities such as find , tar , cpio , grep , sed , rsync , date , etc. with some rather complicated commands containing many options. Sometimes regular expressions or wildcard patterns are created and used.

example : a shell script, which is usually called by cron at regular intervals, has the task of flipping some huge directory trees from one computer to another using the rsync utility. Several types of files and directories should be excluded from the mirroring process:

  #!/usr/bin/env bash ... function mirror() { ... COMMAND="rsync -aH$VERBOSE$DRY $PROGRESS $DELETE $OTHER_OPTIONS \ $EXCLUDE_OPTIONS $SOURCE_HOST:$DIRECTORY $TARGET" ... if eval $COMMAND then ... else ... fi ... } ... 

As Michael Perce wrote in his famous book Effectively working with Legacy Code , a good unit test works very quickly and does not touch the network, file system or open any database.

Following Michael Peruzโ€™s advice, the following technique should be used: dependency injection . The object to replace here is the rsync utility.

My first idea: in my shell script, the testing environment (I use bats ) I manipulate $PATH so that mockup rsync found instead of the real rsync utility. This mockup object can check the parameters and parameters of the provided command line. Similar to other utilities used in this part of the script in the test .

My past experience with real problems in this area of โ€‹โ€‹scripting was often an error caused by special characters in file or directory names, problems with quoting or encodings, missing ssh keys, incorrect permissions, etc. Such errors could avoid this unit testing technique. (I know: for some of these problems, unit testing is simply not a cure).

Another drawback is that creating a layout for a complex utility like rsync or find is error prone and a tedious engineering task.

I believe that the situation described above is quite common, that other people may encounter similar problems. Who has any smart ideas and would like to share them with me?

+5
source share
2 answers

Cargill quandary:

"Any design problem can be solved by adding an additional level of indirection, with the exception of too many levels of indirection."

Why mimic system commands? After all, if you program Bash, the system is your target, and you should evaluate your script using the system.

Unit test, as the name implies, will give you confidence in the unitary part of the system that you are designing. So you will need to define what your block is in case of a bash script. Function? A script file? Command?

Given that you want to define the device as a function, I would suggest writing a list of known errors listed above:

  • Special characters in file or directory names
  • Problems with quoting or encodings
  • Missing ssh keys
  • Incorrect permissions, etc.

And write a test case for it. And try not to deviate from system commands, since they are an integral part of the system that you deliver.

+1
source

You can mock up any command using a function, for example:

 function rsync() { # mock things here if necessary } 

Then export the function and run unittest:

 export -f rsync unittest 
0
source

Source: https://habr.com/ru/post/1245766/


All Articles