فحوص تعمل من تلقاء نفسها
لا تحميك الاختبارات إلا إن شُغِّلت في كل مرة بشيء غير ذاكرتك. والتكامل المستمر (CI) خادم يسحب الشيفرة عند كل push وطلب دمج في بيئة نظيفة ويشغّل الفحوص التي حددتها: lint وفحص الأنواع والاختبارات واختبار رجعي صغير. وإن فشل شيء حُجب التغيير. أما التسليم/النشر المستمر (CD) فيذهب أبعد: عند نجاح الفحوص على الفرع الرئيس يُبنى الإصدار الجديد ويُنشر تلقائيًا. وفي العمل الكمي الرهان ملموس: إعادة هيكلة تزحزح سعرًا بصمت نقطة أساس واحدة ينبغي أن تلتقطها آلة قبل أن تصل إلى محفظة.
# A test matrix: every operating system with every Python version, minus the combinations you skip
from itertools import product
os_list = ["linux", "macos", "windows"]
py_versions = ["3.11", "3.12"]
exclude = {("windows", "3.11")}
jobs = [(os_name, version) for os_name, version in product(os_list, py_versions) if (os_name, version) not in exclude]
print(len(jobs)) # 5
print(jobs[0], jobs[-1]) # ('linux', '3.11') ('windows', '3.12')ما الذي يفحصه خط معالجة كمي
لخط المعالجة النموذجي مراحل تفشل سريعًا: lint (الأسلوب والأخطاء الواضحة) وفحص الأنواع يستغرقان ثوانيَ ثم اختبارات الوحدة ثم فحوص أبطأ كـ اختبار رجعي للانحدار صغير يُقارن بنتيجة محفوظة. اجعل الفحوص حتمية: بذور ثابتة وبلا شبكة ولا اعتماد على تاريخ اليوم وببيانات من لقطة صغيرة مودعة بدل بث حي. وشغّلها على مصفوفة من إصدارات بايثون وأنظمة التشغيل التي تدّعي دعمها. وللحصول على ردّ سريع شغّل فقط الاختبارات التي قد يؤثر فيها التغيير (اختيار الاختبارات) مع رجوع آمن إلى المجموعة كلها حين لا تعرف.
# Test selection: run only the tests a change can affect; an unknown file means "run everything"
def select_tests(changed_files, mapping):
chosen = set()
for path in changed_files:
matches = [tests for prefix, tests in mapping.items() if path.startswith(prefix)]
if not matches:
return sorted({test for tests in mapping.values() for test in tests})
for tests in matches:
chosen.update(tests)
return sorted(chosen)
mapping = {"src/pricing/": ["tests/test_bs.py", "tests/test_greeks.py"], "src/risk/": ["tests/test_var.py"]}
print(select_tests(["src/risk/var.py"], mapping)) # ['tests/test_var.py']
print(select_tests(["src/risk/var.py", "setup.cfg"], mapping)) # all three tests: setup.cfg could affect anythingالبوابات والأسرار وملف سير العمل
يُوصَف خط المعالجة في ملف محفوظ في المستودع، مثل .github/workflows/ci.yml في GitHub Actions: متى يعمل (on:) والمهام والمصفوفة والخطوات، وكل خطوة إما uses: إجراء جاهزًا أو run: أمرًا. عامل الملف كشيفرة تُراجَع كغيرها. وأضف بوابة نشر: لا ينشر إلا الفرع main ولا ينشر إلا إذا نجحت كل المهام المطلوبة (المهمة المتجاوزة أو الغائبة ليست نجاحًا). واحمِ الفرع كي لا يُدمج دون فحوص ناجحة. وأبقِ مفاتيح API وكلمات المرور في أسرار نظام CI لا في المستودع أبدًا، وامنح كل مهمة الصلاحيات التي تحتاجها فقط. وانشر أولًا في بيئة تداول تجريبي أو بيئة مرحلية.
# The deploy gate: main branch only, and every required job must have succeeded
def can_deploy(results, required, branch):
return branch == "main" and all(results.get(job) == "success" for job in required)
print(can_deploy({"lint": "success", "tests": "success"}, ["lint", "tests"], "main")) # True
print(can_deploy({"lint": "success", "tests": "skipped"}, ["lint", "tests"], "main")) # False skipped is not success
print(can_deploy({"lint": "success"}, ["lint", "tests"], "main")) # False a missing job is not success
print(can_deploy({"lint": "success", "tests": "success"}, ["lint", "tests"], "dev")) # False only main deploys# The workflow file is plain text, so it can be generated and checked like any other output
def workflow_yaml(name, python_versions, command):
versions = ", ".join(f'"{version}"' for version in python_versions)
lines = [f"name: {name}", "on: [push, pull_request]", "jobs:", " test:", " runs-on: ubuntu-latest",
" strategy:", " matrix:", f" python-version: [{versions}]", " steps:",
" - uses: actions/checkout@v4", " - uses: actions/setup-python@v5", " with:",
" python-version: ${{ matrix.python-version }}", " - run: pip install -e .[test]",
f" - run: {command}"]
return "\n".join(lines) + "\n"
text = workflow_yaml("ci", ["3.11", "3.12"], "pytest -q")
print(text.splitlines()[7]) # python-version: ["3.11", "3.12"]
print(text.splitlines()[-1]) # - run: pytest -q- تشغّل CI فحص lint والأنواع والاختبارات في بيئة نظيفة عند كل push؛ ولا ينشر CD تلقائيًا إلا إذا نجحت على
main. - اجعل الفحوص حتمية (بذور ولقطات وبلا شبكة) وشغّلها على مصفوفة من الإصدارات والأنظمة واختر الاختبارات بحسب ما تغيّر.
- صِف خط المعالجة في ملف سير عمل يُراجَع، واشترط للنشر
mainوالمهام المطلوبة، وأبقِ الأسرار في نظام CI، وأصلح الاختبارات المتذبذبة.
التالي: خطوط المعالجة المجدولة، تشغيل مهام البيانات والتداول في الوقت الصحيح وبالترتيب الصحيح.
المختبر: ما الذي يُقيَّم
- وسّع مصفوفة الاختبارات باستخدام
matrix_jobs25 نقطة - شغّل ما يهم فقط باستخدام
select_tests25 نقطة - احرس الإصدار باستخدام
can_deploy25 نقطة - ولّد سير العمل باستخدام
workflow_yaml25 نقطة