دليل استخدام PyTest: ما هو PyTest؟ (مع أمثلة)

⚡ ملخص ذكي

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

  • 🔘 تركيب: يقوم الأمر pip install pytest بإضافة الإطار، ويؤكد الأمر py.test -h الإعداد عن طريق طباعة مخرجات المساعدة.
  • ☑️ اكتشاف الاختبار: يقوم Pytest تلقائيًا بجمع الملفات المسماة test_*.py أو *_test.py والأساليب التي تبدأ أسماؤها بـ test.
  • ✅ التأكيدات: تُبلغ عبارات التأكيد البسيطة عن خطأ تأكيد واضح مع القيم التي تمت مقارنتها كلما فشل الفحص.
  • 🧪 تركيبات: يوفر الديكور @pytest.fixture بيانات إعداد قابلة لإعادة الاستخدام يتم مشاركتها عبر الملفات من خلال ملف conftest.py.
  • 🛠️ عمليات تشغيل انتقائية: تقوم العلامات، ومرشح السلسلة الفرعية -k، والمعاملات بتشغيل مجموعات فرعية مستهدفة أو نفس الاختبار عبر العديد من المدخلات.
  • 🤖 سير عمل الذكاء الاصطناعي: تستخدم فرق التعلم الآلي pytest للتحقق من صحة مسارات البيانات ومخرجات النماذج، ويمكن لـ Copilot صياغة حالات الاختبار.

برنامج تعليمي لـ Pytest

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

ما هو Pytest؟

بيتيست هو إطار عمل للاختبار يتيح لك كتابة كود الاختبار باستخدام Python لغة برمجة. تساعدك على كتابة حالات اختبار بسيطة وقابلة للتطوير لقواعد البيانات، وواجهات برمجة التطبيقات، وحتى واجهات المستخدم. يُستخدم Pytest بشكل أساسي لكتابة اختبارات واجهات برمجة التطبيقات، وهو قابل للتطوير من اختبارات الوحدات البسيطة إلى الاختبارات الوظيفية المعقدة.

لماذا نستخدم Pytest؟

من مزايا pytest ما يلي:

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

كيفية تثبيت Pytest

فيما يلي خطوات تثبيت pytest:

الخطوة 1) يمكنك تثبيت pytest باستخدام الأمر أدناه.

pip install pytest==2.9.1

بمجرد اكتمال التثبيت، يمكنك تأكيده باستخدام الأمر التالي.

py.test -h

سيؤدي هذا إلى عرض المساعدة.

قم بتثبيت pytest واطلع على مخرجات المساعدة

مثال أساسي أولي لـ Pytest

الآن، ستتعلم كيفية استخدام pytest من خلال مثال أساسي لـ pytest.

أنشئ مجلدًا باسم study_pytest. ستُنشئ ملفات الاختبار داخل هذا المجلد. انتقل إلى هذا المجلد من خلال سطر الأوامر. أنشئ ملفًا باسم test_sample1.py داخل المجلد.

أنشئ أول ملف اختبار أساسي باستخدام pytest

أضف الكود أدناه واحفظه.

import pytest
def test_file1_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed"
def test_file1_method2():
	x=5
	y=6
	assert x+1 == y,"test failed" 

قم بتشغيل الاختبار باستخدام الأمر أدناه.

py.test

ستحصل على الناتج كما يلي.

test_sample1.py F.
============================================== FAILURES ========================================
____________________________________________ test_sample1 ______________________________________
    def test_file1_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed"
E       AssertionError: test failed
E       assert 5 == 6
test_sample1.py:6: AssertionError

مخرجات فشل اختبار Pytest

هنا، في الملف test_sample1.py، يشير الحرف F إلى الفشل، بينما تشير النقطة (.) إلى النجاح. في قسم حالات الفشل، يمكنك رؤية الطريقة (الطرق) الفاشلة وسطر الفشل. هنا، x==y تعني 5==6، وهذا خطأ.

بعد ذلك، ستتعرف على التأكيدات في pytest.

التأكيدات في Pytest

تُعدّ تأكيدات Pytest عمليات تحقق تُرجع إما حالة "صحيح" أو "خطأ". في Pytest، إذا فشل تأكيد في دالة اختبار، يتوقف تنفيذ تلك الدالة عند تلك النقطة. لا يتم تنفيذ باقي التعليمات البرمجية في دالة الاختبار، ويستمر Pytest مع دالة الاختبار التالية.

أمثلة على تأكيدات Pytest:

assert "hello" == "Hai" is an assertion failure.
assert 4==4 is a successful assertion
assert True is a successful assertion
assert False is an assertion failure.

النظر في ما يلي.

assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

ضع هذا الكود في الدالة test_file1_method1() بدلاً من التأكيد أدناه.

assert x == y,"test failed"

تشغيل الاختبار سوف يعطي الفشل كما خطأ التأكيد: فشل الاختبار x=5 y=6.

كيف يتعرف برنامج Pytest على ملفات الاختبار وطرق الاختبار

بشكل افتراضي، لا يتعرف pytest إلا على أسماء الملفات التي تبدأ بـ امتحان_ أو تنتهي بـ _اختبار كملفات الاختبار. مع ذلك، يمكننا ذكر أسماء ملفات أخرى صراحةً (سيتم شرح ذلك لاحقًا). يتطلب Pytest أن تبدأ أسماء طرق الاختبار بـ تجربه بالعربييتم تجاهل جميع أسماء الطرق الأخرى، حتى لو طلبنا صراحةً تشغيل تلك الطرق.

اطلع على بعض الأمثلة لأسماء ملفات pytest الصحيحة وغير الصحيحة.

test_login.py - valid
login_test.py - valid
testlogin.py -invalid
logintest.py -invalid

ملاحظة: نعم، يمكننا أن نطلب صراحةً من pytest اختيار testlogin.py و logintest.py.

اطلع على بعض الأمثلة على طرق اختبار pytest الصحيحة وغير الصحيحة.

def test_file1_method1(): - valid
def testfile1_method1(): - valid
def file1_method1(): - invalid	

ملاحظة: حتى لو ذكرنا صراحةً file1_method1()، فلن يقوم pytest بتشغيل هذه الطريقة.

تشغيل اختبارات متعددة من ملف معين وملفات متعددة

يوجد حاليًا داخل المجلد study_pytest ملف باسم test_sample1.py. لنفترض أن لدينا عدة ملفات، مثل test_sample2.py و test_sample3.py. لتشغيل جميع الاختبارات من جميع الملفات في المجلد والمجلدات الفرعية، يكفي تشغيل أمر pytest.

py.test

سيؤدي هذا إلى تشغيل جميع أسماء الملفات التي تبدأ بـ test_ وأسماء الملفات التي تنتهي بـ _test في ذلك المجلد والمجلدات الفرعية الموجودة أسفل ذلك المجلد.

لتشغيل الاختبارات من ملف معين فقط، يمكننا استخدام py.test .

py.test test_sample1.py

تشغيل جزء من الاختبار الكامل باستخدام Pytest

أحيانًا لا نرغب في تشغيل مجموعة الاختبارات بأكملها. يسمح لنا Pytest بتشغيل اختبارات محددة. يمكننا القيام بذلك بطريقتين:

  • الحبوping أسماء الاختبارات عن طريق مطابقة السلاسل الفرعية.
  • الحبوping من الاختبارات بواسطة المؤشرات.

لدينا بالفعل ملف test_sample1.py. أنشئ ملفًا باسم test_sample2.py وأضف إليه الكود التالي.

def test_file2_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
def test_file2_method2():
	x=5
	y=6
	assert x+1 == y,"test failed"

لذا لدينا ما يلي حاليًا.

• test_sample1.py
• test_file1_method1()
• test_file1_method2()
• test_sample2.py
• test_file2_method1()
• test_file2_method2()

الخيار 1) إجراء الاختبارات عن طريق مطابقة السلاسل الفرعية

هنا، لتشغيل جميع الاختبارات التي تحتوي على method1 في اسمها، علينا تشغيل الأمر أدناه.

py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity

لذا فإن تشغيل الأمر py.test -k method1 -v سيعطيك النتيجة التالية.

test_sample2.py::test_file2_method1 FAILED
test_sample1.py::test_file1_method1 FAILED

============================================== FAILURES ==============================================
_________________________________________ test_file2_method1 _________________________________________
    def test_file2_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
test_sample2.py:5: AssertionError

_________________________________________ test_file1_method1 _________________________________________
    @pytest.mark.only
    def test_file1_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
test_sample1.py:8: AssertionError

================================= 2 tests deselected by '-kmethod1' ==================================
=============================== 2 failed, 2 deselected in 0.02 seconds ===============================

هنا يمكنك أن ترى، قرب النهاية، تم إلغاء تحديد اختبارين بواسطة "-kmethod2"، وهما test_file1_method2 و test_file2_method2.

جرب الجري بمجموعات مختلفة مثل ما يلي.

py.test -k method -v - will run all the four methods
py.test -k methods -v – will not run any test as there is no test name matches the substring 'methods'

الخيار 2) إجراء الاختبارات بواسطة العلامات

يُتيح لنا Pytest تحديد سمات متنوعة لأساليب الاختبار باستخدام علامات pytest، @pytest.mark. لاستخدام هذه العلامات في ملف الاختبار، نحتاج إلى استيراد pytest إلى ملفات الاختبار. سنُطبّق هنا أسماء علامات مختلفة على أساليب الاختبار، ونُشغّل اختبارات مُحددة بناءً على هذه الأسماء. يُمكننا تعريف العلامات لكل اسم اختبار باستخدام ما يلي.

@pytest.mark.<name>.			

نُعرّف مجموعتي العلامات set1 و set2 في دوال الاختبار، وسنُشغّل الاختبار باستخدام اسمي هاتين المجموعتين. حدّث ملفات الاختبار باستخدام الكود التالي.

test_sample1.py

import pytest
@pytest.mark.set1
def test_file1_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

@pytest.mark.set2
def test_file1_method2():
	x=5
	y=6
	assert x+1 == y,"test failed"

test_sample2.py

import pytest
@pytest.mark.set1
def test_file2_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

@pytest.mark.set1
def test_file2_method2():
	x=5
	y=6
	assert x+1 == y,"test failed"

يمكننا تشغيل الاختبار المحدد باستخدام الأمر أدناه.

py.test -m <name>
-m <name> mentions the marker name

شغّل الأمر py.test -m set1. سيؤدي هذا إلى تشغيل الدوال test_file1_method1 و test_file2_method1 و test_file2_method2. أما تشغيل الأمر py.test -m set2 فسيؤدي إلى تشغيل الدالة test_file1_method2.

تشغيل الاختبارات بالتوازي باستخدام Pytest

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

pip install pytest-xdist

قم بتشغيل الاختبارات بالتوازي باستخدام pytest-xdist

يمكنك تشغيل الاختبارات الآن باستخدام الأمر التالي.

py.test -n 4

هنا، -ن يُشغّل الاختبارات باستخدام عدة عمال. في الأمر أعلاه، سيكون هناك أربعة عمال لتشغيل الاختبارات.

تركيبات بيتيست

تُستخدم التجهيزات البرمجية عندما نرغب في تشغيل جزء من التعليمات البرمجية قبل كل دالة اختبار. لذا، بدلاً من تكرار نفس التعليمات البرمجية في كل اختبار، نقوم بتعريف التجهيزات البرمجية. عادةً ما تُستخدم التجهيزات البرمجية لتهيئة اتصالات قاعدة البيانات، وتمرير البيانات الأساسية. URLوهكذا. يتم تمييز طريقة ما كأداة اختبار pytest عن طريق وضع علامة عليها بما يلي.

@pytest.fixture

يمكن لطريقة الاختبار استخدام أداة اختبار pytest عن طريق تحديدها كمعامل إدخال. أنشئ ملفًا جديدًا باسم test_basic_fixture.py يحتوي على الكود التالي.

import pytest
@pytest.fixture
def supply_AA_BB_CC():
	aa=25
	bb =35
	cc=45
	return [aa,bb,cc]

def test_comparewithAA(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"

def test_comparewithBB(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed"

def test_comparewithCC(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"

هنا:

  • لدينا وحدة تركيبية باسم supply_AA_BB_CC. تُرجع هذه الطريقة قائمة بثلاث قيم.
  • لدينا ثلاث طرق اختبار للمقارنة مع كل قيمة من القيم.

تحتوي كل دالة من دوال الاختبار على وسيط إدخال يتطابق اسمه مع إحدى الأدوات المتاحة. ثم يستدعي Pytest دالة الأداة المناسبة، وتُخزَّن القيم المُعادة في وسيط الإدخال، وهي هنا القائمة [25,35,45]. تُستخدم عناصر القائمة بعد ذلك في دوال الاختبار للمقارنة. الآن، شغّل الاختبار وشاهد النتيجة.

 py.test test_basic_fixture
test_basic_fixture.py::test_comparewithAA FAILED                                                                                                                                                                                       
test_basic_fixture.py::test_comparewithBB PASSED                                                                                                                                                                                       
test_basic_fixture.py::test_comparewithCC FAILED
                                                                                                                                                                                       
============================================== FAILURES ==============================================
_________________________________________ test_comparewithAA _________________________________________
supply_AA_BB_CC = [25, 35, 45]
    def test_comparewithAA(supply_AA_BB_CC):
    	zz=35
>   	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"
E    AssertionError: aa and zz comparison failed
E    assert 25 == 35
test_basic_fixture.py:10: AssertionError

_________________________________________ test_comparewithCC _________________________________________
supply_AA_BB_CC = [25, 35, 45]
    def test_comparewithCC(supply_AA_BB_CC):
    	zz=35
>   	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
E    AssertionError: cc and zz comparison failed
E    assert 45 == 35
test_basic_fixture.py:16: AssertionError
================================= 2 failed, 1 passed in 0.05 seconds =================================

نجح الاختبار test_comparewithBB، لأن zz=BB=35، وفشل الاختباران المتبقيان.

يقتصر نطاق دالة fixture على ملف الاختبار الذي تم تعريفها فيه. إذا حاولنا الوصول إلى fixture في ملف اختبار آخر، فسنحصل على خطأ يشير إلى fixture. لم يتم العثور على 'supply_AA_BB_CC' بالنسبة لطرق الاختبار في الملفات الأخرى.

لاستخدام نفس الإعداد مع ملفات اختبار متعددة، نُنشئ دوال الإعداد في ملف يُسمى conftest.py. لنرى ذلك من خلال مثال pytest التالي. أنشئ ثلاثة ملفات: conftest.py و test_basic_fixture.py و test_basic_fixture2.py، باستخدام الكود التالي.

conftest.py

import pytest
@pytest.fixture
def supply_AA_BB_CC():
	aa=25
	bb =35
	cc=45
	return [aa,bb,cc]

test_basic_fixture.py

import pytest
def test_comparewithAA(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"

def test_comparewithBB(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed"

def test_comparewithCC(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"

test_basic_fixture2.py

import pytest
def test_comparewithAA_file2(supply_AA_BB_CC):
	zz=25
	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"

def test_comparewithBB_file2(supply_AA_BB_CC):
	zz=25
	assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed"

def test_comparewithCC_file2(supply_AA_BB_CC):
	zz=25
	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"

سيبحث Pytest عن عنصر الاختبار في ملف الاختبار أولاً، وإذا لم يجده، فسيبحث عنه في ملف conftest.py. شغّل الاختبار باستخدام الأمر py.test -k test_comparewith -v للحصول على النتيجة الموضحة أدناه.

test_basic_fixture.py::test_comparewithAA FAILED  
test_basic_fixture.py::test_comparewithBB PASSED 
test_basic_fixture.py::test_comparewithCC FAILED 
test_basic_fixture2.py::test_comparewithAA_file2 PASSED 
test_basic_fixture2.py::test_comparewithBB_file2 FAILED 
test_basic_fixture2.py::test_comparewithCC_file2 FAILED

اختبار Pytest المعلمات

الغرض من استخدام المعاملات في الاختبار هو تشغيله على مجموعات متعددة من الوسائط. يمكننا فعل ذلك باستخدام @pytest.mark.parametrize. سنرى ذلك في مثال pytest التالي. هنا، سنمرر ثلاثة وسائط إلى دالة اختبار. ستقوم هذه الدالة بجمع الوسيطين الأولين ومقارنة النتيجة بالوسيط الثالث. أنشئ ملف الاختبار test_addition.py باستخدام الكود التالي.

import pytest
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def test_add(input1, input2, output):
	assert input1+input2 == output,"failed"

تقبل دالة الاختبار هنا ثلاثة وسائط: input1 و input2 و output. تقوم الدالة بجمع input1 و input2 ومقارنة المجموع مع output. لنُجرِ الاختبار باستخدام الأمر py.test -k test_add -v ونرى النتيجة.

test_addition.py::test_add[5-5-10] PASSED                                                                                                                                                                                              
test_addition.py::test_add[3-5-12] FAILED                                                                                                                                                                                              
============================================== FAILURES ==============================================
__________________________________________ test_add[3-5-12] __________________________________________
input1 = 3, input2 = 5, output = 12
    @pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
    def test_add(input1, input2, output):
>   	assert input1+input2 == output,"failed"
E    AssertionError: failed
E    assert (3 + 5) == 12
test_addition.py:5: AssertionError

يمكنك أن ترى أن الاختبارات قد أجريت مرتين، إحداهما للتحقق من أن 5+5 ==10 والأخرى للتحقق من أن 3+5 ==12.

test_addition.py::test_add[5-5-10] تم اجتيازه

test_addition.py::test_add[3-5-12] فشل

اختبارات Pytest Xfail و Skip

ستكون هناك بعض الحالات التي لا نرغب فيها بتنفيذ اختبار، أو حالة اختبار لا يكون ذلك ذا صلة في وقت معين. في هذه الحالات، لدينا خيار إما إلغاء الاختبار أو تخطي الاختبارات. سيتم تنفيذ الاختبار الذي تم إلغاؤه، لكنه لن يُحتسب ضمن الاختبارات الفاشلة أو الناجحة. لن يكون هناك أي tracيتم عرض رسالة خطأ في حال فشل الاختبار. يمكننا استخدام خاصية xfail لاختبارات الاختبار باستخدام @pytest.mark.xfail. تخطيping يعني وجود علامة "-" في خانة "الاختبار" أن الاختبار لن يُنفذ. يمكننا تخطي الاختبارات باستخدام @pytest.mark.skip. عدّل ملف test_addition.py باستخدام الكود التالي.

import pytest
@pytest.mark.skip
def test_add_1():
	assert 100+200 == 400,"failed"

@pytest.mark.skip
def test_add_2():
	assert 100+200 == 300,"failed"

@pytest.mark.xfail
def test_add_3():
	assert 15+13 == 28,"failed"

@pytest.mark.xfail
def test_add_4():
	assert 15+13 == 100,"failed"

def test_add_5():
	assert 3+2 == 5,"failed"

def test_add_6():
	assert 3+2 == 6,"failed"

هنا:

  • تم تخطي test_add_1 وtest_add_2 ولن يتم تنفيذهما.
  • تم تصنيف الاختبارين test_add_3 و test_add_4 على أنهما فشلا. سيتم تنفيذ هذين الاختبارين وسيكونان جزءًا من اختبارات الفشل (xfailed) أو اختبارات النجاح (xpassed). لن يكون هناك أي tracاسترداد الأموال في حالة الفشل.
  • سيتم تنفيذ الاختبارين test_add_5 و test_add_6، وسيُبلغ الاختبار test_add_6 عن فشل العملية. traceback while test_add_5 pass.

قم بتنفيذ الاختبار باستخدام الأمر py.test test_addition.py -v وشاهد النتيجة.

test_addition.py::test_add_1 SKIPPED
test_addition.py::test_add_2 SKIPPED
test_addition.py::test_add_3 XPASS
test_addition.py::test_add_4 xfail
test_addition.py::test_add_5 PASSED
test_addition.py::test_add_6 FAILED

============================================== FAILURES ==============================================
_____________________________________________ test_add_6 _____________________________________________
    def test_add_6():
>   	assert 3+2 == 6,"failed"
E    AssertionError: failed
E    assert (3 + 2) == 6
test_addition.py:24: AssertionError

================ 1 failed, 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.07 seconds =================

النتائج بتنسيق XML

يمكننا إنشاء نتائج الاختبار بصيغة XML، والتي يمكننا إرسالها إلى خوادم التكامل المستمر لمزيد من المعالجة. يتم ذلك باستخدام الأمر `py.test test_sample1.py -v –junitxml=”result.xml”.` سيسجل الملف `result.xml` نتيجة تنفيذ الاختبار. تجد أدناه مثالاً على ملف `result.xml`.

<?xml version="1.0" encoding="UTF-8"?>
<testsuite errors="0" failures="1" name="pytest" skips="0" tests="2" time="0.046">
   <testcase classname="test_sample1" file="test_sample1.py" line="3" name="test_file1_method1" time="0.001384973526">
     <failure message="AssertionError:test failed because x=5 y=6 assert 5 ==6">
    @pytest.mark.set1
    def test_file1_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
         test_sample1.py:9: AssertionError
    </failure>
   </testcase>
   <testcase classname="test_sample1" file="test_sample1.py" line="10" name="test_file1_method2" time="0.000830173492432" />
</testsuite>

من نرى اختبارين، فشل أحدهما. أسفل ذلك، يمكنك الاطلاع على تفاصيل كل اختبار تم تنفيذه. علامة.

إطار عمل Pytest لاختبار واجهة برمجة التطبيقات (API).

سنقوم الآن بإنشاء إطار عمل pytest صغير لاختبار واجهة برمجة التطبيقات (API). واجهة برمجة التطبيقات المستخدمة هنا مجانية من reqres.inيوفر هذا الموقع الإلكتروني واجهة برمجة تطبيقات قابلة للاختبار فقط، ولا يخزن بياناتنا. سنكتب هنا بعض الاختبارات لما يلي:

  • قائمة ببعض المستخدمين.
  • تسجيل الدخول باستخدام حسابات المستخدمين.

أنشئ الملفات التالية باستخدام الكود المُعطى. أولًا، يحتوي ملف conftest.py على مُلحق يُوفّر الأساس. URL لجميع طرق الاختبار.

import pytest
@pytest.fixture
def supply_url():
	return "https://reqres.in/api"

بعد ذلك، يحتوي الملف test_list_user.py على طرق الاختبار لعرض قائمة المستخدمين الصالحين وغير الصالحين.

  • تقوم الدالة test_list_valid_user باختبار جلب بيانات المستخدم الصالحة والتحقق من الاستجابة.
  • تقوم الدالة test_list_invaliduser باختبار جلب بيانات المستخدم غير الصالحة والتحقق من الاستجابة.
import pytest
import requests
import json
@pytest.mark.parametrize("userid, firstname",[(1,"George"),(2,"Janet")])
def test_list_valid_user(supply_url,userid,firstname):
	url = supply_url + "/users/" + str(userid)
	resp = requests.get(url)
	j = json.loads(resp.text)
	assert resp.status_code == 200, resp.text
	assert j['data']['id'] == userid, resp.text
	assert j['data']['first_name'] == firstname, resp.text

def test_list_invaliduser(supply_url):
	url = supply_url + "/users/50"
	resp = requests.get(url)
	assert resp.status_code == 404, resp.text

ثم، يحتوي ملف test_login_user.py على طرق الاختبار لاختبار وظيفة تسجيل الدخول.

  • تقوم الدالة test_login_valid باختبار محاولة تسجيل الدخول الصحيحة باستخدام البريد الإلكتروني وكلمة المرور.
  • يختبر الأمر test_login_no_password محاولة تسجيل الدخول غير الصحيحة بدون إدخال كلمة مرور.
  • يختبر الأمر test_login_no_email محاولة تسجيل الدخول غير الصالحة بدون إرسال بريد إلكتروني.
import pytest
import requests
import json
def test_login_valid(supply_url):
	url = supply_url + "/login/" 
	data = {'email':'test@test.com','password':'something'}
	resp = requests.post(url, data=data)
	j = json.loads(resp.text)
	assert resp.status_code == 200, resp.text
	assert j['token'] == "QpwL5tke4Pnpja7X", resp.text

def test_login_no_password(supply_url):
	url = supply_url + "/login/" 
	data = {'email':'test@test.com'}
	resp = requests.post(url, data=data)
	j = json.loads(resp.text)
	assert resp.status_code == 400, resp.text
	assert j['error'] == "Missing password", resp.text

def test_login_no_email(supply_url):
	url = supply_url + "/login/" 
	data = {}
	resp = requests.post(url, data=data)
	j = json.loads(resp.text)
	assert resp.status_code == 400, resp.text
	assert j['error'] == "Missing email or username", resp.text

قم بتشغيل الاختبار باستخدام py.test -v وشاهد النتيجة كما يلي.

test_list_user.py::test_list_valid_user[1-George] PASSED                                                                                                                                                                               
test_list_user.py::test_list_valid_user[2-Janet] PASSED                                                                                                                                                                                
test_list_user.py::test_list_invaliduser PASSED                                                                                                                                                                                        
test_login_user.py::test_login_valid PASSED                                                                                                                                                                                            
test_login_user.py::test_login_no_password PASSED                                                                                                                                                                                      
test_login_user.py::test_login_no_email PASSED

قم بتحديث الاختبارات وجرّب مخرجات متنوعة.

الأسئلة الشائعة

كلاهما يركض Python يستخدم Pytest عبارات assert بسيطة، ويحتاج إلى كتابة أقل للبرامج المتكررة، ويوفر أدوات التثبيت والعلامات والمعاملات. يأتي Unittest مع المكتبة القياسية ويتبع أسلوب xUnit القائم على الفئات. مع ذلك، لا يزال بإمكان Pytest تشغيل حالات اختبار Unittest الموجودة.

استخدم صيغة مُعرّف العقدة مع نقطتين رأسيتين: pytest test_sample1.py::test_file1_method1. هذا يُشغّل تلك الدالة فقط. يمكنك أيضًا استهداف ملف كامل (pytest test_sample1.py) أو التصفية حسب الاسم باستخدام الخيار -k.

قم بتثبيت إضافة pytest-cov، ثم شغّل الأمر pytest –cov=your_package. سيعرض لك هذا الأمر نسبة الأسطر التي تم تنفيذها بواسطة اختباراتك، كما أن الخيار –cov-report=html يُنتج تقريرًا قابلاً للتصفح يُبرز الأسطر التي لم يتم اختبارها.

يتحكم نطاق الاختبار في عدد مرات تشغيله: دالة (افتراضيًا)، فئة، وحدة، حزمة، أو جلسة. يمكنك ضبطه باستخدام @pytest.fixture(scope=”module”). تُعيد النطاقات الأوسع استخدام الإعدادات المكلفة، مثل اتصال قاعدة البيانات، عبر العديد من الاختبارات.

شغّل الأمر pytest -x للتوقف فورًا بعد أول اختبار فاشل، أو pytest –maxfail=2 للتوقف بعد عدد محدد من حالات الفشل. هذا يُقلل من وقت الاستجابة عند تصحيح مجموعة اختبارات معطوبة.

يلتقط Pytest مخرجات النظام افتراضيًا. أضف الخيار -s (pytest -s) لتعطيل الالتقاط وعرض عبارات الطباعة مباشرةً. يضيف الخيار -v مخرجات تفصيلية لكل اختبار، تتضمن اسم كل اختبار ونتيجته.

تستخدم فرق التعلم الآلي pytest للتحقق من صحة مسارات تحميل البيانات، وتحويلات الميزات، ومخرجات النماذج. توفر أدوات الاختبار مجموعات بيانات نموذجية، وتتحقق من معلمات العديد من المدخلات، مما يضمن بقاء الأجزاء الذكية من النظام موثوقة مع تغير الكود.

نعم. يمكن لـ GitHub Copilot ومساعدي الذكاء الاصطناعي الوكيلين إنشاء وظائف pytest، والتركيبات، والحالات ذات المعلمات من خلال مطالبة قصيرة أو وظيفة موجودة. Revراجع التأكيدات المُولَّدة، لأن الذكاء الاصطناعي قد يغفل الحالات الشاذة ويُسيء تقدير القيم المتوقعة.

تلخيص هذه التدوينة بـ: