Integrating Tests into Your CI/CD Pipeline

Integrating Tests into Your CI/CD Pipeline

Practical approaches to automated testing in continuous delivery workflows.

devops testing ci-cd automation

Integrating Tests into Your CI/CD Pipeline

Automated testing in CI/CD pipelines is essential for maintaining code quality and shipping with confidence. This guide covers practical strategies for integrating different types of tests into your continuous delivery workflow.

The Testing Pyramid in CI/CD

Unit Tests (Base)

  • Fast execution (milliseconds)
  • Run on every commit
  • High code coverage
  • Isolate business logic

Integration Tests (Middle)

  • Test component interactions
  • Database, API, and service integration
  • Run on pull requests
  • Moderate execution time

End-to-End Tests (Top)

  • Test critical user flows
  • Run on staging/pre-production
  • Slowest but most comprehensive
  • Focus on happy paths and critical journeys

CI/CD Platforms

GitHub Actions Example

name: CI Pipeline

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm test

      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

      - name: Run E2E tests
        run: npm run test:e2e

      - name: Upload coverage
        uses: codecov/codecov-action@v3

GitLab CI Example

stages:
  - test
  - build
  - deploy

unit-test:
  stage: test
  script:
    - npm ci
    - npm test
  coverage: '/Statements\s*:\s*(\d+\.\d+)%/'

integration-test:
  stage: test
  services:
    - postgres:14
  script:
    - npm run test:integration
  only:
    - merge_requests
    - main

e2e-test:
  stage: test
  script:
    - npm run test:e2e
  only:
    - main

Best Practices

1. Fail Fast

Run quick tests first:

# Run linting and unit tests before expensive operations
- Lint
- Unit tests
- Integration tests
- E2E tests
- Build
- Deploy

2. Parallel Execution

Split tests across multiple runners:

test:
  strategy:
    matrix:
      shard: [1, 2, 3, 4]
  steps:
    - run: npm test -- --shard=${{ matrix.shard }}/4

3. Caching Dependencies

Speed up pipeline with caching:

- name: Cache node modules
  uses: actions/cache@v3
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}

4. Test Different Environments

test:
  strategy:
    matrix:
      node-version: [18.x, 20.x, 21.x]
      os: [ubuntu-latest, windows-latest, macos-latest]

Performance Testing in CI/CD

Lighthouse CI

- name: Run Lighthouse CI
  run: |
    npm install -g @lhci/cli
    lhci autorun

lighthouserc.json:

{
  "ci": {
    "collect": {
      "startServerCommand": "npm run preview",
      "url": ["http://localhost:4173"]
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", {"minScore": 0.9}],
        "categories:accessibility": ["error", {"minScore": 0.9}]
      }
    }
  }
}

Load Testing with k6

- name: Run load tests
  run: |
    docker run --rm -v $(pwd):/scripts grafana/k6 run /scripts/load-test.js

Handling Flaky Tests

  1. Retry Mechanism:
- name: Run tests with retry
  uses: nick-invision/retry@v2
  with:
    timeout_minutes: 10
    max_attempts: 3
    command: npm run test:e2e
  1. Quarantine Flaky Tests:
// Mark as known flaky
test.skip('flaky test', () => {
  // Quarantined until fixed
});
  1. Monitor Test Stability: Track pass/fail rates over time to identify patterns.

Code Quality Gates

Coverage Requirements

- name: Check coverage
  run: |
    npm test -- --coverage
    npx nyc check-coverage --lines 80 --functions 80 --branches 80

Blocking Merge on Failures

required_status_checks:
  strict: true
  contexts:
    - unit-tests
    - integration-tests

Security Testing

Dependency Scanning

- name: Run npm audit
  run: npm audit --audit-level=moderate

- name: Snyk security scan
  uses: snyk/actions/node@master
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Conclusion

A well-designed CI/CD testing strategy catches bugs early, maintains code quality, and enables confident deployments. Start with a solid foundation of unit tests, gradually add integration and E2E tests, and continuously optimize your pipeline for speed and reliability.


Need help setting up your CI/CD pipeline? Reach out.