Testing bash scripts with Jenkins
Dec 8, 2020
--
Bash Scripts Testing 656x400

Testing bash scripts with Jenkins

At Magnolia, we use Jenkins and AWS EC2 instances to run our builds. We mostly do Maven builds that leverage JUnit 4/5 for both unit and IT/UI testing. For the latter, we also use Docker and Selenium to spin up instances and run test scenarios.

Before we can use these instances, they need to download utilities, settings, and a couple of bash scripts. We call those scripts the ‘aws-build-scripts’. They are critical to our development workflow and must follow development best practices. This is why we need to validate them. In today's blog, we'll share how we wrote a test suite with a minimalistic setup.

Evaluating testing frameworks

For sure, there are dedicated libraries such as shUnit2 or Bats out there. These are the solutions we ran into immediately. But our use case was only about improving the stability of our builds. Investing in a 3rd-party library would have complicated the setup as fun as that could have been. It would also have made it more challenging for a colleague to solve an issue without prior knowledge of these frameworks. We decided it wasn't worth it.

Magnolia Features

Magnolia allows you to manage all your content and media in one place, and create personalized experiences across multiple channels. See an overview of key capabilities and benefits.

Defining a simple test convention

We didn’t do anything until we ran into issues that we couldn’t explain easily. This is when we realized that we needed some sort of test that is triggered with every change. We thought: “Wait, we're only one Jenkinsfile away from this!” Because Jenkins automatically scans our Bitbucket repositories, we can use it solve our problem. We didn’t need Jenkins to build anything in this case, but we could use it to run tests.

We then came up with a Jenkinsfile convention to run simple tests:

Java
  stage('NAME_OF_THE_BASH_FILE') {
  stages {
    stage('FIRST_TEST_DESCRIPTION') {
      steps {
        script {
          // GIVEN
          // here it's possible to setup and assess the test's preliminary state, for instance:
          sh "docker volume create test-volume"
          def volumes = callSh("docker volume ls --filter 'dangling=true'")
          assert volumes.contains("test-volume")

          // WHEN
          sh "./NAME_OF_THE_BASH_FILE"
    
          // THEN
          // wrap up the test, for instance:
          volumes = callSh("docker volume ls --filter 'dangling=true'")
          assert !volumes.contains("test-volume")
        }
      }
    }

    stage('SECOND_TEST_DESCRIPTION') {
      …
    }
  }
}

callSh() comes from a file named callSh.groovy which has the following content:

Java
  def call(command) {
  return sh(script: command, returnStdout: true).trim()
}

For example, to clean up and remove any Docker volumes from the machine for the next build we run this test:

Java
  stage('Removes dangling volumes') {
  steps {
    script {
      // GIVEN
      sh "docker volume create test-volume"
      def volumes = callSh("docker volume ls --filter 'dangling=true'")
      assert volumes.contains("test-volume")

      // WHEN
      sh "./docker-clean-up.sh"

      // THEN
      volumes = callSh("docker volume ls --filter 'dangling=true'")
      assert !volumes.contains("test-volume")
    }
  }
}

The 'GIVEN, WHEN, THEN' convention and the Groovy (Java) syntax make it easy for all our developers to get familiar with the test suite.

The use case can even be extended when leveraging other functionality of Jenkins pipelines. For instance, making tests optional can be achieved with the when clause like this:

Java
  stage('Leaves already mounted volume') {
  when {
    expression {
      return callSh("df").contains("/home/ubuntu/ec2")
    }
  }
  …

That’s it: a successful example of bash script testing made simple. You can have a look at the full test suite here. Have fun replicating the setup or tweaking it to fit your needs!

About the author

Maxime Michel

Site Reliability Engineer, Magnolia

Half developer, half Site Reliability Engineer (SRE), Maxime contributes to Magnolia’s source code and works closely with the wider product development team. He believes that machines can take care of repetitive tasks so that people can focus on more creative and interesting work. In his role at Magnolia, Maxime is responsible for processes and automation to present our users with a simple solution and a great user experience.