CI/CD הפך לסטנדרט בפיתוח מודרני. במאמר זה נלמד כיצד לשלב בדיקות בצורה אפקטיבית ב-pipeline.
מה זה CI/CD?
CI (Continuous Integration):
- מיזוג קוד תכוף (מספר פעמים ביום)
- Build אוטומטי
- הרצת בדיקות אוטומטית
- משוב מהיר למפתחים
CD (Continuous Delivery/Deployment):
- Deployment אוטומטי לסביבות שונות
- Release process מהיר ואמין
- Rollback אוטומטי במקרה של בעיה
יתרונות CI/CD לבדיקות
- משוב מהיר - תוצאות תוך דקות
- זיהוי מוקדם - באגים מתגלים מהר
- איכות גבוהה - בדיקות רצות באופן עקבי
- אוטומציה מלאה - הפחתת טעויות אנוש
- תיעוד אוטומטי - היסטוריה מלאה של builds ובדיקות
ארכיטקטורת Pipeline
Pipeline טיפוסי
┌─────────────┐
│ Commit │
└──────┬──────┘
│
▼
┌─────────────┐
│ Build │
└──────┬──────┘
│
▼
┌─────────────┐
│ Unit Tests │
└──────┬──────┘
│
▼
┌─────────────┐
│ SAST Scan │
└──────┬──────┘
│
▼
┌─────────────┐
│Integration │
│ Tests │
└──────┬──────┘
│
▼
┌─────────────┐
│ Deploy │
│ to Dev │
└──────┬──────┘
│
▼
┌─────────────┐
│ E2E │
│ Tests │
└──────┬──────┘
│
▼
┌─────────────┐
│ Deploy to │
│ Staging │
└──────┬──────┘
│
▼
┌─────────────┐
│ Deploy to │
│ Production │
└─────────────┘
דוגמאות מעשיות
GitHub Actions
name: CI/CD Pipelineon:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run Unit Tests
run: |
pytest tests/unit --cov=src --cov-report=xml
- name: Run Integration Tests
run: |
pytest tests/integration
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: true
- name: Security Scan
run: |
pip install bandit
bandit -r src/
e2e-tests:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up test environment
run: |
docker-compose up -d
sleep 10 # Wait for services
- name: Run E2E Tests
run: |
pytest tests/e2e --html=report.html
- name: Upload Test Report
if: always()
uses: actions/upload-artifact@v3
with:
name: test-report
path: report.html
- name: Cleanup
if: always()
run: docker-compose down
deploy:
needs: e2e-tests
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to Production run: | # Deployment script here echo "Deploying to production..."
GitLab CI
stages:
- build
- test
- security
- deploy
variables:
DOCKER_DRIVER: overlay2
build:
stage: build
script:
- pip install -r requirements.txt
- python setup.py build
artifacts:
paths:
- build/
expire_in: 1 hour
unit_tests:
stage: test
script:
- pytest tests/unit -v --junitxml=report.xml
artifacts:
reports:
junit: report.xml
integration_tests:
stage: test
services:
- postgres:13
- redis:6
variables:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
script:
- pytest tests/integration -v
security_scan:
stage: security
script:
- pip install safety
- safety check
- bandit -r src/
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging..."
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy_production:
stage: deploy
script:
- echo "Deploying to production..."
- ./deploy.sh production environment: name: production url: https://example.com when: manual only:
- main
Jenkins Pipeline
pipeline {
agent any
environment {
PYTHON_VERSION = '3.10'
TEST_ENV = 'ci'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh '''
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
'''
}
}
stage('Unit Tests') {
steps {
sh '''
. venv/bin/activate
pytest tests/unit --junitxml=results.xml
'''
}
post {
always {
junit 'results.xml'
}
}
}
stage('Integration Tests') {
steps {
sh '''
. venv/bin/activate
pytest tests/integration -v
'''
}
}
stage('Security Scan') {
parallel {
stage('SAST') {
steps {
sh 'bandit -r src/'
}
}
stage('Dependency Check') {
steps {
sh 'safety check'
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh './deploy.sh staging'
}
}
stage('E2E Tests') {
when {
branch 'develop'
}
steps {
sh '''
. venv/bin/activate
pytest tests/e2e --html=report.html
'''
}
post {
always {
publishHTML([
reportDir: '.',
reportFiles: 'report.html',
reportName: 'E2E Test Report'
])
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
input {
message "Deploy to production?"
ok "Deploy"
}
steps {
sh './deploy.sh production'
}
}
}
post {
failure {
emailext(
subject: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "Check ${env.BUILD_URL} for details",
to: "team@example.com"
)
}
success {
echo 'Pipeline completed successfully!'
}
}
}
Best Practices
1. פירמידת הבדיקות
/\ /E2E\ / \ /-------\ /Integration\ / \ /--------------\ / Unit Tests \ /__________________\
70% Unit | 20% Integration | 10% E2E
2. שמירה על Pipeline מהיר
- הרצת בדיקות parallel כשאפשר
- שימוש ב-caching
- בדיקות E2E רק על קוד שהשתנה
- Fail fast - עצירה מיידית אם יש כשל
3. ניהול סביבות
# config.py import osENVIRONMENTS = { 'dev': { 'api_url': 'https://dev-api.example.com', 'db_host': 'dev-db.example.com', 'timeout': 30 }, 'staging': { 'api_url': 'https://staging-api.example.com', 'db_host': 'staging-db.example.com', 'timeout': 10 }, 'production': { 'api_url': 'https://api.example.com', 'db_host': 'prod-db.example.com', 'timeout': 5 } }
def get_config(): environment = os.getenv('TEST_ENV', 'dev') return ENVIRONMENTS[environment]
4. Test Data Management
# conftest.py import pytest@pytest.fixture(scope="session") def test_data(): # Setup test data data = create_test_data() yield data # Cleanup cleanup_test_data(data)
@pytest.fixture(autouse=True) def reset_database(): # Reset state before each test reset_db_state()
5. Notifications ו-Reporting
הודעות חכמות:
- התראה רק על כשלים
- סיכום יומי של builds
- התראות ל-Slack/Teams
- דשבורד ריכוזי
Metrics חשובים
- Build Success Rate - אחוז ה-builds המצליחים
- Mean Time to Detect (MTTD) - זמן ממוצע לזיהוי בעיה
- Mean Time to Repair (MTTR) - זמן ממוצע לתיקון
- Test Coverage - אחוז כיסוי הקוד
- Deployment Frequency - תדירות ההעלאות לפרודקשן
- Change Failure Rate - אחוז השינויים שגורמים לכשל
כלים נוספים
Containerization
# Dockerfile for test environment FROM python:3.10-slimWORKDIR /app
COPY requirements.txt . RUN pip install -r requirements.txt
COPY . .
CMD ["pytest", "tests/", "-v"]
Docker Compose לבדיקות
version: '3.8'services:
app:
build: .
depends_on:
- db
- redis
environment:
- DATABASE_URL=postgresql://test:test@db:5432/testdb
- REDIS_URL=redis://redis:6379
db:
image: postgres:13
environment:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
redis:
image: redis:6-alpine
Troubleshooting נפוץ
בעיה: Tests עוברים לוקלית אבל נכשלים ב-CI
פתרונות:
- ודאו סביבה זהה (dependencies, versions)
- בדקו environment variables
- השתמשו ב-Docker לעקביות
בעיה: Pipeline איטי מדי
פתרונות:
- הרצה parallel של tests
- Caching של dependencies
- הקטנה של test data
- שימוש ב-faster runners
סיכום
CI/CD טוב משפר את איכות התוכנה, מזרז את הפיתוח ומפחית סיכונים. השקיעו זמן בהקמה נכונה מההתחלה.
המפתח להצלחה: התחילו פשוט, למדו ושפרו בהדרגה.