בדיקות מקבילות ב-CI/CD: פיצול ובידוד
בדיקות שעוברות מקומית תוך 2 דקות פתאום לוקחות 25 ב-CI. אתה מסתכל בלוג — הראנר מבזבז 8 דקות על התקנת תלויות, עוד 3 על בנייה, ואז 14 דקות על הרצה ליניארית של 800 מקרי בדיקה אחד אחרי השני. ביצוע בדיקות במקביל הוא לא רק דגל --parallel. זה פיצול נכון, ניהול מצב משותף, בידוד מסד נתונים וקאשינג חכם. הניסיון שלנו מראה: הגדרה נכונה מקצרת את זמן ה-pipeline פי 3-5, וחיסכון במשאבי ראנר מגיע ל-40%. בפרויקט אחד עם 1500 בדיקות, קיצרנו את הזמן מ-45 ל-12 דקות.
למה בדיקות מקבילות נכשלות ב-CI?
רוב מסגרות הבדיקה תומכות בביצוע מקביל כברירת מחדל. תיעוד Jest ממליץ להשתמש ב---shard עבור ערכות בדיקה גדולות. RSpec דרך gem parallel_tests, pytest דרך pytest-xdist. אבל פשוט להפעיל אותם זה לא מספיק.
בעיית המצב המשותף: בדיקות שכותבות לאותו מסד נתונים לא יכולות לרוץ במקביל בבטחה. user_id = 1 בבדיקה אחת מתנגש עם user_id = 1 באחרת. עבור מסדי נתונים, הפתרון הוא DB נפרד לכל עובד או rollback של טרנזקציות אחרי כל בדיקה. ב-Rails — DatabaseCleaner עם אסטרטגיית :transaction לבדיקות מקבילות. ב-Django — --keepdb עם סכמות מבודדות.
בעיית סדר הבדיקות: לחלק מהבדיקות יש תופעות לוואי — יצירת קבצים, שינוי משתני סביבה, mock של singletons גלובליים. תחת ביצוע מקביל, תופעות לוואי אלו מתנגשות. זה מתבטא בצורה לא נעימה: בדיקה עוברת כשהיא רצה לבד ונכשלת ב-CI כשהיא רצה במקביל.
איך להשיג פיצול אחיד?
עבור ערכות בדיקה גדולות, מקביליות בתוך ראנר יחיד אינה מספיקה — יש לחלק את הבדיקות על פני מספר ראנרים. זהו פיצול בדיקות (test sharding).
ב-GitLab CI, הפרמטר parallel מפעיל מספר עותקים של אותה עבודה:
---
test:
parallel: 6 # запустит 6 копий джобы
script:
- bundle exec rspec --format progress \
$(bundle exec ruby scripts/split_tests.rb $CI_NODE_INDEX $CI_NODE_TOTAL)
---GitLab מגדיר test: parallel: 6 # запустит 6 копий джобы script: - bundle exec rspec --format progress \ $(bundle exec ruby scripts/split_tests.rb $CI_NODE_INDEX $CI_NODE_TOTAL) (0..5) ו-CI_NODE_INDEX (6) עבור כל shard. הסקריפט CI_NODE_TOTAL מחלק את רשימת קבצי ה-spec לחלקים שווים לפי אינדקס.
ב-GitHub Actions דרך matrix:
---
test:
strategy:
matrix:
shard: [1, 2, 3, 4, 5, 6]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests (shard ${{ matrix.shard }}/6)
run: |
npx jest --shard=${{ matrix.shard }}/6 --coverage
---
Jest תומך ב-split_tests.rb מגרסה 28. אין צורך לכתוב סקריפט חלוקה — Jest מחלק את הבדיקות לפי hash של שם הקובץ.
מתי ליישם פיצול מבוסס זמן?
חלוקה פשוטה לפי מספר קבצים מייצרת shards לא אחידים: אחד עשוי להכיל 5 בדיקות אינטגרציה ארוכות, אחר 50 בדיקות יחידה מהירות. הגישה הנכונה היא לחלק לפי זמן ביצוע צפוי.
RSpec יכול להשתמש ב-test: strategy: matrix: shard: [1, 2, 3, 4, 5, 6] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests (shard ${{ matrix.shard }}/6) run: | npx jest --shard=${{ matrix.shard }}/6 --coverage כדי לשמור את זמן כל בדיקה ב-JUnit XML. --shard=N/TOTAL או סקריפט מותאם קורא את התזמונים ההיסטוריים ומאזן את ה-shards כך שכולם יסתיימו בערך באותו זמן. Knapsack Pro עושה זאת דרך API בענן — הוא אוסף נתונים מכל הראנרים וממטב את החלוקה בכל ריצה.
עבור pytest — rspec_junit_formatter עם גישה דומה: knapsack_pro.
השוואת גישות:
| שיטה | אחידות | מורכבות יישום | דינמיות |
|---|---|---|---|
| לפי מספר קבצים | נמוכה | נמוכה | לא |
| לפי זמן | גבוהה | בינונית | כן (Knapsack) |
פיצול מבוסס זמן (Knapsack) נותן חלוקה אחידה יותר פי 1.5-2 מאשר לפי מספר קבצים, כפי שאושר בפרויקטים שלנו.
מה אם בדיקות תלויות במצב משותף?
אם בדיקות משתמשות בקבצים משותפים, משתנים גלובליים או שירותים חיצוניים, ביצוע מקביל יגרום לתנאי מרוץ (race conditions). פתרון — בידוד מלא: כל עובד מקבל קבצים זמניים משלו (דרך pytest-split), משתני סביבה משלו (דרך pytest --splits 6 --group 1 --durations-path .test_durations או הגדרת CI). עבור שירותים חיצוניים — השתמש ב-mocks או במכולות נפרדות לכל עובד. ב-Kubernetes, כל shard יכול לרוץ ב-pod נפרד עם משאבים מבודדים.
בידוד מסד נתונים לבדיקות מקבילות
ב-Rails עם Tempfile, כל עובד מקבל DB משלו: dotenv, parallel_tests, test... נוצר דרך test2. ב-test3:
test: database: myapp_test<%= ENV['TEST_ENV_NUMBER'] %> ב-Django כשמפצלים לפי ראנר — כל ראנר יוצר DB בדיקה משלו. אם משתמשים ב-PostgreSQL, ודא שלמשתמש ה-CI יש הרשאות rake parallel:create parallel:migrate, אחרת database.yml ייכשל עם test: database: myapp_test<%= ENV['TEST_ENV_NUMBER'] %> .
דוחות בדיקה וניתוח בדיקות איטיות
עם ביצוע מקביל, דוחות JUnit XML נוצרים בנפרד על כל shard. מערכת ה-CI חייבת לאגד אותם:
# GitLab — artifacts из всех шардов мержатся автоматически artifacts: reports: junit: reports/rspec-*.xml CircleCI Test Analytics, Buildkite Test Analytics, GitHub Actions עם CREATEDB — כולם יכולים לאגד דוחות ממספר shards ולבנות מגמות היסטוריות לזמן הביצוע של כל בדיקה. זה עוזר למצוא בדיקות שהאטו אחרי commit מסוים.
השוואת תמיכה בבדיקות מקבילות במערכות CI
| מערכת CI | עבודות מקבילות | פיצול | איגוד דוחות |
|---|---|---|---|
| GitLab CI | מילת מפתח manage.py test |
ידני או Knapsack | אוטומטי |
| GitHub Actions | אסטרטגיית matrix | permission denied to create database או ידני |
דרך פעולה של צד שלישי |
| CircleCI | מפתח parallelism | ידני | Test Analytics |
| Jenkins | שלבים מקבילים | ידני | תוספים |
טעויות אופייניות בהגדרת בדיקות מקבילות
הרחב רשימה
- שכחת בידוד מסד נתונים:
# GitLab — artifacts из всех шардов мержатся автоматически artifacts: reports: junit: reports/rspec-*.xmlלא מוגדר ל-dorny/test-reporter— בדיקות נכשלות עםparallel:. - לא מתחשבים בקאשינג: כל shard בונה תלויות מההתחלה — הזמן לא מתקצר.
- שימוש בפיצול לפי מספר קבצים ללא ניתוח תזמון — ה-shards משתנים מאוד.
- לא מוודאים שבדיקות רצות ברצף: אחרי ריצה מקבילה, בדיקות עשויות להשאיר זבל שמשפיע על ריצות עוקבות.
- לא מגדירים איגוד דוחות — מאבדים מידע על בדיקות שנכשלו.
מה כלול בעבודה
אנו מספקים הגדרה מלאה מאודיט ועד יישום. כתוצאה מכך, אתה מקבל:
- ניתוח ה-pipeline הנוכחי וזיהוי צווארי בקבוק.
- אודיט לבדיקות לבעיות מצב משותף עם המלצות לתיקון.
- בחירה ויישום אסטרטגיית פיצול (לפי קובץ או לפי זמן).
- הגדרת בידוד מסד נתונים לראנרים מקבילים.
- הגדרת איגוד דוחות והתראות לבדיקות איטיות.
- תיעוד לתחזוקה והדרכת צוות.
שלבי עבודה
- ניתוח זמן ה-pipeline הנוכחי — איפה בדיוק הזמן אובד, צווארי בקבוק.
- אודיט לבדיקות לבעיות מצב משותף — ריצה עם
--shardבסדרים שונים. - בחירת אסטרטגיית פיצול — לפי מספר או לפי היסטוריית תזמון.
- הגדרת מקביליות ב-CI —
DatabaseCleanerב-GitLab, matrix ב-GitHub Actions. - הגדרת בידוד DB על ראנרים מקבילים.
- הגדרת איגוד דוחות JUnit.
- מדידת זמן ה-pipeline הסופי.
לוח זמנים: 1-3 ימים תלוי במורכבות תשתית הבדיקות. העלות מחושבת באופן אישי. אנו מבטיחים תוצאות שקופות — תראה קיצור בזמן ה-pipeline אחרי השלב הראשון. אם אתה רוצה לזרז את ה-pipeline שלך — בקש אודיט היום. צור קשר כדי להעריך את הפרויקט שלך.







