كيفية إنشاء توثيقات محدثة تلقائياً من اختبارات بايثون

دقائق القراءة: 5

مقدمة: توثيق الشيفرة بذكاء

هل تخيلت يوماً أن بإمكانك إنشاء توثيقات لمشاريع Python الخاصة بك تلقائياً من اختباراتك الحالية، وأن تظل هذه التوثيقات محدثة باستمرار؟ وماذا لو كان بالإمكان حفظها بصيغة Markdown لتُدمج مع شيفرة مشروعك وتُعرض بسلاسة على منصات مثل GitLab أو GitHub؟ يبدو الأمر رائعاً، أليس كذلك؟ دعنا نستكشف كيف يمكن تحقيق ذلك.

سياق التوثيق: أهمية التحديث المستمر

غالباً ما يُقنعنا خبراء مثل Simon Brown بأهمية وجود توثيقات كافية لمشاريعنا البرمجية. ويشددون على ضرورة أن تكون هذه التوثيقات محدثة باستمرار، وأن تقدم معلومات موجزة وواضحة عبر مستويات مختلفة من التجريد. من المؤكد أن العمل على قاعدة شيفرة (codebase) مزودة بتوثيقات بهذا المستوى سيكون تجربة مثالية لأي مطور.

تحديات التوثيق التقليدي

لقد قرأت العديد من الكتب والمقالات حول هندسة البرمجيات والمواضيع ذات الصلة. ومع ذلك، لم أتمكن أبداً من حشد الطاقة الكافية، أو الحصول على الدعم اللازم، لإنشاء توثيقات بهذا المعيار العالي، ناهيك عن تحديثها بانتظام. لذلك، بالنسبة لوضعي على الأقل، أحتاج إلى طريقة لإنشاء وتحديث التوثيقات تلقائياً.

أود أيضاً تخزين المخططات “كشيفرة برمجية” (diagrams as code) بحيث يمكن إدراجها في مستودع الشيفرة (repository). بهذه الطريقة، يمكن رؤية التغييرات التي تطرأ عليها ومناقشتها بسهولة في طلبات السحب (pull requests) ومراجعات الشيفرة الأخرى.

مشاكل أدوات توليد المخططات الحالية

توجد العديد من الأدوات التي يمكنها توليد مخططات التبعية (dependency diagrams) من الشيفرة أثناء وقت البناء (build time)، وقد استخدمت عدداً لا بأس به منها. لكن المشكلة تبدو في أن هذه المخططات غالباً ما تبدو معقدة وغير واضحة (spaghetti-like)، حتى عندما تكون الشيفرة جيدة التصميم. كما أنها معقدة في الإعداد. يبدو من الصعب جداً ضبط مستوى التفاصيل الصحيح.

  • لا توجد طريقة لعرض الشيفرة ذات الصلة في مجموعات منطقية للمخططات عالية المستوى.
  • لا توجد طريقة أيضاً لاختيار العلاقات البرمجية الخاصة بسياق معين للمخططات منخفضة المستوى.
  • بالإضافة إلى ذلك، لا تقدم هذه الأدوات أي معلومات حول علاقات الشيفرة أثناء وقت التشغيل (run time relationships)، والتي تكون عادةً مشكلة أكبر من العلاقات أثناء وقت التصميم (design time relationships).

حل مبتكر: التوثيق من اختبارات بايثون

لالتقاط علاقات وقت التشغيل، يُعد توليد المخططات من الشيفرة قيد التشغيل هو الخيار الوحيد. ولحسن الحظ، لدينا بالفعل الكثير من الشيفرة التي تُنفذ بانتظام في شكل اختبارات. يجب أن تحتوي المستودعات (repositories) بالفعل على مجموعة جيدة من الاختبارات (مثل اختبارات الوحدات unit tests، والتكامل integration tests، والاختبارات الشاملة end-to-end tests)، ويجب أن يكون كل اختبار قصيراً وبسيطاً نسبياً. يجب أن تجسد هذه الاختبارات بالفعل تجميعات منطقية للشيفرة ومستويات معقولة من التجريد. لذا، فهي مرشح ممتاز لتوليد التوثيقات.

يتضمن الحل “تجهيز” الشيفرة التي يتم استيرادها بواسطة الاختبار (instrumenting the code). تحتفظ هذه الشيفرة المجهزة بسجل لتسلسل استدعاءات وقت التشغيل (run time call hierarchy)، وتكون قادرة على كتابة النتائج كمخطط Mermaid Markdown (وهو تقنياً مخطط تسلسل sequence diagram).

كيفية عمل الحل: مثال عملي

الشيفرة أدناه (اختبار من حزمة Python) توضح كيفية عمل ذلك. لكل اختبار موجود، تقوم بإنشاء اختبار “مغلّف” (wrapper test) يكون مسؤولاً عن تهيئة تسلسل الاستدعاءات وحفظ المخطط. إذا كان لديك الكثير من الاختبارات، فقد ترغب في استخدام مزين (decorator) لتجنب التكرار.

from docs_from_tests.instrument_call_hierarchy import instrument_and_import_package, instrument_and_import_module, initialise_call_hierarchy, finalise_call_hierarchy
from samples.hello_world_combiner import HelloWorldCombiner
import os
from pathlib import Path

# يمكنك تجهيز حزم / مجلدات بأكملها دفعة واحدة هكذا
instrument_and_import_package(os.path.join(Path(__file__).parent.absolute(), '..' , 'samples' ), 'samples' )

# يمكنك تجهيز وحدات فردية هكذا
# instrument_and_import_module('tests.blah')

# هذا هو اختبار مغلّف يقوم أيضاً بإخراج التوثيقات / مخطط التسلسل
def test_hello_world (): 
    # هذا يهيئ تسجيل تسلسل الاستدعاءات
    initialise_call_hierarchy( 'start' )

    # هذا يشغل الاختبار الفعلي
    _test_hello_world()

    # هذا ينهي تسلسل الاستدعاءات ويعيد الجذر
    root_call = finalise_call_hierarchy()

    # هذا يعيد مخطط تسلسل لتسلسل الاستدعاءات
    sequence_diagram = root_call.sequence_diagram(
        show_private_functions= False ,
        excluded_functions=[ 'HelloWorldCombiner.__init__' , ]
    )

    # هذا يكتب الـ Markdown على القرص
    sequence_diagram_filename = os.path.join(os.path.dirname(__file__), '..' , 'doc' , 'top-level-sequence-diagram.md' )
    Path(sequence_diagram_filename).write_text(sequence_diagram)

# هذا هو الاختبار الأصلي / المصدر
def _test_hello_world (): 
    assert HelloWorldCombiner().combine() == 'Hello world'

تشغيل pytest على هذه الشيفرة سيؤدي إلى تنفيذ الاختبار، وإنشاء “المخطط كشيفرة” (diagram as code) بصيغة Markdown (الموضح أدناه) في مجلد doc:

sequenceDiagram
start-->>HelloWorldCombiner.combine: calls x1
HelloWorldCombiner.combine-->>hello: calls x1
hello-->>HelloWorldCombiner.combine: returns str
HelloWorldCombiner.combine-->>world: calls x1
world-->>HelloWorldCombiner.combine: returns str
HelloWorldCombiner.combine-->>start: returns str

يظهر هذا المخطط بعد تحويله بالشكل التالي:

مخطط تسلسل Mermaid يوضح تدفق الاستدعاءات بين الدوال في مثال HelloWorldCombiner

مزامنة التوثيق مع الشيفرة

ستظهر التغييرات في المخطط ضمن سجل Git وسيتم دمجها مع الشيفرة التي سببت هذا التغيير. هذا يعني أن التغيير في الشيفرة والتغيير في المخطط مرتبطان ويمكن رؤيتهما معاً. عادةً ما يتم استبعاد الدوال الخاصة (private methods) (على الرغم من أن هذا اختياري)، ويمكنك استبعاد دوال أخرى لجعل المخطط يظهر بالشكل المطلوب. نظراً لأن تسلسل الاستدعاءات يُخزن في بنية شجرية (tree structure)، فإن استبعاد دالة معينة يستبعد أيضاً جميع الدوال الفرعية التابعة لها.

تحسين جودة الشيفرة من خلال التوثيق

نأمل أن تكون لديك بالفعل اختبارات على مستويات التجريد المناسبة (تقليدياً، اختبارات الوحدات unit tests، والتكامل integration tests، والاختبارات الشاملة end-to-end tests). هذا يسهل إنشاء المخططات على هذه المستويات. إذا لم يكن الأمر كذلك، فإن الرغبة في إنشاء مخططات جيدة يجب أن توجهك نحو إنشاء اختبارات جيدة أيضاً.

في بعض الأحيان، قد تبدو المخططات معقدة بعض الشيء، وقد ينتهي بك الأمر إلى تجاهل الكثير من الدوال. هذا يعد مؤشراً على أن الشيفرة يمكن تبسيطها على الأرجح. وفي هذه الحالة، يجب أن توجهك الرغبة في إنشاء مخططات جيدة نحو تبسيط الشيفرة.

الخلاصة

نأمل أن يلهمك هذا المقال لإنشاء وصيانة التوثيقات التي سيشكرك عليها زملاؤك في الفريق وذاتك المستقبلية! الأمر سهل للغاية. جميع الوظائف متوفرة في حزمة Python (docs-from-tests)، ويتوفر مستودع أمثلة (example repo) يوضح كيفية استخدامها.

الخلاصة التقنية

يمثل النهج الموضح في هذا المقال نقلة نوعية في إدارة توثيقات المشاريع البرمجية. فبدلاً من التعامل مع التوثيق كعبء إضافي منفصل، يتم دمجه بشكل عضوي مع عملية التطوير من خلال الاستفادة من الاختبارات الحالية. هذه الطريقة لا تضمن فقط تحديث التوثيقات باستمرار وتوافقها مع أحدث التغييرات في الشيفرة، بل إنها أيضاً تعزز من جودة الشيفرة نفسها. فالسعي لإنشاء مخططات واضحة ومفهومة يدفع المطورين بشكل غير مباشر نحو كتابة اختبارات أفضل وتصميم شيفرة أكثر بساطة وتنظيمًا. استخدام أدوات مثل Mermaid لتحويل المخططات إلى “شيفرة” يسهل تتبع التغييرات ومراجعتها ضمن أنظمة التحكم في الإصدار (version control systems)، مما يجعل التوثيق جزءاً لا يتجزأ من دورة حياة التطوير.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *