Pipeline سبز است، اما سه حالت می‌تواند پشت این رنگ پنهان بماند: Job تست اصلاً ساخته نشده، دستور تست با Exit Code صفر تمام شده با اینکه Assertionها شکست خورده‌اند، یا JUnit در مسیر اشتباه مانده و GitLab هیچ Evidenceی دریافت نکرده است. «اجرای تست در CI» فقط گذاشتن pytest یا npm test داخل یک Job نیست؛ باید بتوانید ثابت کنید کدام کد، در کدام Runtime و با کدام داده اجرا شد، نتیجه چگونه روی وضعیت Job اثر گذاشت و Artifact تا چه زمانی قابل‌ممیزی است.

این راهنما تست خودکار در GitLab CI را به‌صورت تولیدی طراحی می‌کند: Pipeline Policy، مرز اعتماد Runner، ورودی تکرارپذیر، JUnit، Cache و Artifact، Service readiness، اجرای موازی، Retry و عیب‌یابی. اگر تازه با CI/CD، Jenkinsfile و GitLab آشنا می‌شوید، ابتدا آموزش پایه CI/CD تست خودکار را بخوانید؛ مالکیت این مقاله جزئیات عملیاتی GitLab است.

خلاصه اجرایی: جریان قابل‌اعتماد GitLab این زنجیره را حفظ می‌کند: Pipeline Source → Commit → Trusted Runner → Runtime/Lockfile → Test Exit → JUnit/Artifact → Gate/Owner. JUnit نتیجه را در UI نمایش می‌دهد اما Job را Failed نمی‌کند؛ خود دستور تست باید Non-zero خارج شود. Cache شتاب‌دهنده و غیرقطعی است، Artifact شاهد/خروجی است، Service بازبودن Port را با آمادگی کسب‌وکاری یکی نمی‌کند و Retry نباید Failure محصول را سبز کند.

خروجی نهایی این راهنما چیست

در پایان یک فایل .gitlab-ci.yml دارید که:

  • فقط برای Merge Request، شاخه پیش‌فرض، Tag و Schedule تعریف‌شده Pipeline می‌سازد؛
  • Job منسوخ را در صورت Commit جدید Cancel می‌کند؛
  • Dependency را از فایل قفل‌شده و Hashدار نصب می‌کند؛
  • Lint، Unit و Integration را مستقل اجرا می‌کند؛
  • خطاهای زیرساختی را فقط یک‌بار Retry می‌کند، نه شکست Test Script را؛
  • JUnit را حتی هنگام Failure آپلود می‌کند و Retention صریح دارد؛
  • PostgreSQL را در شبکه Job راه می‌اندازد و Readiness را با Probe پروژه بررسی می‌کند؛
  • برای توسعه به DAG، Shard/Matrix، تست انتخابی و Runner ایرانی مرز روشن دارد.

نمونه با Python ۳.۱۳، pytest و PostgreSQL نوشته شده، اما قرارداد آن برای Jest، Vitest، JUnit، Maven، Gradle، .NET یا Go یکسان است: دستور CLI بازتولیدپذیر، Exit Code درست و Report قابل‌پردازش.

مدل ذهنی GitLab CI: هر جزء چه مسئولیتی دارد

جزء پرسش اصلی اشتباه رایج
workflow: rules آیا اصلاً Pipeline ساخته شود؟ ساخت هم‌زمان Branch و MR Pipeline
Job rules کدام Job در Pipeline موجود باشد؟ فرض اینکه Job rule می‌تواند Pipeline منع‌شده را برگرداند
Runner/Executor کد روی چه Trust boundary اجرا شود؟ اجرای MR نامطمئن کنار Secret یا پروژه دیگر
Image Runtime و ابزار دقیق چیست؟ Tag شناور و Runtime متفاوت Local/CI
Service Dependency شبکه‌ای موقت چیست؟ برابرگرفتن Open port با Schema/Readiness
Cache چه دانلودی بین اجراها سریع‌تر شود؟ تکیه correctness به Cache یا Cache poisoning
Artifact چه خروجی/Evidence ذخیره یا به Job بعد منتقل شود؟ ذخیره Secret، PII یا کل dependency tree
Report GitLab کدام فایل ساختاریافته را تفسیر کند؟ تصور اینکه JUnit وضعیت Job را تعیین می‌کند
needs وابستگی واقعی و DAG اجرا چیست؟ انتظار بی‌دلیل برای تمام Stage قبلی
Environment/Resource group کجا و با چه قفل هم‌زمانی تغییر می‌دهیم؟ Parallel deploy روی منبع مشترک

مرجع رسمی syntax فایل GitLab CI/CD منبع حقیقت Keywordها است. نسخه GitLab.com و Self-managed خود را نیز ثبت کنید؛ قابلیت‌ها و History برخی Keywordها بین نسخه‌ها تفاوت دارد.

پیش‌نیازهای واقعی قبل از نوشتن YAML

  1. تست از CLI اجرا شود: همان دستور باید در Clone تمیز، بدون IDE و بدون State پنهان موفق باشد.
  2. Dependency قفل باشد: برای Python یک فایل تولیدشده مانند requirements-ci.txt با Version و Hash؛ برای Node، Lockfile و npm ci.
  3. Exit Code معتبر باشد: شکست Assertion باید Job را Non-zero کند. || true، Pipeline shell بدون pipefail یا Wrapper ضعیف این قرارداد را می‌شکند.
  4. JUnit تولید شود: مسیر، نام Test، Classname و Encoding پایدار باشند؛ فایل خالی نیز Failure قابل‌تشخیص باشد.
  5. داده مستقل باشد: هر Job/Shard Namespace و Cleanup خودش را داشته باشد؛ Clock، Seed و Locale ثبت شوند.
  6. Runner policy معلوم باشد: چه Repository/Role/Branchی روی کدام Executor و Network اجرا می‌شود؟
  7. Secret plan نوشته شود: MR نامطمئن برای تست پایه نباید به Secret تولیدی نیاز داشته باشد.
  8. مالک Failure مشخص باشد: Product failure، Test failure، Environment failure و Report failure مسیر یکسان ندارند.

برای معماری Laneها و Gateهای سریع/کند/Event-driven، راهنمای طراحی Continuous Testing مکمل این پیاده‌سازی است.

Pipeline Policy با workflow:rules

قبل از اجرای Job، تصمیم بگیرید چه رویدادی حق ساخت Pipeline دارد. نمونه این مقاله چهار Source را می‌پذیرد: Merge Request، شاخه پیش‌فرض، Tag و Schedule. Feature branch بدون MR عمداً Pipeline سرور نمی‌سازد؛ اگر تیم شما Branch pipeline، Trigger، Parent/child یا API pipeline لازم دارد، آن Source را آگاهانه اضافه کنید.

مستند رسمی workflow تأکید می‌کند که این Rules پیش از Jobها ارزیابی می‌شوند. ترکیب بی‌دقت Job rules و fallbackهایی مانند when: always می‌تواند Pipeline تکراری بسازد. only/except را برای طراحی جدید انتخاب نکنید؛ Policy را یک‌جا و با تست Sourceها نسخه‌دار کنید.

workflow:
  auto_cancel:
    on_new_commit: interruptible
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - if: '$CI_COMMIT_TAG'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - when: never

on_new_commit: interruptible فقط Jobهایی را Cancel می‌کند که Cancel شدنشان امن تعریف شده است. Migration، انتشار یا Mutation غیرIdempotent را کورکورانه interruptible نکنید.

مرز امنیتی: YAML کد از راه دور است

فایل Pipeline صرفاً تنظیمات نیست؛ به Runner فرمان اجرا می‌دهد. توسعه‌دهنده‌ای که بتواند Script یا Dependency را تغییر دهد، عملاً کد روی زیرساخت CI اجرا می‌کند. راهنمای امنیت Runner خودمیزبان GitLab درباره خطر Shell executor، Runner غیرEphemeral مشترک، Docker privileged و سرقت CI_JOB_TOKEN/Secret هشدار می‌دهد.

مرز کنترل حداقلی نشانه خطر
MR داخلی/فورک بدون Secret تولیدی، Network محدود، Review تغییر YAML Job نامطمئن روی Protected runner
Runner Ephemeral/Project-scoped، Non-privileged، Patch و Cleanup Shell executor مشترک بین پروژه‌های نامطمئن
Registry/Package Mirror مورداعتماد، TLS/CA، Digest و Lockfile Tag شناور یا Pull از دامنه ناشناس
Variables Protected/Masked/File یا Secret manager، حداقل Scope چاپ Env یا فعال‌کردن Debug trace
Artifact/Report Redaction، Retention، دسترسی و حجم محدود Screenshot شامل PII، Token یا داده بانکی
Network Egress allowlist، Metadata block، سرویس‌های Job-local دسترسی Runner تست به Production/VPN گسترده

مستند امنیت CI/CD Variables می‌گوید تغییرهای .gitlab-ci.yml را پیش از دراختیارگذاشتن Protected variables بازبینی کنید. Masking جلوی Exfiltration عمدی را نمی‌گیرد؛ فقط نمایش ناخواسته بعضی مقادیر را کاهش می‌دهد. برای MR پایه، Stub و Credential موقت Job-local بهتر از Secret مشترک است.

نمونه کامل .gitlab-ci.yml برای pytest

YAML زیر با Parser مستقل بررسی شده است. برای خوانایی از Image tag استفاده می‌کند؛ در محیط تولیدی آن را پس از تأیید به Registry/Mirror داخلی و Digest مورداعتماد تبدیل کنید. requirements-ci.txt باید Version و Hash داشته باشد و scripts/wait_for_database.py باید تا Timeout یک Query واقعی مانند SELECT 1 را بررسی کند، نه اینکه فقط Sleep کند.

workflow:
  auto_cancel:
    on_new_commit: interruptible
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - if: '$CI_COMMIT_TAG'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - when: never

stages:
  - verify
  - test

default:
  image: python:3.13-slim-bookworm
  interruptible: true
  retry:
    max: 1
    when:
      - runner_system_failure
      - stuck_or_timeout_failure
      - api_failure
  before_script:
    - python --version
    - python -m pip install --require-hashes -r requirements-ci.txt
  cache:
    key:
      files:
        - requirements-ci.txt
    paths:
      - .cache/pip/
    policy: pull-push

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  PYTHONDONTWRITEBYTECODE: "1"
  PYTHONUNBUFFERED: "1"
  FF_NETWORK_PER_BUILD: "true"

lint:
  stage: verify
  needs: []
  timeout: 5m
  script:
    - ruff check .

unit_tests:
  stage: test
  needs: []
  timeout: 10m
  script:
    - mkdir -p reports/unit
    - pytest -m "not integration" --junitxml=reports/unit/junit.xml
  artifacts:
    when: always
    expire_in: 7 days
    reports:
      junit: reports/unit/junit.xml
    paths:
      - reports/unit/

integration_tests:
  stage: test
  needs: []
  timeout: 20m
  services:
    - name: postgres:17-bookworm
      alias: postgres
  variables:
    POSTGRES_DB: app_test
    POSTGRES_USER: ci_runner
    POSTGRES_PASSWORD: ci_ephemeral_only
    DATABASE_URL: postgresql://ci_runner:ci_ephemeral_only@postgres:5432/app_test
  script:
    - python scripts/wait_for_database.py --url "$DATABASE_URL" --timeout 30
    - mkdir -p reports/integration
    - pytest -m integration --junitxml=reports/integration/junit.xml
  artifacts:
    when: always
    expire_in: 7 days
    reports:
      junit: reports/integration/junit.xml
    paths:
      - reports/integration/

رمز ci_ephemeral_only در این مثال Secret نیست: فقط برای PostgreSQL موقت داخل شبکه همان Job است و هرگز نباید برای محیط مشترک یا Production بازاستفاده شود. اگر Dependency بیرونی واقعی نیاز به Secret دارد، آن Job را از Pipeline نامطمئن جدا و Scope متغیر/Runner را محدود کنید.

چرا هر خط این Pipeline مهم است

تصمیم اثر محدودیت/کنترل
image: python:3.13-slim-bookworm Runtime خوانا و مشترک Tag می‌تواند جابه‌جا شود؛ در Production با Digest و Mirror تأییدشده Pin شود
--require-hashes Version و محتوای Dependency بررسی می‌شود فایل باید با فرایند امن تولید و بازبینی شود
interruptible: true Commit جدید کار Safe قدیمی را لغو می‌کند برای Side effect غیرIdempotent مناسب نیست
Retry فقط Infra Runner/API failure گذرا یک فرصت دیگر دارد script_failure عمداً Retry نمی‌شود
needs: [] Lint/Unit/Integration بی‌درنگ و موازی شروع می‌شوند فقط وقتی Artifact/Build قبلی لازم نیست
Timeout برای هر Job Hang منابع را بی‌نهایت نگه نمی‌دارد بر اساس p95 سالم و Startup واقعی تنظیم شود
Cache با Lockfile key تغییر Dependency، Cache key را عوض می‌کند Cache ممکن است نباشد؛ Install باید مستقل موفق شود
artifacts: when: always Report شکست نیز جمع می‌شود فایل حساس باید پیش از Upload Redact شود
JUnit path صریح GitLab Result را Parse و مقایسه می‌کند وجود فایل و اندازه/نام‌های یکتا باید کنترل شود
Probe دیتابیس Test پس از Readiness واقعی آغاز می‌شود Socket باز، Migration و قابلیت Query را تضمین نمی‌کند

نکته ظریف: بخش‌های default در GitLab به Job کپی می‌شوند، اما اگر همان Keyword را در Job بازتعریف کنید الزاماً Merge عمیق رخ نمی‌دهد. برای مثال، تعریف before_script جدید در Integration job می‌تواند نصب Dependency پیش‌فرض را جایگزین کند. اثر نهایی YAML را در CI Lint نسخه GitLab خود بررسی کنید.

JUnit، Exit Code و معنای واقعی Pass/Fail

مستند رسمی Unit Test Reports در GitLab یک مرز بسیار مهم دارد: JUnit فقط Report را در Merge Request/Pipeline نمایش می‌دهد و به‌تنهایی وضعیت Job را تغییر نمی‌دهد. دستور تست باید هنگام Failure با Exit Code غیرصفر تمام شود.

این الگو درست است:

pytest -m "not integration" --junitxml=reports/unit/junit.xml

این الگوها اطمینان کاذب می‌سازند:

# بد: شکست تست را صفر می‌کند
pytest --junitxml=reports/junit.xml || true

# پرریسک: اگر shell وضعیت سمت چپ Pipe را حفظ نکند
pytest --junitxml=reports/junit.xml | tee reports/console.log

اگر Wrapper لازم دارید، Exit اصلی را صریح حفظ و در پایان همان را برگردانید. بهتر است Report مستقل تولید شود و artifacts: when: always Upload را حتی پس از Non-zero انجام دهد؛ برای جمع‌آوری فایل، تست را سبز نکنید.

قرارداد JUnit قابل‌اعتماد

  • هر testcase ترکیب یکتای classname و name داشته باشد؛ Duplicate ممکن است از نمایش حذف شود.
  • برای Shardها مسیر جدا مانند reports/unit-$CI_NODE_INDEX.xml بسازید.
  • فایل منفرد باید زیر ۳۰ MB و مجموع فایل‌های یک Job زیر ۱۰۰ MB بماند؛ Limit نسخه/Instance خود را هم بررسی کنید.
  • برای مقایسه MR، Pipeline موفق شاخه هدف باید Baseline report داشته باشد.
  • یک Guard کوچک بررسی کند Report وجود دارد، XML قابل‌Parse است و در Suite مورد انتظار حداقل یک Test ثبت شده؛ «No tests collected» را Policy کنید.
  • Timestamp، Stdout، Error و Screenshot را پیش از Upload از Token، PII و داده مالی پاک کنید.

در راهنمای مدیریت Test Run و Result تفاوت Passed، Failed، Blocked، Inconclusive، Retest و Quarantined دقیق‌تر تعریف شده است؛ JUnit استاندارد همه این معناها را به‌طور کامل حمل نمی‌کند، پس Metadata مکمل را در Manifest نگه دارید.

Cache، Artifact، Report و Log را قاطی نکنید

نوع هدف نمونه قاعده اعتماد
Cache شتاب بین Job/Pipeline Package download cache اختیاری، قابل‌حذف و هرگز Oracle
Artifact خروجی/Evidence یا انتقال به Job بعد Build، screenshot، manifest Retention، دسترسی، checksum و provenance
Report Artifact ساختاریافته قابل‌تفسیر GitLab JUnit، coverage، security report Schema/Limit/نام یکتا و Completeness
Log Timeline و عیب‌یابی اجرا stdout/stderr و Correlation ID Redaction، اندازه محدود و بدون Secret

راهنمای رسمی Cache در GitLab Cache را برای Dependencyهای دانلودی و Artifact را برای نتیجه میانی/خروجی تعریف می‌کند. Cache بین Runnerها تضمین‌شده نیست، به Executor و Distributed cache بستگی دارد و Protected/Non-protected context می‌تواند جداسازی امنیتی داشته باشد. اگر Cache خالی یا خراب شد، نصب Lockfile باید نتیجه یکسان بسازد؛ فقط کندتر.

مستند Job Artifacts را برای expire_in، Access و دریافت Artifact بررسی کنید. گزارش مالی، Dump دیتابیس، Video یا Trace مرورگر را پیش‌فرض عمومی نکنید. Retention را از نیاز Triage، Audit و هزینه Storage بسازید؛ «برای همیشه» Policy نیست.

الگوی Cache سالم

  • Key به Lockfile، Runtime و معماری حساس باشد؛ Package باینری Linux/amd64 را با arm64 مشترک نکنید.
  • کد Buildشده را به‌عنوان Cache قابل‌اعتماد Promote نکنید؛ Artifact با checksum و provenance بسازید.
  • مسیر Cache و Artifact هم‌پوشان نباشد؛ ترتیب Restore می‌تواند فایل را بازنویسی کند.
  • Untrusted branch اجازه Poison کردن Cache مورداعتماد شاخه Protected را نداشته باشد.
  • Cache hit را KPI کیفیت نکنید؛ p50/p95 زمان نصب و نرخ Miss فقط برای ظرفیت و هزینه‌اند.

Integration Test با services: Running با Ready یکسان نیست

GitLab services یک Container شبکه‌ای کنار Job می‌سازد؛ Database از Hostname alias مانند postgres در دسترس است، نه localhost. طبق مستند رسمی Services، Runner Portهای Exposed را برای دسترس‌پذیری بررسی می‌کند. بازشدن Port ثابت نمی‌کند Authentication، Schema، Migration، Seed و Query موردنیاز آماده‌اند.

Probe خوب:

  1. Deadline محدود و Backoff دارد؛ Sleep ثابت ندارد.
  2. با همان Credential و Database مورد استفاده Test وصل می‌شود.
  3. یک Query واقعی اجرا و در صورت نیاز Schema version را بررسی می‌کند.
  4. در Timeout با Non-zero و پیام Redacted خارج می‌شود.
  5. زمان Readiness را به‌عنوان Telemetry ثبت می‌کند، نه اینکه Password را چاپ کند.

Migration را یک مرحله صریح و Fail-fast نگه دارید. Test نباید Schema را به‌طور پنهانی بسازد. برای Stack چندسرویسی، Health/Readiness، Migration، Test Runner و Evidence را با راهنمای Docker Compose محیط تست هماهنگ کنید.

Data isolation در Job و Shard

Service Container هر Job جداست، اما منابع بیرونی، Queue، Bucket، Emulator، Port یا Environment مشترک نیستند. Namespace را از CI_PIPELINE_ID، CI_JOB_ID و در Parallel از CI_NODE_INDEX بسازید؛ Cleanup باید Idempotent، محدود به همان Namespace و دارای TTL باشد. داده Production را به Runner نکشید. راهنمای مدیریت داده تست Synthetic، Masking، Subsetting، Isolation و Retention را پوشش می‌دهد.

سرعت با DAG و Parallel؛ نه با حذف شواهد

Stageها مدل ساده‌ای می‌دهند، اما همه Jobهای Stage بعد منتظر اتمام Stage قبلی می‌مانند. مستند GitLab برای needs اجازه می‌دهد Dependency واقعی را به DAG تبدیل کنید. در نمونه ما Lint، Unit و Integration هیچ Artifact قبلی لازم ندارند، پس با needs: [] فوری شروع می‌شوند.

سه روش را جدا کنید:

  • DAG: Job فقط منتظر تولیدکننده واقعی Artifact می‌ماند.
  • Shard با parallel: N: یک Suite میان N Job تقسیم می‌شود؛ ابزار Sharding باید Testها را بدون تکرار/حذف توزیع کند.
  • parallel:matrix: همان Contract در Runtime، Database یا Platformهای مشخص اجرا می‌شود.

راهنمای Job control و Parallel در GitLab متغیرهای CI_NODE_INDEX/CI_NODE_TOTAL و Matrix را توضیح می‌دهد. Parallel کردن زمانی مفید است که Queue time، Startup و Contention سود را نبلعند.

پیش از افزایش Parallel شاهد لازم
Suite قابلیت Shard دارد مجموع Testهای یکتا برابر Baseline و هیچ Missing/Duplicate نیست
داده مستقل است Namespace و Cleanup مجزا؛ صفر Collision
Dependency ظرفیت دارد Rate limit/DB connection/CPU زیر Budget
Artifact نام یکتا دارد JUnit و screenshot هر Shard بازنویسی نمی‌شود
Runner ظرفیت دارد p95 queue time و Cost پس از افزایش بدتر نمی‌شود
Failure قابل‌تشخیص است Shard، Seed، Test ID و Context در Report ثبت است

Selective testing با rules:changes؛ سریع اما محدود

در Monorepo می‌توان Job را با rules: changes به مسیرهای مرتبط محدود کرد، اما «فایل تغییر نکرده» معادل «رفتار تحت‌تأثیر نیست» نیست. Contract مشترک، Dependency، Configuration، Generator، Schema، Base image و Pipeline template ممکن است چند سرویس را غیرمستقیم تغییر دهند.

یک Strategy متوازن:

  • Lint و Unit سریع روی همه MRهای مرتبط؛
  • Test انتخابی بر اساس Dependency graph، نه فقط glob ساده؛
  • Contract/Integration برای Consumerهای متأثر؛
  • Full suite روی Default branch و Schedule؛
  • مسیر Override دستی برای Change پرریسک؛
  • اندازه‌گیری Escaped impact ناشی از Selection و بازبینی Ruleها.

اگر Policy انتخاب ناقص باشد، Pipeline سریع‌تر فقط سریع‌تر اطمینان کاذب می‌دهد.

Retry، Flaky و Quarantine را از هم جدا کنید

Retry زیرساختی برای Failureهایی مانند Runner system/API/stuck مناسب است. Retry کردن script_failure در سطح Job می‌تواند Product failure، Test defect و Data collision را یکسان پنهان کند. اگر Framework برای تشخیص Flakiness تکرار انجام می‌دهد، Attempt اول و همه نتایج را نگه دارید؛ «Pass after retry» برابر Pass عادی نیست.

وضعیت Pipeline policy اقدام
Runner/API failure Retry محدود و خودکار Runner/SRE owner و trend
Assertion قابل‌بازتولید Fail بدون Retry سبزکننده مالک محصول/کد
Test defect Fail یا Quarantine کنترل‌شده Fix با Regression برای Suite
Flaky تأییدشده Quarantined با برچسب visible مالک، علت، Expiry و جایگزین Evidence
Blocked dependency Blocked/Infra، نه Product pass Dependency owner و fallback

Quarantine قبرستان نیست. Test باید Risk پوشش‌داده‌شده، دلیل، Ticket، مالک، Expiry و Evidence جایگزین داشته باشد. معماری قابل‌نگهداری و Diagnosability را در راهنمای کد تست قابل‌نگهداری دنبال کنید.

Run Manifest: چه چیزی واقعاً آزمایش شد

Pipeline ID به‌تنهایی هویت Run نیست. برای Testهای تصمیم‌ساز، یک Manifest ماشینی (مثلاً JSON) بسازید و کنار Report نگه دارید:

  • Project، Commit SHA، MR/Branch/Tag و Pipeline source/ID؛
  • GitLab و Runner version، Executor، Runner tag و معماری؛
  • Job image digest و Service image digest؛
  • Lockfile hash، Test code revision و Config/Feature flag hash؛
  • Environment، Dependency mode و Contract version؛
  • Data namespace، Schema/Seed version و Random seed؛
  • Timezone/Locale/Clock mode و زمان شروع/پایان UTC؛
  • Shard index/total، Attempt number و First-attempt result؛
  • JUnit/Artifact path، checksum و Retention؛
  • Passed/Failed/Skipped/Blocked/Quarantined/Unknown summary؛
  • Gate decision، مالک Failure و لینک اقدام بعدی.

Secret و PII را داخل Manifest نگذارید؛ فقط شناسه نسخه یا مرجع امن بنویسید. این فایل به سؤال «دیروز سبز بود، امروز چرا شکست؟» پاسخ قابل‌مقایسه می‌دهد.

GitLab CI در ایران: دسترس‌پذیری، رجیستری و شواهد فارسی

برای تیم ایرانی، Failure شبکه و Supply chain نباید با Failure محصول مخلوط شود. GitLab Self-managed، Runner داخل کشور یا شبکه سازمان و Registry/Package mirror می‌توانند Latency و دسترس‌پذیری را بهبود دهند، اما خودشان دارایی عملیاتی با Patch، Backup، Capacity و Trust policy هستند.

ریسک کنترل Evidence
۴۰۳/Timeout رجیستری یا Package Mirror مورداعتماد، Digest، Timeout محدود و fallback مصوب دامنه مؤثر، digest، زمان Pull و طبقه‌بندی Infra
CA داخلی/Self-signed Trust store مدیریت‌شده و Rotation Issuer/expiry بدون افشای کل Certificate chain حساس
Runner مشترک سازمانی Project scope، Ephemeral worker و Network segmentation Runner ID/executor/image digest و cleanup status
PSP/SMS/Map محدود یا ناپایدار Stub قراردادمحور در MR؛ Sandbox کنترل‌شده در Lane جدا Contract version، mode و Failure injection
ریال/تومان واحد Canonical و Test data دارای واحد صریح مقدار ورودی/خروجی و Ledger oracle
رقم/حرف فارسی و عربی Fixture نسخه‌دار برای ۰۱۲/۰۱۲، ی/ی، ک/ک و ZWNJ Encoding/normalization policy و diff خوانا
جلالی/تهران/UTC Clock تزریقی، ذخیره UTC و Policy تاریخ کسب‌وکار Instant، timezone و displayed date
PII در JUnit/Screenshot Synthetic data، Redaction و Artifact access/expiry Redaction check و retention owner

Credential واقعی PSP، Production database یا VPN گسترده نباید شرط اجرای MR تستی باشد. دو Lane بسازید: Evidence امن و تکرارپذیر با Stub برای هر MR؛ سپس Validation محدود با Sandbox/Dependency واقعی در Context محافظت‌شده. اگر مشکل در Stack محلی/Container پیچیده است، لَب عیب‌یابی Docker Compose مسیر بازتولید و Evidence-before-teardown را ارائه می‌کند.

راهنمای عیب‌یابی GitLab CI از نشانه تا علت

نشانه اول بررسی کنید اقدام بعدی
Pipeline ساخته نشد CI_PIPELINE_SOURCE و ترتیب workflow:rules Source را در CI Lint/رویداد واقعی Test کنید
دو Pipeline برای یک Commit Branch + MR rules و fallback when Pipeline creation را در workflow یکپارچه کنید
Job در Pending ماند Runner online، tag، protected scope و ظرفیت Queue/Runner را رفع کنید؛ Test را Retry کور نکنید
Image pull با ۴۰۳/TLS/Timeout Registry auth، mirror، CA، DNS و digest Infra failure ثبت و منبع تأییدشده را بازیابی کنید
Dependency install ناپایدار Lockfile/hash، index، cache و proxy بدون Cache بازتولید؛ Supply-chain diff را بررسی کنید
Service warning یا Connection refused Alias، exposed port، readiness probe و log سرویس Probe محدود Query/Schema؛ از Sleep ثابت دوری کنید
Test fail ولی Job سبز || true، Pipe، Wrapper exit و allow_failure Exit اصلی را Preserve و Exception را حذف کنید
Job fail ولی Tests tab خالی مسیر/وجود/اندازه/XML/expiry JUnit Artifact always و Parse guard اضافه کنید
تعداد تست Report کمتر است Duplicate classname/name و Shard overwrite نام و مسیر Shard را یکتا کنید
Protected variable در MR خالی است نوع Pipeline، protection source/target و policy MR پایه را بدون Secret طراحی یا Lane محافظت‌شده جدا کنید
Parallel فقط گاهی شکست می‌خورد Data/port/file/queue namespace و capacity CI_NODE identity، TTL و Shard balance را ثبت کنید
Pass after retry زیاد شد First-attempt result، Seed، Clock، Dependency و Runner Flaky triage؛ Retry را Success عادی گزارش نکنید
Artifact در Job بعد نیست needs/dependencies، path و expiry Dependency واقعی و artifact download را صریح کنید
Job قدیمی Cancel نمی‌شود interruptible و Side-effect state Safe job را interruptible؛ Mutation را Idempotent/Serialized کنید

متریک‌های سالم برای Pipeline تست

هدف «سبزبودن بیشتر» نیست؛ Feedback سریع، قابل‌اعتماد و تصمیم‌پذیر است. این سیگنال‌ها را با Trend و Context بسنجید:

  • Feedback latency p50/p95: از Commit/MR تا اولین نتیجه actionable؛ Queue و Execution را جدا کنید.
  • First-attempt pass/fail: Retry را از نتیجه اول جدا نگه دارید.
  • Flaky/Quarantine rate: به تفکیک Suite، Owner، علت و Age.
  • Report completeness: Jobهای موردانتظار که JUnit معتبر و Non-empty دارند.
  • Time-to-triage: از Failure تا طبقه‌بندی Product/Test/Infra/Data/Unknown.
  • Cancellation waste: دقیقه Runner صرف‌شده روی Commit منسوخ.
  • Queue saturation: p95 انتظار، Utilization و Pending by tag.
  • Artifact health: Upload failure، حجم، expiry mismatch و Redaction violation.
  • Risk evidence coverage: ریسک‌های بحرانی دارای Evidence تازه و معتبر، نه درصد Automation.
  • Escaped outcome: Regression/Incidentهایی که Gate نتوانسته ببیند، با علت محدودیت.

Cache hit، تعداد Job یا تعداد Test بدون Outcome می‌تواند Game شود. برای تعریف مخرج، Cohort و Guardrail، راهنمای متریک‌های تست نرم‌افزار را مبنا بگیرید.

برنامه ۳۰روزه پیاده‌سازی

هفته اول: قرارداد و امنیت

  • Pipeline source matrix و انتظار MR/default/tag/schedule را بنویسید.
  • Runner/Executor/Network/Secret threat model و مالک را مشخص کنید.
  • تست‌ها را در Clone تمیز با Lockfile و Exit درست اجرا کنید.
  • Schema JUnit، مسیر و Data redaction را قرارداد کنید.

هفته دوم: Fast lane قابل‌اعتماد

  • Lint و Unit را با Timeout، interruptible و infra-only retry اضافه کنید.
  • Cache قفل‌محور را بدون وابستگی correctness فعال کنید.
  • JUnit always، Retention و Parse/non-empty guard بسازید.
  • Baseline پیش/پس را برای Feedback و Flaky ثبت کنید.

هفته سوم: Integration و Parallel

  • Service، Query readiness، Migration و Namespace داده را اضافه کنید.
  • DAG واقعی را با needs بسازید و Stage wait را کم کنید.
  • یک Suite را ابتدا دو Shard کنید و Missing/Duplicate/Collision/Capacity را بسنجید.
  • Lane Sandbox محافظت‌شده را از MR بدون Secret جدا کنید.

هفته چهارم: عملیات و Governance

  • Run Manifest، failure taxonomy و Dashboard تیم را فعال کنید.
  • چهار Failure Drill اجرا کنید: Registry outage، DB late-ready، corrupt JUnit و Flaky test.
  • Artifact access/expiry و Secret leakage drill را بازبینی کنید.
  • Ruleها، Runtime/Digest و Quarantineهای منقضی را در Review ماهانه قرار دهید.

Anti-patternهای GitLab CI برای تست

  • Image شناور مانند latest بدون ثبت Digest؛
  • pip install/npm install بدون Lockfile معتبر؛
  • استفاده از Cache به‌عنوان Artifact یا منبع حقیقت؛
  • || true، allow_failure یا Pipeای که Test failure را سبز می‌کند؛
  • JUnit بدون when: always و Retention؛
  • نام Test تکراری یا Report shardها در یک مسیر؛
  • Sleep ثابت برای Database/Service readiness؛
  • Shell/Privileged runner مشترک برای کد نامطمئن؛
  • Protected Secret در Pipeline فورک/MR بدون Review؛
  • چاپ Environment کامل یا Debug trace حاوی Token؛
  • Retry همه Script failureها و گزارش Pass نهایی؛
  • Parallel کردن پیش از Data/Artifact isolation؛
  • استفاده از rules:changes به‌عنوان تنها Regression strategy؛
  • Pipeline تکراری Branch/MR به‌علت Policy پراکنده؛
  • Artifact شامل داده واقعی مشتری یا Evidence بدون Expiry؛
  • حذف Full scheduled/default-branch suite پس از Test selection؛
  • بهینه‌سازی زمان با حذف Test به‌جای تحلیل Risk/Value؛
  • اعلام «Pipeline سبز» بدون Unknown، Quarantine و Report completeness.

چک‌لیست Merge Request برای .gitlab-ci.yml

  • □ Sourceهای مجاز و جلوگیری از Pipeline تکراری Test شده‌اند.
  • □ Runner/Executor/Tag و مرز Secret متناسب با Trust کد است.
  • □ Image/Service در Mirror مورداعتماد و ترجیحاً Digest-pin شده‌اند.
  • □ Runtime و Dependency از Lockfile/Hash تکرارپذیرند.
  • □ Test failure با Non-zero وضعیت Job را Fail می‌کند.
  • □ JUnit معتبر، یکتا، Non-empty و when: always است.
  • □ Artifact مسیر، دسترسی، Redaction و expire_in دارد.
  • □ Cache حذف‌پذیر و Key آن به ورودی مناسب حساس است.
  • □ Service یک Probe واقعی و Timeout محدود دارد.
  • □ داده/پورت/فایل هر Job و Shard Namespace مستقل دارد.
  • □ needs فقط Dependency واقعی را مدل می‌کند.
  • □ Retry فقط Infra و Quarantine دارای Owner/Expiry است.
  • □ Timeout، Cancellation و Capacity با p95 سالم تنظیم شده‌اند.
  • □ Run Manifest و مسیر Triage/Owner برای Failure وجود دارد.

سؤالات متداول تست خودکار در GitLab CI

چرا GitLab تست‌های ناموفق را نشان می‌دهد اما Job سبز است؟

JUnit وضعیت Job را تعیین نمی‌کند. احتمالاً دستور تست Exit Code صفر برگردانده یا Wrapper، || true، Pipe و allow_failure آن را خنثی کرده است. Test runner باید Non-zero خارج شود؛ Artifact را با when: always جمع کنید، نه با سبزکردن Script.

تفاوت Cache و Artifact در GitLab چیست؟

Cache دانلودها را بین Job/Pipeline سریع می‌کند و در دسترس‌بودنش تضمین نیست. Artifact خروجی یا Evidence یک Job است، Retention و دسترسی دارد و می‌تواند به Job بعد منتقل شود. Dependency cache را هر بار با Lockfile اعتبارسنجی کنید؛ Build/Report تصمیم‌ساز را Artifact نگه دارید.

چگونه Integration Test را با PostgreSQL در GitLab CI اجرا کنیم؟

PostgreSQL را با services و Alias شبکه‌ای اجرا کنید، از Job به Hostname alias وصل شوید، سپس با Timeout یک Query واقعی و Schema/Migration مورد انتظار را Probe کنید. Credential فقط Job-local، داده Namespaceدار و Cleanup/TTL لازم است. Port باز به‌تنهایی Ready نیست.

آیا parallel کردن همیشه Pipeline را سریع‌تر می‌کند؟

خیر. Startup، Queue، محدودیت Runner، DB connection، Rate limit و Artifact upload ممکن است سود را از بین ببرند. ابتدا Suite را با ابزار درست Shard، Testهای Missing/Duplicate و Data collision را صفر، سپس از ۲ به ۴ Worker مرحله‌ای افزایش دهید و p95/Cost را بسنجید.

چرا Protected variable در Merge Request در دسترس نیست؟

GitLab برای جلوگیری از افشای Secret، دسترسی متغیر/Runner محافظت‌شده را به Context و Policy protection محدود می‌کند. MR نامطمئن را بدون Secret طراحی کنید. اگر Integration واقعی لازم است، یک Lane محافظت‌شده با Approval، Runner محدود و Scope حداقلی بسازید؛ تغییر YAML را پیش از اجرا بازبینی کنید.

جمع‌بندی: Pipeline سبز باید قابل‌اثبات باشد

GitLab CI زمانی به تصمیم کیفیت کمک می‌کند که رنگ Pipeline نتیجه یک زنجیره قابل‌ردیابی باشد: Source مجاز، Commit دقیق، Runner مورداعتماد، Runtime و Dependency تکرارپذیر، داده مستقل، Exit Code صادق، JUnit کامل و Artifact امن. سرعت با Cache، DAG، Cancellation و Parallel به‌دست می‌آید؛ اما فقط پس از حفظ Evidence و مرزهای امنیتی. Pipeline خوب Test بیشتری اجرا نمی‌کند؛ بازخورد معتبرتر را زودتر و با اقدام روشن‌تر تحویل می‌دهد.

دیدگاهتان را بنویسید