GitHub Actions Testing - CI/CD测试集成
全面的 GitHub Actions 测试工作流,包括自动化测试、并行执行、测试报告、缓存和现代开发流水线的高级 CI/CD 模式
💻 GitHub Actions 基础测试设置 yaml
🟢 simple
⭐⭐
完整的 GitHub Actions 测试设置,包含基础工作流、测试执行、报告和 CI/CD 集成
⏱️ 40 min
🏷️ github actions, ci cd, setup, workflows
Prerequisites:
Git basics, CI/CD concepts, YAML, JavaScript/TypeScript
# GitHub Actions Testing - Basic Setup and Configuration
# 1. .github/workflows/test.yml - Basic Testing Workflow
name: Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
NODE_VERSION: '18'
NODE_OPTIONS: '--max-old-space-size=4096'
jobs:
# Unit Tests
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Upload coverage reports
if: matrix.node-version == '18'
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
# Integration Tests
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup test environment
run: |
cp .env.test .env
npm run db:migrate
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
REDIS_URL: redis://localhost:6379
# End-to-End Tests
e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build application
run: npm run build
- name: Run E2E tests
run: npm run test:e2e
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
# 2. .github/workflows/quality.yml - Code Quality Checks
name: Code Quality
on:
pull_request:
branches: [ main ]
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Prettier check
run: npm run format:check
- name: Type checking
run: npm run type-check
security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Run security audit
run: npm audit --audit-level=moderate
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# 3. .github/workflows/performance.yml - Performance Testing
name: Performance Tests
on:
schedule:
- cron: '0 2 * * *' # Run daily at 2 AM UTC
workflow_dispatch:
jobs:
lighthouse:
name: Lighthouse Performance Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/[email protected]
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
load-testing:
name: Load Testing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install k6
run: |
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
- name: Run load tests
run: k6 run --out json=results.json tests/performance/load-test.js
- name: Upload results
uses: actions/upload-artifact@v3
with:
name: load-test-results
path: results.json
# 4. package.json scripts configuration
{
"name": "example-project",
"version": "1.0.0",
"scripts": {
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
"test:unit": "jest",
"test:unit:watch": "jest --watch",
"test:unit:coverage": "jest --coverage",
"test:integration": "jest --config=jest.integration.config.js",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:debug": "playwright test --debug",
"lint": "eslint src/**/*.js tests/**/*.js",
"lint:fix": "eslint src/**/*.js tests/**/*.js --fix",
"format": "prettier --write src/**/* tests/**/*",
"format:check": "prettier --check src/**/* tests/**/*",
"type-check": "tsc --noEmit",
"build": "tsc && npm run build:assets",
"build:assets": "cp -r src/public dist/",
"dev": "npm run build && node dist/index.js",
"db:migrate": "sequelize-cli db:migrate",
"db:seed": "sequelize-cli db:seed:all"
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@lhci/cli": "^0.12.0",
"eslint": "^8.55.0",
"prettier": "^3.1.0",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"snyk": "^1.1291.0"
}
}
# 5. .github/workflows/notify.yml - Test Result Notifications
name: Test Notifications
on:
workflow_run:
workflows: ["Tests"]
types:
- completed
jobs:
notify:
name: Send Notifications
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: Send Slack notification
uses: 8398a7/action-slack@v3
with:
status: failure
channel: '#dev-alerts'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
text: |
Tests failed for ${{ github.repository }}
Branch: ${{ github.ref }}
Commit: ${{ github.sha }}
View details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Send email notification
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: "Test Failed - ${{ github.repository }}"
body: |
Tests failed for ${{ github.repository }}
Branch: ${{ github.ref }}
Commit: ${{ github.sha }}
Author: ${{ github.event.head_commit.author.name }}
View details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
to: ${{ secrets.NOTIFICATION_EMAIL }}
from: GitHub Actions
# 6. .github/workflows/release.yml - Release Testing
name: Release
on:
release:
types: [published]
jobs:
release-tests:
name: Pre-release Testing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run full test suite
run: npm run test
- name: Build release
run: npm run build
- name: Run smoke tests
run: npm run test:smoke
- name: Upload release artifacts
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./dist/app.zip
asset_name: app-v${{ github.event.release.tag_name }}.zip
asset_content_type: application/zip
# 7. jest.config.js - Jest Configuration for CI
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/__tests__/**/*.+(ts|tsx|js)',
'**/*.(test|spec).+(ts|tsx|js)'
],
transform: {
'^.+\.(ts|tsx)$': 'ts-jest',
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.ts',
],
coverageDirectory: 'coverage',
coverageReporters: [
'text',
'lcov',
'html',
'json'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
testTimeout: 30000,
};
💻 高级 GitHub Actions 测试模式 yaml
🔴 complex
⭐⭐⭐⭐
复杂的 CI/CD 模式,包括矩阵构建、缓存策略、测试并行化、条件执行和企业级工作流优化
⏱️ 90 min
🏷️ github actions, advanced, matrix, caching, optimization
Prerequisites:
GitHub Actions basics, CI/CD advanced, YAML, DevOps concepts
# GitHub Actions Advanced Testing Patterns
# 1. .github/workflows/matrix-testing.yml - Advanced Matrix Strategy
name: Matrix Testing
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Matrix strategy for multiple environments
test-matrix:
name: Test (${{ matrix.os }}, Node ${{ matrix.node-version }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18, 20]
include:
- os: ubuntu-latest
node-version: 20
coverage: true
- os: windows-latest
node-version: 18
edge-cases: true
- os: macos-latest
node-version: 16
browser-tests: true
exclude:
- os: windows-latest
node-version: 16
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.node-version }}-
- name: Install browsers
if: matrix.browser-tests
run: npx playwright install chromium firefox webkit
- name: Run unit tests
run: npm run test:unit
- name: Run tests with coverage
if: matrix.coverage
run: npm run test:coverage
- name: Run edge case tests
if: matrix.edge-cases
run: npm run test:edge-cases
- name: Run browser tests
if: matrix.browser-tests
run: npm run test:browser
- name: Upload coverage to Codecov
if: matrix.coverage && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
flags: ${{ matrix.os }}-node${{ matrix.node-version }}
name: codecov-${{ matrix.os }}-${{ matrix.node-version }}
# Parallel execution with test splitting
parallel-tests:
name: Parallel Tests (Shard ${{ matrix.shard }}/${{ strategy.job-total }})
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Split tests
id: split-tests
run: |
# Get total number of tests and split them
TEST_FILES=$(npm run test:list 2>/dev/null | grep -E "\.test\.|\.spec\." | wc -l)
echo "total-tests=$TEST_FILES" >> $GITHUB_OUTPUT
# Calculate which tests to run for this shard
SHARD_SIZE=$((TEST_FILES / 4 + 1))
START=$(((matrix.shard - 1) * SHARD_SIZE + 1))
END=$((matrix.shard * SHARD_SIZE))
echo "start=$START" >> $GITHUB_OUTPUT
echo "end=$END" >> $GITHUB_OUTPUT
- name: Run shard tests
run: |
npm run test:shard -- --shard=${{ matrix.shard }}/4
- name: Upload test results
uses: actions/upload-artifact@v3
with:
name: test-results-shard-${{ matrix.shard }}
path: test-results/
# 2. .github/workflows/conditional-testing.yml - Conditional Execution
name: Conditional Testing
on:
push:
branches: [ main ]
pull_request:
types: [opened, synchronize, reopened]
jobs:
detect-changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
backend-changed: ${{ steps.changes.outputs.backend }}
frontend-changed: ${{ steps.changes.outputs.frontend }}
docs-changed: ${{ steps.changes.outputs.docs }}
all-changed: ${{ steps.changes.outputs.all }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Detect file changes
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
backend:
- 'src/backend/**'
- 'package.json'
frontend:
- 'src/frontend/**'
- 'public/**'
docs:
- 'docs/**'
- '**/*.md'
all:
- '**'
# Conditional job execution
backend-tests:
name: Backend Tests
needs: detect-changes
if: needs.detect-changes.outputs.backend-changed == 'true'
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run backend tests
run: |
npm run test:backend
npm run test:api
- name: Run integration tests
run: npm run test:integration
frontend-tests:
name: Frontend Tests
needs: detect-changes
if: needs.detect-changes.outputs.frontend-changed == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build application
run: npm run build:frontend
- name: Run frontend tests
run: npm run test:frontend
- name: Run E2E tests
run: npm run test:e2e
full-suite:
name: Full Test Suite
needs: detect-changes
if: |
needs.detect-changes.outputs.all-changed == 'true' ||
github.event_name == 'schedule' ||
contains(github.event.head_commit.message, '[full-test]')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run complete test suite
run: npm run test:all
# 3. .github/workflows/caching-optimization.yml - Advanced Caching
name: Optimized Testing with Caching
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test-with-caching:
name: Tests with Advanced Caching
runs-on: ubuntu-latest
env:
CACHE_VERSION: v2
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Cache node modules
id: cache-node
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ env.CACHE_VERSION }}-npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ env.CACHE_VERSION }}-npm-${{ runner.os }}-
- name: Install dependencies
if: steps.cache-node.outputs.cache-hit != 'true'
run: npm ci
- name: Cache build artifacts
id: cache-build
uses: actions/cache@v3
with:
path: |
dist/
.next/
build/
key: ${{ env.CACHE_VERSION }}-build-${{ github.sha }}
restore-keys: |
${{ env.CACHE_VERSION }}-build-
- name: Build application
if: steps.cache-build.outputs.cache-hit != 'true'
run: npm run build
- name: Cache Playwright browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ env.CACHE_VERSION }}-playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Cache test database
uses: actions/cache@v3
with:
path: |
test-data/
fixtures/
key: ${{ env.CACHE_VERSION }}-test-data-${{ hashFiles('test-data/**') }}
- name: Setup test database
run: |
npm run db:setup
npm run db:seed
- name: Run tests
run: npm run test
- name: Cache test results
uses: actions/cache@v3
with:
path: |
coverage/
test-results/
playwright-report/
key: ${{ env.CACHE_VERSION }}-test-results-${{ github.sha }}
# 4. .github/workflows/reusable-workflows.yml - Reusable Workflows
# This file defines reusable workflows that can be called from other workflows
# .github/workflows/test-reusable.yml
name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '18'
test-type:
required: false
type: string
default: 'all'
cache-key:
required: false
type: string
default: 'default'
secrets:
NPM_TOKEN:
required: false
CODECOV_TOKEN:
required: false
jobs:
run-tests:
name: Tests (${{ inputs.test-type }})
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Configure private registry
if: secrets.NPM_TOKEN
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc
echo "@example:registry=https://npm.example.com" >> ~/.npmrc
- name: Run tests based on type
run: |
case "${{ inputs.test-type }}" in
"unit")
npm run test:unit
;;
"integration")
npm run test:integration
;;
"e2e")
npm run test:e2e
;;
"all")
npm run test
;;
esac
- name: Upload coverage
if: inputs.test-type != 'e2e' && secrets.CODECOV_TOKEN
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: ${{ inputs.test-type }}
# .github/workflows/main.yml - Using reusable workflow
name: Main Testing Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
unit-tests:
uses: ./.github/workflows/test-reusable.yml
with:
node-version: '18'
test-type: 'unit'
cache-key: 'unit-tests'
integration-tests:
uses: ./.github/workflows/test-reusable.yml
with:
node-version: '18'
test-type: 'integration'
cache-key: 'integration-tests'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
e2e-tests:
uses: ./.github/workflows/test-reusable.yml
with:
node-version: '20'
test-type: 'e2e'
cache-key: 'e2e-tests'
# 5. .github/workflows/test-metrics.yml - Test Metrics and Reporting
name: Test Metrics and Reporting
on:
workflow_run:
workflows: ["Tests"]
types:
- completed
jobs:
generate-report:
name: Generate Test Report
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion != 'skipped' }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: test-results
path: ./test-results
- name: Generate test report
run: |
# Create comprehensive test report
node scripts/generate-test-report.js
- name: Update PR comment with results
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('./test-report.json', 'utf8'));
const comment = `## Test Results
**Total Tests:** ${report.total}
**Passed:** ${report.passed} ✅
**Failed:** ${report.failed} ❌
**Skipped:** ${report.skipped} ⏭️
**Coverage:** ${report.coverage}%
[View detailed report](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
- name: Update test metrics
run: |
# Update metrics in external system
node scripts/update-metrics.js
- name: Create test summary
id: test-summary
run: |
# Create markdown summary
echo "summary<<EOF" >> $GITHUB_OUTPUT
cat test-summary.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comment on PR
uses: peter-evans/create-or-update-comment@v3
if: github.event_name == 'pull_request'
with:
issue-number: ${{ github.event.pull_request.number }}
body: ${{ steps.test-summary.outputs.summary }}