Skip to main content
GitLab CI
CHAPTER 05

Automated Testing with GitLab CI

Updated: May 15, 2026
25 min read

# CHAPTER 5

Automated Testing with GitLab CI

1. Introduction

A CI/CD pipeline that merely compiles code and deploys it is not a DevOps pipeline; it is just an automated disaster delivery system. The absolute core value of Continuous Integration is the Quality Gate—the automated assurance that broken code never reaches production. In this chapter, we will transform our pipeline from a simple script execution engine into a rigorous automated testing facility. We will focus on integrating unit tests (specifically PHPUnit) into our .gitlab-ci.yml configuration, teaching GitLab how to pass or fail a pipeline based on the mathematical results of our code tests.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand the role of automated Unit Testing in CI/CD.
  • Configure a GitLab CI job to execute a testing framework.
  • Understand how Exit Codes determine job success or failure.
  • Implement PHPUnit testing within a GitLab pipeline.
  • Read and interpret automated test logs in the GitLab UI.

3. Beginner Explanation

Imagine an assembly line building smartphones.
  • Without Testing: The robot puts the screen on the phone, puts the phone in a box, and ships it to the customer. The customer opens it, turns it on, and the screen is dead. The company loses millions in refunds.
  • With Automated Testing (CI): Before the phone goes in the box, a robotic arm plugs a cable into it and runs a diagnostic check. Does the screen turn on? Yes. Do the speakers work? No. The robot instantly throws the phone into the "Defective" bin and sounds an alarm. The phone never reaches the customer.

In software, Unit Tests are the diagnostic checks. GitLab CI is the robotic arm throwing the broken code into the defective bin.

4. How GitLab Knows a Test Failed (Exit Codes)

How does GitLab actually know if your tests passed or failed? It relies on standard Linux Exit Codes. When a terminal command finishes running:
  • If it was completely successful, it sends a hidden 0 to the operating system (Exit Code 0).
  • If an error occurred, it sends a number greater than 0 (e.g., Exit Code 1).

GitLab CI watches these codes. If your testing script outputs Exit Code 0, GitLab turns the pipeline Green (Passed). If your script outputs Exit Code 1, GitLab turns the pipeline Red (Failed) and instantly aborts the rest of the pipeline.

5. Integrating PHPUnit into the Pipeline

Let's look at how to run a standard PHPUnit test suite inside our test stage. To do this correctly, we must use a Docker image that already has PHP installed.
yaml
1234567891011121314151617
stages:
  - test

run_phpunit_tests:
  stage: test
  # Use a Docker container that has PHP 8.2 pre-installed
  image: php:8.2-cli
  
  # Before the test runs, install the required dependencies
  before_script:
    - apt-get update -y && apt-get install -y unzip zip
    - curl -sS https://getcomposer.org/installer | php
    - php composer.phar install

  # Run the actual tests
  script:
    - ./vendor/bin/phpunit tests/

6. Mini Project: Automate PHP Tests

Let's simulate failing and fixing a test pipeline.

Step-by-Step Walkthrough:

  1. 1. Imagine you have a simple PHP project with a test that asserts 1 + 1 equals 2.
  1. 2. You commit your .gitlab-ci.yml (using the code from Section 5).
  1. 3. You push to GitLab. The pipeline runs, PHPUnit executes, and the math is correct (Exit Code 0). The pipeline turns Green.
  1. 4. The Break: You edit your PHP code. You accidentally change the math logic so the application thinks 1 + 1 equals 3.
  1. 5. You commit and push.
  1. 6. The pipeline triggers. PHPUnit runs the test. The test fails (1+1 != 3). PHPUnit throws an Error (Exit Code 1).
  1. 7. GitLab detects Exit Code 1. It instantly terminates the pipeline, turns the UI Red, and sends an email to the developer saying: "Pipeline Failed." The defective code is stopped dead in its tracks.

7. Test Artifacts

When a test fails, you want to know *why*. Sometimes tests generate coverage reports (HTML files showing exactly which lines of code were tested). You can tell GitLab to save these files using Artifacts so you can download them from the UI after the pipeline finishes.
yaml
12345678
run_phpunit_tests:
  stage: test
  script:
    - ./vendor/bin/phpunit --coverage-html coverage/
  artifacts:
    paths:
      - coverage/  # Saves the coverage folder to GitLab so you can download it
    expire_in: 1 week # Deletes it after a week to save server space

8. Best Practices

  • Test Database Isolation: Never run automated tests against your live production database! When testing in CI, you should configure your application to use a temporary, lightweight database (like SQLite in-memory) or spin up a temporary database container specifically for the pipeline using GitLab Services. The database should be destroyed the moment the pipeline finishes.

9. Common Mistakes

  • Ignoring the beforescript: Developers often write script: phpunit and wonder why the pipeline fails immediately with "command not found". A fresh GitLab CI Docker container is completely blank. It doesn't have your dependencies. You MUST use the beforescript to run composer install (or npm install) to download your required libraries before attempting to run the testing framework.

10. Exercises

  1. 1. Explain the mechanism by which GitLab determines if a pipeline Job has succeeded or failed.
  1. 2. What is the operational purpose of saving Test Artifacts in a CI pipeline?

11. FAQs

Q: Can I run Python tests and PHP tests in the same pipeline? A: Absolutely. You simply create two different jobs in the test stage. Give the PHP job image: php:8.2 and give the Python job image: python:3.9. GitLab will spin up two entirely different Docker containers and run them simultaneously in parallel!

12. Summary

In Chapter 5, we fundamentally upgraded our pipeline from a simple script runner to a rigorous Quality Gate. We explored the mechanics of automated testing, understanding that standard Linux Exit Codes dictate the survival or termination of a pipeline branch. By integrating PHPUnit into a containerized environment and managing dependencies via the before_script, we created an impenetrable barrier against defective code. Finally, we utilized Artifacts to preserve diagnostic data, ensuring developers have the intelligence required to quickly debug and resolve failures.

13. Next Chapter Recommendation

We have referenced using specific "images" for our tests. How does GitLab magically spin up completely blank, specialized servers for every single job? Proceed to Chapter 6: Docker and GitLab CI.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·