The Pipeline: Install Dependencies, Run Pytest, Publish Report

The author created a small shopping-cart calculator with 10 tests (happy paths, error paths, parametrized cases) and implemented the same automation in three CI/CD tools. The pipeline does three things: install dependencies from requirements.txt, run pytest test_calculator.py -v --junitxml=report.xml, and publish the JUnit report.

Round 1: GitHub Actions

name: Tests (GitHub Actions)
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest test_calculator.py -v --junitxml=report-${{ matrix.python-version }}.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-report-${{ matrix.python-version }}
          path: report-${{ matrix.python-version }}.xml

Key points:

  • Matrix strategy tests two Python versions in parallel with four extra lines.
  • Reusable actions (actions/setup-python) replace manual scripting.
  • The pipeline runs live in the repo on every push.

Round 2: GitLab CI

stages:
  - test

.test_template: &test_template
  stage: test
  before_script:
    - pip install -r requirements.txt
  script:
    - pytest test_calculator.py -v --junitxml=report.xml
  artifacts:
    when: always
    reports:
      junit: report.xml

test:python3.11:
  <<: *test_template
  image: python:3.11

test:python3.12:
  <<: *test_template
  image: python:3.12

Key points:

  • Jobs declare a Docker image directly; no setup step needed.
  • reports: junit is built into the platform; failed tests appear annotated in the merge request UI.
  • Version matrix requires YAML anchors (&template/<<:) — more manual than Actions' matrix.

Round 3: Jenkins (Jenkinsfile)

pipeline {
    agent {
        docker { image 'python:3.12' }
    }
    stages {
        stage('Install') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }
        stage('Test') {
            steps {
                sh 'pytest test_calculator.py -v --junitxml=report.xml'
            }
        }
    }
    post {
        always {
            junit 'report.xml'
        }
    }
}

Key points:

  • Uses Groovy DSL, which allows real programming (variables, conditionals, shared libraries).
  • post { always { junit ... } } is Jenkins' classic test-report integration.
  • You must maintain the server, agents, plugins, and upgrades.

The Landscape

The article includes a comparison table of nine CI/CD tools:

ToolHostingConfigPricing (open source)Standout featureWatch out for
GitHub ActionsSaaS (+ self-hosted runners)YAML in repoFree for public reposMarketplace with 20k+ reusable actionsVendor lock-in to GitHub
GitLab CISaaS or self-hostedYAML in repoFree tier + minutesSingle tool: repo, CI, registry, environmentsComplex YAML at scale
JenkinsSelf-hostedJenkinsfile (Groovy)100% free, open sourceTotal control, 1,900+ pluginsYou maintain the server, plugins break
CircleCISaaSYAMLFree tierVery fast, first-class DockerCredits model gets pricey
Travis CISaaSYAMLLimited freeHistoric pioneer for OSSLost most mindshare after pricing changes
TeamCitySelf-hosted or CloudUI or Kotlin DSLFree tierDeep JetBrains IDE integrationHeavier setup
Bitbucket PipelinesSaaSYAMLFree tierNative Jira/Bitbucket integrationOnly useful inside Atlassian stack
TektonKubernetesCRDs (YAML)Free, CNCFCloud-native building blocksSteep learning curve; it's a framework, not a product
HarnessSaaSYAML/UIFree tierAI-assisted pipelines, deployment verificationEnterprise-oriented complexity

What the Comparison Teaches

Every CI tool automates the same four verbs: trigger, environment, steps, report. The differences are dialect and philosophy:

  • GitHub Actions optimizes for reusable building blocks.
  • GitLab CI optimizes for platform integration.
  • Jenkins optimizes for control and extensibility.
  • Tekton expresses them as Kubernetes resources.
  • Bitbucket Pipelines uses Atlassian-flavored YAML.
  • Harness wraps them in an enterprise UI.

Recommendation

  • If your code lives on GitHub, use Actions.
  • If your code lives on GitLab, use GitLab CI.
  • Choose Jenkins when you need on-premise control or heavy customization.
  • Choose Tekton only if your team already breathes Kubernetes.

Conclusion

The question "Which CI tool should I learn?" is less important than understanding the shared model: trigger → environment → steps → report. Learn it once, and every tool becomes a syntax lookup. The full working example is public at github.com/Dayan-18/ci-tools-demo, with GitHub Actions running it live on every push.