tarekamr.com — الـ Assert في Pytest مش اللي إنت فاكره!
~ / pages / الـ Assert في Pytest مش اللي إنت فاكره!

الـ Assert في Pytest مش اللي إنت فاكره!

إيه هو AST؟ وإزاي Pytest بيهاكه عشان يديك رسائل خطأ أحسن؟

تخيل معايا: إنت بتكتب unit test (اختبار وحدة) لكود جديد، الاختبار فشل، وطلعلك رسالة AssertionError بتقولك "الاتنين مش متساوين" - بس! مش عارف إيه المشكلة بالضبط، ولا فين الاختلاف!

محبط، صح؟ طب تخيل لو الرسالة بتقولك بالضبط إيه العناصر المختلفة في القائمتين، وفين الاختلاف حصل!

ده بالضبط اللي Pytest بيعمله - بس السؤال: إزاي؟!

الـ Assert العادي في بايثون: بسيط بس مش مفيد أوي

في بايثون (وفي لغات برمجة كتير)، فيه كلمة محجوزة اسمها assert - شغلتها بسيطة:

وظيفتها:

  • ✅ لو الشرط صح (True) → ما تعملش حاجة
  • ❌ لو الشرط غلط (False) → ارمي AssertionError

عبارة assert في بايثون

يعني إيه بالعربي؟ لو بتختبر إن نتيجة دالة (function) معينة صحيحة، بتستخدم assert:

assert calculate_total([100, 200, 300]) == 600  # لازم يطلع 600

المشكلة: لو الاختبار فشل، الرسالة مش بتقولك إيه المشكلة بالضبط!

مثال يوضح المشكلة

تخيل: عندك قائمتين (lists) عايز تتأكد إنهم متطابقين:

رسائل خطأ مش وصفية من assert العادي

الرسالة بتقول: "AssertionError" - وبس!

  • مش عارف فين الاختلاف
  • مش عارف إيه القيم المختلفة
  • لازم تدور بنفسك في الكود

طب شوف بقى لما نستخدم Pytest في نفس الكود:

رسائل خطأ مفصلة من Pytest

ياه! دي رسالة مفيدة فعلاً:

  • بتقولك فين الاختلاف بالضبط
  • بتوريك القيم القديمة والجديدة
  • توفر عليك وقت التدوير

السؤال الكبير: إزاي Pytest بيعمل كده؟! هو قدر يغير سلوك كلمة محجوزة في بايثون؟!

هل ممكن نعيد تعريف كلمة محجوزة في بايثون؟

السؤال ده ودّاني في رحلة عشان أفهم إزاي Pytest بيشتغل!

الفكرة: الطريقة الوحيدة إن Pytest يديك رسائل مفصلة كده، هي إنه يغير طريقة عمل الـ assert نفسها!

المشكلة: بعد بحث سريع، هتلاقي إن مفيش طريقة تعيد تعريف الكلمات المحجوزة (keywords) في بايثون!

مكتبات تانية عملت إيه؟

unittest (مكتبة الاختبارات القديمة في بايثون):

  • عارفة إنها مش تقدر تغير الـ assert
  • عملت methods جديدة بدل ما تستخدم assert
  • مثلاً: assertEqual، assertTrue، إلخ

Unittest بتعمل methods خاصة بدل assert

يعني: unittest استسلمت وعملت حل بديل!

طب Pytest؟ Pytest لسه بيستخدم assert العادي - إزاي بقى يهرب من القيد ده؟

الإجابة: لازم نفهم حاجة اسمها Abstract Syntax Tree (AST)!

الشجرة النحوية المجردة (AST): إزاي بايثون بتفهم الكود؟

تخيل معايا لما بايثون بتشغل الكود بتاعك:

الخطوة الأولى: تقسيم لـ Tokens (رموز)

بايثون بتقسم الكود لـ رموز صغيرة:

  • كلمات محجوزة: if، for، while
  • أسماء متغيرات: x، total، user_name
  • أرقام: 3.14، 42، 100
  • عمليات: ==، +، *

الخطوة التانية: تنظيم في شجرة (AST)

الرموز دي بتتنظم في شجرة بتوضح:

  • مين يتنفذ الأول؟
  • مين يتبع مين؟
  • إيه أولوية العمليات؟

مثال: شوف المعادلتين دول:

مثال AST مع المعادلات

1 + 2 * 3  # النتيجة: 7
(1 + 2) * 3  # النتيجة: 9

ليه النتايج مختلفة؟ لأن الـ AST مختلف!

  • في الأولى: الضرب الأول، بعدين الجمع
  • في التانية: الجمع الأول (بسبب الأقواس)، بعدين الضرب

يعني: الـ AST هي اللي بتحدد ترتيب تنفيذ الكود حسب قواعد اللغة!

الخطوة التالتة: تحويل لـ Bytecode

الشجرة دي بتتحول لـ bytecode (كود بايت) - ده اللي بايثون بتفهمه فعلاً وبتشغله.

الحيلة السحرية: بايثون بتسمحلك تعدل الـ AST!

ده المفتاح: بايثون بتديك hooks (نقاط دخول) في عملية import (استيراد الملفات)!

معنى كده:

  • لما بايثون بتستورد ملف
  • تقدر تتدخل في العملية
  • تقدر تغير الكود قبل ما يتشغل
  • تقدر تعدل الـ AST على الطاير!

ده بالضبط اللي Pytest بيعمله! شوف جزء من الكود الأصلي لـ Pytest:

كود Pytest الأصلي

باختصار: Pytest بيعمل كده:

  1. لما يستورد ملف الاختبار بتاعك
  2. بيقرا الـ AST بتاعه
  3. بيلاقي كل assert في الكود
  4. بيستبدلها بـ if statement مع كود بيطبع تفاصيل الخطأ!

والجزء الجميل: بايثون جاهزة بكده! فيه module مدمج اسمه ast بيساعدك تعمل كل ده!

إزاي نستخدم وحدة AST في بايثون؟

خلينا نشوف بالكود إزاي نلعب بالـ AST:

خطوة 1: تحويل كود لـ AST

import ast

code = "x = 1 + 2 * 3"
tree = ast.parse(code)  # حول الكود لشجرة

خطوة 2: عرض الشجرة

print(ast.dump(tree))  # اطبع الشجرة بشكل قابل للقراءة

عرض AST الكود

خطوة 3: تعديل الشجرة

بايثون بتوفرلك ast.NodeTransformer - class جاهز يساعدك تعدل الشجرة بسهولة!

يعني: مش محتاج تكتب كود معقد عشان تعبر الشجرة - بايثون مجهزاك!

وقت التطبيق: نعمل Pytest مصغر!

تخيل معايا: عايزين نعمل حاجة شبه Pytest - بس بدل ما نعدل assert، هنعمل حاجة أبسط وأوضح!

الفكرة: هنعمل كود يقرب الأرقام تلقائياً في الاختبارات!

الملف الأول: my_test.py

عندنا ملف فيه اختبار بسيط:

ملف my_test.py

def test_equal():
    assert 3.14159 == 3.14  # دي هتفشل - الرقمين مش متطابقين!

المشكلة: الاختبار ده هيفشل لأن 3.14159 مش بالضبط 3.14!

الملف التاني: tester.py (بدون تعديل)

لو استوردنا my_test.py عادي:

ملف tester.py بدون تعديل

from my_test import test_equal
test_equal()  # AssertionError - فشل!

النتيجة: ❌ AssertionError - فشل!

الحل: نعدل الـ AST قبل الاستيراد!

الخطة:

  1. نعترض عملية الاستيراد (import)
  2. نقرا محتوى my_test.py
  3. نعمل AST للكود
  4. نستبدل كل الأرقام العشرية بـ round() - يقرب الرقم
  5. نحفظ الـ AST المعدل ونشغله!

الكود: دالة بتعدل الـ AST:

دالة auto_round في tester.py

شرح الكود:

  1. ast.parse: حول الكود لـ AST
  2. NodeTransformer: عدّل كل رقم عشري → حطه جوا round()
  3. ast.unparse: حول الـ AST رجوع لكود عادي

الكود: hook للاستيراد:

Hook الاستيراد في tester.py

شرح الـ Hook:

  • لما بايثون تستورد my_test.py
  • الـ hook بيشتغل قبل ما الملف يتحمل
  • بيستخدم auto_round عشان يعدل الكود
  • بعدين بيحمل الكود المعدل!

النتيجة النهائية:

# الكود الأصلي كان:
assert 3.14159 == 3.14

# بعد التعديل بقى:
assert round(3.14159) == round(3.14)

# النتيجة: ✅ نجح! - لأن الاتنين بيطلعوا 3

الخلاصة: فهمنا إيه؟

رحلة اليوم علمتنا:

1. المشكلة الأصلية

  • assert العادي في بايثون بيديك رسائل مش مفيدة
  • مش بيقولك إيه المشكلة بالضبط
  • Pytest بيحل المشكلة دي - إزاي؟

2. الحل السحري: AST

  • Abstract Syntax Tree (الشجرة النحوية المجردة)
  • بايثون بتحول كل كود لـ شجرة
  • الشجرة دي بتوضح ترتيب التنفيذ وعلاقة الأجزاء ببعضها

3. القوة الخفية في بايثون

  • بايثون بتسمحلك تعدل الـ AST قبل التنفيذ!
  • تقدر تعترض عملية الاستيراد (import)
  • تقدر تغير الكود على الطاير!

4. Pytest استغل القوة دي

  • بيعترض استيراد ملفات الاختبارات
  • بيعدل الـ assert statements
  • بيستبدلها بـ if statements بترسائل مفصلة!

5. التطبيق العملي

  • عملنا مثال بسيط: auto-round
  • فهمنا إزاي نستخدم ast.parse
  • فهمنا إزاي نعمل import hooks

الخلاصة: Pytest مش سحر - هو استغلال ذكي لميزات بايثون المدمجة!

تقدر تشوف الكود الكامل هنا لو عايز تجربه بنفسك!


طارق عمرو، 13 يناير 2023

الترجمات: [EN] [NL]   [↓ .md]
~ / pages / الـ Assert في Pytest مش اللي إنت فاكره!
>