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
- تست از CLI اجرا شود: همان دستور باید در Clone تمیز، بدون IDE و بدون State پنهان موفق باشد.
- Dependency قفل باشد: برای Python یک فایل تولیدشده مانند
requirements-ci.txtبا Version و Hash؛ برای Node، Lockfile وnpm ci. - Exit Code معتبر باشد: شکست Assertion باید Job را Non-zero کند.
|| true، Pipeline shell بدونpipefailیا Wrapper ضعیف این قرارداد را میشکند. - JUnit تولید شود: مسیر، نام Test، Classname و Encoding پایدار باشند؛ فایل خالی نیز Failure قابلتشخیص باشد.
- داده مستقل باشد: هر Job/Shard Namespace و Cleanup خودش را داشته باشد؛ Clock، Seed و Locale ثبت شوند.
- Runner policy معلوم باشد: چه Repository/Role/Branchی روی کدام Executor و Network اجرا میشود؟
- Secret plan نوشته شود: MR نامطمئن برای تست پایه نباید به Secret تولیدی نیاز داشته باشد.
- مالک 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 خوب:
- Deadline محدود و Backoff دارد؛ Sleep ثابت ندارد.
- با همان Credential و Database مورد استفاده Test وصل میشود.
- یک Query واقعی اجرا و در صورت نیاز Schema version را بررسی میکند.
- در Timeout با Non-zero و پیام Redacted خارج میشود.
- زمان 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 بیشتری اجرا نمیکند؛ بازخورد معتبرتر را زودتر و با اقدام روشنتر تحویل میدهد.

