General Info

Asserts

Command Description
assertEquals [MESSAGE] EXPECTED ACTUAL Compares Strings and Integer values, the message must be “quoted”
assertNotEquals [MESSAGE] UNEXPECTED ACTUAL Compares Strings and Integer values, the message must be “quoted”
assertSame [MESSAGE] EXPECTED ACTUAL Functionally equivalent to assertEquals
assertNotSame [MESSAGE] UNEXPECTED ACTUAL Functionally equivalent to assertNotEquals
assertContains [MESSAGE] CONTAINER CONTENT Container contains content
assertNotContains [MESSAGE] CONTAINER CONTENT Container does not contain content
assertNull [MESSAGE] VALUE Value is null, or in shell terms, a zero-length string
assertNotNull [MESSAGE] VALUE Value is not null, or in shell terms, a non-empty string
assertTrue [MESSAGE] CONDITION Shell test condition is true
assertFalse [MESSAGE] CONDITION Shell test condition is false
  • Examples:
    • Testing for the ability to read a file can also be done.
      1
      assertTrue 'test failed' "[ -r /some/non-existant/file' ]"
    • Testing using and (-a), or (-o)
      1
      assertTrue 'test failed' '[ 1 -eq 1 -a 2 -eq 2 ]'

Failures

Command Description
fail [MESSAGE] Fails immediately. The message must be quoted
failNotEquals [MESSAGE] UNEXPECTED ACTUAL Fails immediately, reporting that the unexpected and actual values are not equal to one another
failSame [MESSAGE] EXPECTED ACTUAL Fails immediately, reporting that the expected and actual values are the same
failNotSame [message] EXPECTED ACTUAL Fails inmediately, reporting that the expected and actual values are not the same
failFound [message] CONTENT Fails immediately, reporting that the content was found
failNotFound [message] CONTENT Fails the test immediately, reporting that the content was not found

Setup/Teardown

Command Description
oneTimeSetUp Setup common environment
oneTimeTearDown Clean up the environment after all tests
setUp Reset the environment before each test
tearDown Clean up the environment after each test

Skipping

Command Description
startSkipping Forces the remaining assert and fail functions to be “skipped”
endSkipping Returns calls to the assert and fail functions to their default behavior, i.e. they will be called
isSkipping Returns the current state of skipping. It can be compared against ${SHUNIT_TRUE} or ${SHUNIT_FALSE} if desired

Suites

Command Description
suite

  • If this function exists, it will be called when shunit2 is sourced
  • IIf not, shUnit2 will search the parent script for all functions beginning with the word test, and they will be added dynamically to the test suite
  • suite_addTest name

  • This function adds a function named name to the list of tests scheduled for execution as part of this test suite
  • This function should only be called from within the suite() function
  • Advanced

    Constants

    • Predefined

      Constant Value
      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
      SHUNIT_VERSION The version of shUnit2 you are running
    • User defined

      Constant Value
      SHUNIT_CMD_EXPR Override which expr command is used. By default expr is used, except on BSD systems where gexpr is used
      SHUNIT_COLOR Enable colorized output. Options are ‘auto’, ‘always’, or ‘none’, with ‘auto’ being the default
      SHUNIT_PARENT The filename of the shell script containing the tests. This is needed specifically for Zsh support
      SHUNIT_TEST_PREFIX Define this variable to add a prefix in front of each test name that is output in the test report

    Error handling

    • 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
    • The variable flags_error is filled with a detailed error message if any function returns with a SHUNIT_ERROR value

    Test skipping

    • File
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # file: 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))
      }
    • Test file
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      #! /bin/sh
      # file: 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.
      . ./shunit2

    Run specific test

    • Simply run script

      1
      test-script.sh -- testOne testTwo otherFunction
    • Clarifying, with shunit2 call

      1
      shunit2 test-script.sh testOne testTwo otherFunction

    Introduction to shUnit2

    Unit tests for Bash with shUnit2

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

    Setup

    • Install

      1
      2
      3
      4
      # Manjaro
      yum -y install shunit2

      # You may also just download shunit2 and make sure it's on your path.
    • Quickstart

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      #! /bin/sh
      # file: examples/party_test.sh

      testEquality()
      {
      assertEquals 1 1
      }

      # load shunit2
      . /usr/share/shunit2/shunit2

    Tests structure

    • 3 sections:
      • set up section
        • setUp and tearDown functions
        • define mocks
      • test cases section
      • call to the shunit2 test runner (. shunit2.)

    Example

    • File to test

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      #!/usr/bin/env bash

      cidr="$1"

      usage() {
      [ ! -z "$1" ] && echo $1
      cat <<EOF
      Print all IPs in a CIDR range, similar to the Ubuntu prips utility.
      This script assumes that the Red Hat version of ipcalc is available.
      Usage: $0 <cidr> [-h]
      Example: $0 192.168.0.3/28
      EOF
      exit 1
      }
      [ -h == "$1" ] && usage
      [ ! -z "$2" ] && usage 'You may only pass one CIDR'
      [ -z "$cidr" ] && usage 'You must pass a CIDR'
      echo $cidr | egrep -q "^(?:[0-9]+\.){3}[0-9]+/[0-9]+$" || \
      usage "$cidr is not a valid CIDR"

      # range is bounded by network (-n) & broadcast (-b) addresses.
      lo=$(ipcalc -n $cidr | cut -f2 -d=)
      hi=$(ipcalc -b $cidr | cut -f2 -d=)

      IFS=. read a b c d <<< "$lo"
      IFS=. read e f g h <<< "$hi"

      eval "echo {$a..$e}.{$b..$f}.{$c..$g}.{$d..$h}"
    • The test cases (White-box testing)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      #!/usr/bin/env bash

      # Mock the output of the ipcalc.
      ipcalc() {
      case "$*" in
      "-n 192.168.0.2/28")
      echo NETWORK=192.168.0.0
      ;;
      "-b 192.168.0.2/28")
      echo BROADCAST=192.168.0.15
      ;;
      "-n 10.45.0.0/16")
      echo NETWORK=10.45.0.0
      ;;
      "-b 10.45.0.0/16")
      echo BROADCAST=10.45.255.255
      esac
      }

      # test cases
      test_minus_h() {
      first_line=$(. ./prips.sh -h | head -1)
      assertEquals "Print all IPs in a CIDR range, similar to the Ubuntu \
      prips utility." "$first_line"
      }

      test_missing_args() {
      first_line=$(. ./prips.sh | head -1)
      assertEquals 'You must pass a CIDR' "$first_line"
      }

      test_too_many_args() {
      first_line=$(. ./prips.sh 192.168.0.2/28 192.168.0.2/30 | head -1)
      assertEquals 'You may only pass one CIDR' "$first_line"
      }

      test_bad_input() {
      first_line=$(. ./prips.sh bad_input | head -1)
      assertEquals 'bad_input is not a valid CIDR' "$first_line"
      }

      test_a_little_cidr() {
      response=$(. ./prips.sh 192.168.0.2/28)
      expected="192.168.0.0 192.168.0.1 192.168.0.2 192.168.0.3 192.168.0.4 \
      192.168.0.5 192.168.0.6 192.168.0.7 192.168.0.8 192.168.0.9 192.168.0.10 \
      192.168.0.11 192.168.0.12 192.168.0.13 192.168.0.14 192.168.0.15"
      assertEquals "$expected" "$response"
      }

      test_a_big_cidr() {
      number_of_ips=$(. ./prips.sh 10.45.0.0/16 | wc -w)
      assertEquals 65536 "$number_of_ips"
      }

      . shunit2
    • Run tests

      1
      bash examples/test_prips.sh

    Setup

    • Install Container Structure Test
      1
      2
      3
      curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && \
      chmod +x container-structure-test-linux-amd64 && \
      sudo mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test
    • Select test options:
      • Command Tests: execute a command in your image and check the output
      • File Existence Tests: check if a file is, or isn’t, present in the image
      • File Content Tests: check the content of a file
      • Metadata Test: check if a container metadata is correct

    Write unit tests

    Prerequisites

    • a Dockerfile
    • a .yaml or .json file that contains your test cases

    Example

    • Pre-existing code
      • Dockerfile
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        FROM ubuntu:bionic

        RUN apt-get update \
        && apt-get install -y curl gnupg \
        && curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg \
        && mv bazel.gpg /etc/apt/trusted.gpg.d/ \
        && echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" > /etc/apt/sources.list.d/bazel.list \
        && apt-get update \
        && apt-get install -y bazel \
        && rm -rf /var/lib/apt/lists/*

        RUN groupadd -g 1000 user \
        && useradd -d /home/user -m -u 1000 -g 1000 user \
        && chown -R user:user /home/user \
        && mkdir -p /bazel/cache \
        && chown -R user:user /bazel

        RUN echo "build --repository_cache=/bazel/cache">/home/user/.bazelrc
      • Build dockerfile
        1
        docker build -t docker-unit-test .
    • Unit Test code
      • unit-test.yaml
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        schemaVersion: '2.0.0'
        fileExistenceTests:
        - name: 'Check bazel cache folder exists and is owned by the non-root user'
        path: '/bazel/cache'
        shouldExist: true
        uid: 1000
        gid: 1000
        isExecutableBy: 'group'
        fileContentTests:
        - name: 'Validate cache folder config'
        path: '/home/user/.bazelrc'
        expectedContents: ['.*build --repository_cache=/bazel/cache.*']
      • Run the tests
        1
        container-structure-test test --image docker-unit-test --config unit-test.yaml

    Automate testing

    • Automation with Ansible
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      - name: unit test Docker Image
      shell: |
      container-structure-test test --image {{ docker_image }} --config {{ test_file }}
      if $?
      then
      echo "Test Failed"
      exit 1
      else
      echo "Test Succeeded"
      exit 0
      fi

    General parameters

    Parameter Description
    -v, --verbose verbose mode (more output)
    -q, --quiet quiet mode (less output)
    -h, --help help
    -V, --version display version info
    --isolated ignore environment variables and user config
    --log PATH log file
    --proxy PROXY USER:PSWD@SERVER:PORT
    --retries X retry connection X times
    --timeout SECONDS try for X seconds before retry
    --cache-dir DIR cache directory
    --no-cache-dir disable cache
    --disable-pip-version-check do not check Pip version
    --cert PATH path to secondary CA bundle
    --client-cert CERT path to SSL certificate
    --trusted-host HOSTNAME consider the host trusted

    List and search

    List

    Command Description
    pip list list packages
    pip list -o, pip list --outdated list outdated packages
    -u, --uptodate list current packages
    -e, --editable list editable items
    -l, --local list local virtualenv packages
    --user list user-site packages
    --pre include developmental packages
    -i URL, --index-url URL PyPI URL
    --extra-index-url URL additional package repos
    --no-index ignore package index
    -f URL, --find-links URL search for archives at the URL
    --allow-external PKG allow package installation
    --allow-all-external allow installing externally hosted packages
    --allow-unverified PKG install insecure package
    --process-dependency-links process links for dependencies

    Show

    Command Description
    pip show PKG display package info
    pip show -f, pip show --files list package’s files
    Command Description
    pip search KEYWORD search PyPI for keyword
    pip search --index URL repository to search

    Install, Freeze and uninstall

    Install

    Command Description
    pip install PKG install package
    pip install PKG==1.0 install specific version
    pip install 'PKG>=1.0' at least, install version X
    pip install -r FILE, pip install --requirement FILE install listed packages in the requirements file
    pip install -b DIR, pip install --build DIR directory for building packages
    pip install -t DIR, pip install --target DIR install in directory
    pip install -d DIR, pip install --download DIR download only
    pip install -U, pip install --upgrade update listed packages
    pip install --force-reinstall re-install packages when updating
    pip unstall -I, pip install --ignore-installed re-install
    pip install --no-deps do not install dependencies
    pip install --egg install as an Egg
    pip install --compile compile to *.py to *.pyc
    pip install --no-compile do not compile
    pip install --no-use-wheel do not use wheels
    pip install --pre include developmental versions
    pip install --no-clean do not clean build directories
    pip install -i URL, pip install --index-url URL get PyPI URL
    pip install --extra-index-url URL get additional URLs
    pip install --no-index only use --find-links URLs
    pip install -f URL, pip install --find-links URL parse links for archives
    pip install --allow-external PKG install a 3rd-party package
    pip install --allow-all-external install 3rd-party packages
    pip install --allow-unverified PKG install unverified package
    pip install --process-dependency-links process links for dependencies

    Freeze

    Command Description
    pip freeze generate requirements file
    pip freeze -r FILE, pip freeze --requirement FILE use the order given in the file
    pip freeze -f URL, pip freeze --find-links URL URL for finding packages
    pip freeze -l, pip freeze --local only list virtualenv packages
    pip freeze --user only list user-site packages

    Uninstall

    Command Description
    pip uninstall PKG uninstall/remove package
    pip unistall -r FILE, pip unistall -requirement FILE uninstall packages listed in requirements file
    pip uninstall -y, pip uninstall --yes assume “yes” for questions

    Definition

    • Temporary adjustment to how Python runs code.
    • It is not a virtual machine, nor a container.
    • It manipulates the environment variables of your shell so that you can execute one known version of Python using a local set of modules.

    Creation

    Command Description
    venv ENV_DIR create in ENV_DIR path to directory
    venv --system-site-packages ENV_DIR give the venv access to the system site-packages dir
    venv --symlinks try to use symlinks rather than copies (avoid in Windows)
    venv --copies ENV_DIR try to use copies rather than symlinks
    venv --clear ENV_DIR delete the contents of the environment directory
    venv --upgrade ENV_DIR upgrade the environment directory to use this version of Python
    venv --without-pip ENV_DIR skips installing or upgrading pip in the virtual environment
    venv --prompt PROMPT provides an alternative prompt prefix for this environment

    Activation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # no virtual environment
    $ python --version
    Python 2.7.17

    # virtual environment
    $ python3 -m venv myvirtualenv
    #creates a virtual environment called myvirtualenv
    $ source ./venv/bin/activate
    # activates the virtual environment
    (myvirtualenv)$ python --version
    Python 3.7
    # check we have python 3 active
    (myvirtualenv)$ deactivate

    # no virtual environment
    $ python --version
    Python 2.7.17

    Using modules

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # no virtual environment
    $ python3
    >>> import flask
    ModuleNotFoundError: No module named 'flask'

    # virtual environment
    (venv)$ python -m pip install flask
    [...]
    (venv) bash-4.3$ python -m pip install flask
    Collecting flask
    Downloading [...]
    Successfully installed [...]
    (venv)$ python
    >>> from flask import Flask
    >>> app = Flask(__name__)
    (venv)$ deactivate

    # no virtual environment again
    $ python3
    >>> import flask
    ModuleNotFoundError: No module named 'flask'

    Storing a Virtual Environment configuration

    Get the installed packages list and pipe it into the requirements.txt file.

    1
    pip freeze > requirements.txt

    Restoring a Virtual Environment

    The virtual environment system encourages you to keep track of the modules you’re using in a requirements.txt file within your project directory.
    You can process requirements.txt with pip to handle automated installs of all dependencies:

    1
    2
    3
    4
    5
    6
    7
    $ cd myproject
    $ source ./venv/bin/activate
    (venv)$ python -m pip install -r requirements.txt
    Installed [...]
    $ python
    >>> from flask import Flask
    >>> import examplepymod

    Cheatsheet

    Description Command
    Create a virtual environment python -m venv /directory/venv
    Activate a virtual environment source ./venv/bin/activate
    Install required modules (venv)$ python -m pip install -r requirements.txt
    Deactivate a virtual environment (venv)$ deactivate
    Store an environment configuration pip freeze > requirements.txt
    0%