بيثون اختبار مع pytest. الفصل 2 ، وظائف اختبار الكتابة

رجوع التالي


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



الأمثلة في هذا الكتاب مكتوبة باستخدام Python 3.6 و pytest 3.2. يدعم pytest 3.2 Python 2.6 و 2.7 و Python 3.3+.


تتوفر شفرة المصدر لمشروع المهام ، وكذلك لجميع الاختبارات الموضحة في هذا الكتاب ، على الرابط في صفحة الويب للكتاب على pragprog.com . لا تحتاج إلى تنزيل الكود المصدري لفهم كود الاختبار ؛ يتم تقديم رمز الاختبار في شكل مناسب في الأمثلة. ولكن من أجل متابعة مهام المشروع ، أو تكييف أمثلة الاختبار لاختبار المشروع الخاص بك (يديك غير مقيدة!) ، يجب عليك الانتقال إلى صفحة الويب الخاصة بالكتاب وتنزيل العمل. هناك ، على صفحة الكتاب على شبكة الإنترنت ، هناك رابط لرسائل خطأ ومنتدى مناقشة .

تحت المفسد هي قائمة من المقالات في هذه السلسلة.



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


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


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


ملاحظة المترجم: إذا كنت تستخدم Python 3.5 أو 3.6 ، فعندما تقوم بإجراء الاختبارات في الفصل 2 ، قد تتلقى رسائل مثل هذا

يتم التعامل مع هذه المشكلة عن طريق إصلاح ...\code\tasks_proj\src\tasks\tasksdb_tinydb.py وإعادة تثبيت حزمة المهام
 $ cd /path/to/code $ pip install ./tasks_proj/` 


eids إلى doc_ids eids المعلمات المسماة على doc_ids و eid على doc_id في الوحدة النمطية ...\code\tasks_proj\src\tasks\tasksdb_tinydb.py

شرح #83783 هنا.

اختبار الحزمة


لمعرفة كيفية كتابة وظائف الاختبار لحزمة Python ، سوف نستخدم مشروع Tasks عينة كما هو موضح في مشروع Tasks في الصفحة xii. المهام هي حزمة Python تتضمن أداة سطر أوامر بنفس اسم المهمة.


يقدم الملحق 4 ، تغليف وتوزيع مشاريع Python في الصفحة 175 ، شرحًا لكيفية توزيع مشاريعك محليًا داخل فريق صغير أو عالميًا من خلال PyPI ، لذلك لن أخوض في تفاصيل حول كيفية القيام بذلك ؛ ومع ذلك ، دعونا نلقي نظرة سريعة على ما هو موجود في مشروع "المهام" وكيف تتناسب الملفات المختلفة مع سجل الاختبار لهذا المشروع.


فيما يلي بنية ملف مشروع المهام:


 tasks_proj/ ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── setup.py ├── src │ └── tasks │ ├── __init__.py │ ├── api.py │ ├── cli.py │ ├── config.py │ ├── tasksdb_pymongo.py │ └── tasksdb_tinydb.py └── tests ├── conftest.py ├── pytest.ini ├── func │ ├── __init__.py │ ├── test_add.py │ └── ... └── unit ├── __init__.py ├── test_task.py └── ... 

لقد قمت بتضمين قائمة كاملة بالمشروع (باستثناء القائمة الكاملة لملفات الاختبار) للإشارة إلى مدى ملاءمة الاختبارات لبقية المشروع والإشارة إلى عدة ملفات أساسية للاختبار ، وهي conftest.py ، pytest.ini ، ومختلف __init__.py ملفات __init__.py و setup.py .


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


جميع الملفات ذات المستوى الأعلى ، CHANGELOG.rst ، LICENSE ، README.rst ، MANIFEST.in ، و setup.py ، تمت مناقشتها بمزيد من التفاصيل في الملحق 4 ، تغليف وتوزيع مشروعات Python ، على الصفحة 175. على الرغم من أن setup.py مهم لبناء توزيع من الحزمة ، وكذلك القدرة على تثبيت الحزمة محليا بحيث تكون الحزمة متاحة للاستيراد.


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


يحتوي المشروع على نوعين من ملفات __init__.py : تلك الموجودة في src/ directory وتلك الموجودة في tests/ . يخبر src/tasks/__init__.py Python أن الدليل عبارة عن حزمة. يعمل أيضًا كواجهة رئيسية للحزمة عندما يستخدم شخص ما import tasks . أنه يحتوي على رمز لاستيراد وظائف معينة من api.py ، بحيث يمكن لـ cli.py وملفات الاختبار الخاصة بنا الوصول إلى وظائف الحزمة ، على سبيل المثال tasks.add() ، بدلاً من تنفيذ task.api.add () . tests/func/__init__.py الملفات tests/func/__init__.py tests/unit/__init__.py فارغة. يخبرون pytest بالارتقاء بدليل واحد للعثور على جذر دليل الاختبار وملف pytest.ini .


ملف pytest.ini اختياري. أنه يحتوي على التكوين العام pytest للمشروع بأكمله. يجب ألا يحتوي مشروعك على أكثر من واحد. قد يحتوي على توجيهات تغير سلوك pytest ، على سبيل المثال ، إعداد قائمة من المعلمات التي سيتم استخدامها دائمًا. سوف تتعلم كل شيء عن pytest.ini في الفصل 6 ، "التكوين" ، في الصفحة 113.


ملف conftest.py اختياري أيضًا. يُعتبر pytest بمثابة "مكون إضافي محلي" وقد يحتوي على وظائف ربط وتثبيتات. وظائف ربط هي وسيلة لتضمين التعليمات البرمجية في جزء من وقت تشغيل pytest لتغيير كيفية عمل pytest. هي تركيبات الإعداد و teardown التي تعمل قبل وبعد وظائف الاختبار ويمكن استخدامها لتمثيل الموارد والبيانات المستخدمة في الاختبارات. (تمت مناقشة التركيبات في الفصل 3 ، تركيبات pytest ، في الصفحة 49 والفصل 4 ، التركيبات المدمجة ، في الصفحة 71 ، ووظائف الخطاف تمت مناقشتها في الفصل 5 "الإضافات" في الصفحة 95.) وظائف الخطاف والتركيبات المستخدمة في يجب تضمين الاختبارات في العديد من الدلائل الفرعية في الاختبارات / conftest.py. يمكن أن يكون لديك العديد من ملفات conftest.py ؛ على سبيل المثال ، يمكنك الحصول على واحد في الاختبارات وواحد لكل دليل فرعي للاختبارات.


إذا لم تكن قد قمت بذلك بالفعل ، فيمكنك تنزيل نسخة من الكود المصدري لهذا المشروع على موقع الكتاب الإلكتروني . بدلاً من ذلك ، يمكنك العمل في مشروعك بهيكل مماثل.


هنا test_task.py:


ch2 / task_proj / tests / unit / test_task.py

 """Test the Task data type.""" # -*- coding: utf-8 -*- from tasks import Task def test_asdict(): """_asdict()   .""" t_task = Task('do something', 'okken', True, 21) t_dict = t_task._asdict() expected = {'summary': 'do something', 'owner': 'okken', 'done': True, 'id': 21} assert t_dict == expected def test_replace(): """replace ()      .""" t_before = Task('finish book', 'brian', False) t_after = t_before._replace(id=10, done=True) t_expected = Task('finish book', 'brian', True, 10) assert t_after == t_expected def test_defaults(): """        .""" t1 = Task() t2 = Task(None, None, False, None) assert t1 == t2 def test_member_access(): """ .field  namedtuple.""" t = Task('buy milk', 'brian') assert t.summary == 'buy milk' assert t.owner == 'brian' assert (t.done, t.id) == (False, None) 

يحتوي ملف test_task.py على عبارة الاستيراد هذه:


 from tasks import Task 

أفضل طريقة للسماح باختبارات استيراد المهام أو استيراد شيء من المهام هي تثبيت المهام محليًا باستخدام نقطة. هذا ممكن لأن هناك ملف setup.py للاتصال بالنقاط مباشرة.


تثبيت المهام عن طريق تشغيل pip install . أو pip install -e . من الدليل task_proj. أو خيار آخر لتشغيل pip install -e tasks_proj من الدليل بمستوى أعلى:


 $ cd /path/to/code $ pip install ./tasks_proj/ $ pip install --no-cache-dir ./tasks_proj/ Processing ./tasks_proj Collecting click (from tasks==0.1.0) Downloading click-6.7-py2.py3-none-any.whl (71kB) ... Collecting tinydb (from tasks==0.1.0) Downloading tinydb-3.4.0.tar.gz Collecting six (from tasks==0.1.0) Downloading six-1.10.0-py2.py3-none-any.whl Installing collected packages: click, tinydb, six, tasks Running setup.py install for tinydb ... done Running setup.py install for tasks ... done Successfully installed click-6.7 six-1.10.0 tasks-0.1.0 tinydb-3.4.0 

إذا كنت ترغب فقط في إجراء اختبارات للمهام ، فسيتم هذا الأمر. إذا كنت تريد أن تكون قادرًا على تغيير التعليمات البرمجية المصدر أثناء تثبيت المهام ، فأنت بحاجة إلى استخدام التثبيت مع الخيار -e (للتحرير "قابل للتحرير"):


 $ pip install -e ./tasks_proj/ Obtaining file:///path/to/code/tasks_proj Requirement already satisfied: click in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Requirement already satisfied: tinydb in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Requirement already satisfied: six in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Installing collected packages: tasks Found existing installation: tasks 0.1.0 Uninstalling tasks-0.1.0: Successfully uninstalled tasks-0.1.0 Running setup.py develop for tasks Successfully installed tasks 

حاول الآن إجراء الاختبارات:


 $ cd /path/to/code/ch2/tasks_proj/tests/unit $ pytest test_task.py ===================== test session starts ====================== collected 4 items test_task.py .... =================== 4 passed in 0.01 seconds =================== 

لقد عملت الاستيراد! يمكن للاختبارات الأخرى الآن استخدام مهام الاستيراد بأمان. الآن دعنا نكتب بعض الاختبارات.


باستخدام تأكيد البيانات


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


إذا استخدمت أي منصة اختبار أخرى ، فمن المحتمل أن تكون قد رأيت العديد من وظائف مساعد التأكيد. على سبيل المثال ، فيما يلي قائمة ببعض أشكال وظائف التأكيد والتأكيد المساعدة:


pytestunittest
تأكيد شيء ماassertTrue (شيء ما)
تأكيد = بتأكيد المساواة (أ ، ب)
تأكيد <= بassertLessEqual (a، b)
......

مع pytest يمكنك استخدام التأكيد <expression> مع أي تعبير. إذا تم تقييم التعبير إلى False عندما يتم تحويله إلى منطقي ، فسيفشل الاختبار.


تتضمن pytest وظيفة تسمى إعادة كتابة assert التي تعترض تأكيد المكالمات واستبدالها بواحدة يمكنها إخبارك أكثر عن سبب فشل عباراتك. دعونا نرى مدى فائدة إعادة الصياغة هذه إذا نظرنا إلى بعض أخطاء العبارة:


ch2 / task_proj / tests / unit / test_task_fail.py

 """ the Task type    .""" from tasks import Task def test_task_equality(): """     .""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') assert t1 == t2 def test_dict_equality(): """ ,   dicts,    .""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() assert t1_dict == t2_dict 

كل هذه الاختبارات تفشل ، ولكن المعلومات الموجودة في التتبع مثيرة للاهتمام:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\unit>pytest test_task_fail.py ============================= test session starts ============================= collected 2 items test_task_fail.py FF ================================== FAILURES =================================== _____________________________ test_task_equality ______________________________ def test_task_equality(): """Different tasks should not be equal.""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') > assert t1 == t2 E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None) E At index 0 diff: 'sit there' != 'do something' E Use -v to get the full diff test_task_fail.py:9: AssertionError _____________________________ test_dict_equality ______________________________ def test_dict_equality(): """Different tasks compared as dicts should not be equal.""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() > assert t1_dict == t2_dict E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)]) E Omitting 3 identical items, use -vv to show E Differing items: E {'owner': 'okken'} != {'owner': 'okkem'} E Use -v to get the full diff test_task_fail.py:16: AssertionError ========================== 2 failed in 0.30 seconds =========================== 

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


أنا وضعت عمدا عدم التطابق في test_task_equality() ، ولكن تم عرض الأول فقط في التعليمة البرمجية السابقة. دعونا نحاول مرة أخرى باستخدام علامة -v ، كما هو مقترح في رسالة الخطأ:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\unit>pytest -v test_task_fail.py ============================= test session starts ============================= collected 2 items test_task_fail.py::test_task_equality FAILED test_task_fail.py::test_dict_equality FAILED ================================== FAILURES =================================== _____________________________ test_task_equality ______________________________ def test_task_equality(): """Different tasks should not be equal.""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') > assert t1 == t2 E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None) E At index 0 diff: 'sit there' != 'do something' E Full diff: E - Task(summary='sit there', owner='brian', done=False, id=None) E ? ^^^ ^^^ ^^^^ E + Task(summary='do something', owner='okken', done=False, id=None) E ? +++ ^^^ ^^^ ^^^^ test_task_fail.py:9: AssertionError _____________________________ test_dict_equality ______________________________ def test_dict_equality(): """Different tasks compared as dicts should not be equal.""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() > assert t1_dict == t2_dict E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)]) E Omitting 3 identical items, use -vv to show E Differing items: E {'owner': 'okken'} != {'owner': 'okkem'} E Full diff: E {'summary': 'make sandwich', E - 'owner': 'okken', E ? ^... E E ...Full output truncated (5 lines hidden), use '-vv' to show test_task_fail.py:16: AssertionError ========================== 2 failed in 0.28 seconds =========================== 

حسنًا ، أعتقد أنه رائع! pytest لم يكن فقط قادرا على العثور على كل الاختلافات ، ولكن أيضا أظهر لنا بالضبط أين هذه الاختلافات. يستخدم هذا المثال تأكيد المساواة فقط ؛ يمكنك العثور على العديد من الأشكال المختلفة لبيان التأكيد على pytest.org مع معلومات تصحيح التتبع المذهلة.


استثناء متوقع


يمكن أن تحدث استثناءات في عدة أماكن في واجهة برمجة تطبيقات المهام. دعونا نلقي نظرة سريعة على الوظائف الموجودة في المهام / api.py :


 def add(task): # type: (Task) -\> int def get(task_id): # type: (int) -\> Task def list_tasks(owner=None): # type: (str|None) -\> list of Task def count(): # type: (None) -\> int def update(task_id, task): # type: (int, Task) -\> None def delete(task_id): # type: (int) -\> None def delete_all(): # type: () -\> None def unique_id(): # type: () -\> int def start_tasks_db(db_path, db_type): # type: (str, str) -\> None def stop_tasks_db(): # type: () -\> None 

هناك اتفاق بين رمز CLI في cli.py ورمز API في api.py بشأن الأنواع التي سيتم تمريرها إلى وظائف API. مكالمات API هي حيث أتوقع رفع الاستثناءات إذا كان النوع غير صحيح. للتأكد من أن هذه الوظائف تلقي استثناءات إذا لم يتم استدعاؤها بشكل صحيح ، استخدم النوع الخطأ في وظيفة الاختبار لرمي استثناءات TypeError عمداً واستخدامها مع pytest.raises (استثناء متوقع) ، على سبيل المثال:


ch2 / task_proj / tests / func / test_api_exceptions.py

 """    -   API.""" import pytest import tasks def test_add_raises(): """add()       param.""" with pytest.raises(TypeError): tasks.add(task='not a Task object') 

في test_add_raises() ، مع pytest.raises(TypeError) : pytest.raises(TypeError) البيان أن كل شيء في الكتلة التالية من التعليمات البرمجية يجب أن يرمي استثناء TypeError. إذا لم يتم رفع استثناء ، يفشل الاختبار. إذا أثار الاختبار استثناءًا آخر ، فإنه يفشل.


لقد فحصنا فقط نوع الاستثناء في test_add_raises() . يمكنك أيضًا التحقق من خيارات الاستبعاد. بالنسبة إلى start_tasks_db(db_path, db_type) ، يجب ألا يكون db_type فقط عبارة عن سلسلة ، بل يجب أن يكون إما "صغير" أو "mongo". يمكنك التحقق للتأكد من صحة رسالة الاستثناء عن طريق إضافة ملف excinfo:


ch2 / task_proj / tests / func / test_api_exceptions.py

 def test_start_tasks_db_raises(): """,     .""" with pytest.raises(ValueError) as excinfo: tasks.start_tasks_db('some/great/path', 'mysql') exception_msg = excinfo.value.args[0] assert exception_msg == "db_type must be a 'tiny' or 'mongo'" 

هذا يتيح لنا أن ننظر عن كثب في هذا الاستثناء. يتم إدخال اسم المتغير بعد كـ (في هذه الحالة ، excinfo) مع معلومات الاستثناء وهو من النوع ExceptionInfo.


في حالتنا ، نريد التأكد من أن المعلمة الاستثناء الأول (والوحيد) تتطابق مع السلسلة.


بمناسبة وظائف الاختبار


يوفر pytest آلية باردة لوضع علامات في وظائف الاختبار. يمكن أن يكون للاختبار أكثر من علامة واحدة ، ويمكن أن تكون العلامة في العديد من الاختبارات.


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


لإضافة مجموعة اختبار دخان إلى مشروع المهام ، تحتاج إلى إضافة @mark.pytest.smoke لبعض الاختبارات. دعنا نضيفها إلى العديد من اختبارات test_api_exceptions.py (لاحظ أن الدخان والحصول على علامات ليست مدمجة في pytest ؛ لقد توصلت إليها للتو):


ch2 / task_proj / tests / func / test_api_exceptions.py

 @pytest.mark.smoke def test_list_raises(): """list()       param.""" with pytest.raises(TypeError): tasks.list_tasks(owner=123) @pytest.mark.get @pytest.mark.smoke def test_get_raises(): """get()       param.""" with pytest.raises(TypeError): tasks.get(task_id='123') 

الآن لنقم بتشغيل تلك الاختبارات التي تحمل علامة -m marker_name :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests>cd func (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_list_raises PASSED test_api_exceptions.py::test_get_raises PASSED ============================= 5 tests deselected ============================== =================== 2 passed, 5 deselected in 0.18 seconds ==================== (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func> (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_get_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ==================== 

تذكر أن -v اختصار لـ - --verbose ويتيح لنا رؤية أسماء الاختبارات قيد التشغيل. يؤدي استخدام -m "دخان" إلى إجراء الاختبارين المسمى @ pytest.mark.smoke.


سيؤدي استخدام -m "get" إلى تشغيل اختبار واحد وضع علامة @pytest.mark.get . بسيط جدا.


كل شيء يصبح المعجزات والمعجزات! يمكن للتعبير بعد -m استخدام and / or not الجمع بين علامات متعددة:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke and get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_get_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ==================== 

لقد قمنا بهذا الاختبار فقط smoke get علامات. لا يمكننا استخدام:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke and not get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_list_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ==================== 

تم تحديد إضافة -m 'smoke and not get' على اختبار تم وضع علامة عليه على @pytest.mark.smoke ولكن ليس @pytest.mark.get .


ملء اختبار الدخان


الاختبارات السابقة لا تبدو بعد مجموعة معقولة من smoke test . في الواقع لم نلمس قاعدة البيانات ولم نقم بإضافة أي مهام. بالطبع سيتعين على smoke test القيام بذلك.


دعنا نضيف بعض الاختبارات التي تفكر في إضافة مهمة ، واستخدام واحد منها كجزء من مجموعة اختبار الدخان لدينا:


ch2 / task_proj / tests / func / test_add.py

 """  API tasks.add ().""" import pytest import tasks from tasks import Task def test_add_returns_valid_id(): """tasks.add(valid task)    .""" # GIVEN an initialized tasks db # WHEN a new task is added # THEN returned task_id is of type int new_task = Task('do something') task_id = tasks.add(new_task) assert isinstance(task_id, int) @pytest.mark.smoke def test_added_task_has_id_set(): """,   task_id  tasks.add().""" # GIVEN an initialized tasks db # AND a new task is added new_task = Task('sit in chair', owner='me', done=True) task_id = tasks.add(new_task) # WHEN task is retrieved task_from_db = tasks.get(task_id) # THEN task_id matches id field assert task_from_db.id == task_id 

يحتوي كلا الاختبارين على تعليق GIVEN على قاعدة بيانات المهام التي تمت تهيئتها ، ولكن لا توجد قاعدة بيانات مهيأة في الاختبار. يمكننا تحديد لاعبا اساسيا لتهيئة قاعدة البيانات قبل الاختبار وتنظيف بعد الاختبار:


ch2 / task_proj / tests / func / test_add.py

 @pytest.fixture(autouse=True) def initialized_tasks_db(tmpdir): """Connect to db before testing, disconnect after.""" # Setup : start db tasks.start_tasks_db(str(tmpdir), 'tiny') yield #    # Teardown : stop db tasks.stop_tasks_db() 

يتم تثبيت لاعبا اساسيا ، tmpdir المستخدمة في هذا المثال ، لاعبا اساسيا. سوف تتعلم كل شيء عن التركيبات المدمجة في الفصل 4 ، التركيبات المضمنة ، في الصفحة 71 ، وستتعلم كيفية كتابة التركيبات الخاصة بك وكيف تعمل في الفصل 3 ، تركيبات pytest ، في الصفحة 49 ، بما في ذلك معلمة autouse المستخدمة هنا.


يوضح autouse المستخدم في اختبارنا أن جميع الاختبارات في هذا الملف سوف تستخدم أداة التثبيت. يتم تنفيذ الكود قبل yield قبل كل اختبار ؛ يتم تنفيذ الكود بعد yield بعد الاختبار. إذا رغبت في ذلك ، يمكن أن ترجع البيانات إلى الاختبار. ستأخذ بعين الاعتبار كل هذا وأكثر من ذلك بكثير في الفصول التالية ، لكننا هنا بحاجة إلى تكوين قاعدة البيانات بطريقة أو بأخرى للاختبار ، لذلك لم يعد بإمكاني الانتظار حتى أضطر إلى إظهار هذا الجهاز (التثبيت بالطبع!). (يدعم pytest أيضًا وظائف الإعداد والتقليص القديمة ، مثل تلك المستخدمة في unittest والأنف ، لكنها ليست مثيرة للاهتمام للغاية. ومع ذلك ، إذا كنت مهتمًا ، فهي موصوفة في الملحق 5 ، تركيبات xUnit ، في الصفحة 183.)


دعنا نؤجل مناقشة التركيبات الآن ونذهب إلى بداية المشروع وندير مجموعة اختبار الدخان لدينا:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>cd .. (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests>cd .. (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -m "smoke" ============================= test session starts ============================= collected 56 items tests/func/test_add.py::test_added_task_has_id_set PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED ============================= 53 tests deselected ============================= =================== 3 passed, 53 deselected in 0.49 seconds =================== 

يوضح أن الاختبارات الموسومة من ملفات مختلفة يمكن أن تعمل معًا.


تخطي الاختبارات


على الرغم من أن العلامات التي تمت مناقشتها في تحديد طرق التحقق في الصفحة 31 كانت هي الأسماء التي تختارها ، إلا أن pytest تتضمن بعض العلامات المضمنة المفيدة: skip و skipif و xfail . في هذا القسم ، سأتحدث عن skip و skipif ، وفي ملف -xfail التالي.


تتيح لك skipif skip و skipif تخطي الاختبارات التي لا تحتاج إلى إجراء. على سبيل المثال ، دعنا نقول أننا لم نعرف كيف يجب أن تنجح tasks.unique_id() . يجب على كل مكالمة إرجاع رقم مختلف؟ , ?


-, (, initialized_tasks_db ; ):


ch2/tasks_proj/tests/func/ test_unique_id_1.py

 """Test tasks.unique_id().""" import pytest import tasks def test_unique_id(): """ unique_id ()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 

:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_1.py ============================= test session starts ============================= collected 1 item test_unique_id_1.py F ================================== FAILURES =================================== _______________________________ test_unique_id ________________________________ def test_unique_id(): """Calling unique_id() twice should return different numbers.""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() > assert id_1 != id_2 E assert 1 != 1 test_unique_id_1.py:11: AssertionError ========================== 1 failed in 0.30 seconds =========================== 

هم. , . API , , docstring """Return an integer that does not exist in the db.""", , DB . . , :


ch2/tasks_proj/tests/func/ test_unique_id_2.py

 @pytest.mark.skip(reason='misunderstood the API') def test_unique_id_1(): """ unique_id ()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 def test_unique_id_2(): """unique_id()    id.""" ids = [] ids.append(tasks.add(Task('one'))) ids.append(tasks.add(Task('two'))) ids.append(tasks.add(Task('three'))) #   id uid = tasks.unique_id() # ,        assert uid not in ids 

, , , @pytest..skip() .


:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_2.py ============================= test session starts ============================= collected 2 items test_unique_id_2.py::test_unique_id_1 SKIPPED test_unique_id_2.py::test_unique_id_2 PASSED ===================== 1 passed, 1 skipped in 0.19 seconds ===================== 

, - , , 0.2.0 . skipif:


ch2/tasks_proj/tests/func/ test_unique_id_3.py

 @pytest.mark.skipif(tasks.__version__ < '0.2.0', reason='not supported until version 0.2.0') def test_unique_id_1(): """ unique_id ()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 

, skipif() , Python. , , . skip , skipif . skip , skipif . ( reason ) skip , skipif xfail . :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py s. ===================== 1 passed, 1 skipped in 0.20 seconds ===================== 

s. , (skipped), (passed). , - -v :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py::test_unique_id_1 SKIPPED test_unique_id_3.py::test_unique_id_2 PASSED ===================== 1 passed, 1 skipped in 0.19 seconds ===================== 

. -rs :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -rs test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py s. =========================== short test summary info =========================== SKIP [1] func\test_unique_id_3.py:8: not supported until version 0.2.0 ===================== 1 passed, 1 skipped in 0.22 seconds ===================== 

-r chars :


 $ pytest --help ... -r chars show extra test summary info as specified by chars (     ,  ) (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (p)passed, (P)passed with output, (a)all except pP. ... 

, .



skip skipif , . xfail pytest , , . unique_id () , xfail :


ch2/tasks_proj/tests/func/ test_unique_id_4.py

 @pytest.mark.xfail(tasks.__version__ < '0.2.0', reason='not supported until version 0.2.0') def test_unique_id_1(): """ unique_id()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 @pytest.mark.xfail() def test_unique_id_is_a_duck(): """ xfail.""" uid = tasks.unique_id() assert uid == 'a duck' @pytest.mark.xfail() def test_unique_id_not_a_duck(): """ xpass.""" uid = tasks.unique_id() assert uid != 'a duck' 

Running this shows:


, , xfail . == vs.! =. .


:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_4.py ============================= test session starts ============================= collected 4 items test_unique_id_4.py xxX. =============== 1 passed, 2 xfailed, 1 xpassed in 0.36 seconds ================ 

X XFAIL, « ( expected to fail )». X XPASS «, , ( expected to fail but passed. )».


--verbose :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_4.py ============================= test session starts ============================= collected 4 items test_unique_id_4.py::test_unique_id_1 xfail test_unique_id_4.py::test_unique_id_is_a_duck xfail test_unique_id_4.py::test_unique_id_not_a_duck XPASS test_unique_id_4.py::test_unique_id_2 PASSED =============== 1 passed, 2 xfailed, 1 xpassed in 0.36 seconds ================ 

pytest , , , xfail , FAIL. pytest.ini :


 [pytest] xfail_strict=true 

pytest.ini 6, , . 113.



, ​​ . . , , . , . . .


A Single Directory


, pytest :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest tests\func --tb=no ============================= test session starts ============================= collected 50 items tests\func\test_add.py .. tests\func\test_add_variety.py ................................ tests\func\test_api_exceptions.py ....... tests\func\test_unique_id_1.py F tests\func\test_unique_id_2.py s. tests\func\test_unique_id_3.py s. tests\func\test_unique_id_4.py xxX. ==== 1 failed, 44 passed, 2 skipped, 2 xfailed, 1 xpassed in 1.75 seconds ===== 

, -v , .


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v tests\func --tb=no ============================= test session starts ============================= 

...


 collected 50 items tests\func\test_add.py::test_add_returns_valid_id PASSED tests\func\test_add.py::test_added_task_has_id_set PASSED tests\func\test_add_variety.py::test_add_1 PASSED tests\func\test_add_variety.py::test_add_2[task0] PASSED tests\func\test_add_variety.py::test_add_2[task1] PASSED tests\func\test_add_variety.py::test_add_2[task2] PASSED tests\func\test_add_variety.py::test_add_2[task3] PASSED tests\func\test_add_variety.py::test_add_3[sleep-None-False] PASSED ... tests\func\test_unique_id_2.py::test_unique_id_1 SKIPPED tests\func\test_unique_id_2.py::test_unique_id_2 PASSED ... tests\func\test_unique_id_4.py::test_unique_id_1 xfail tests\func\test_unique_id_4.py::test_unique_id_is_a_duck xfail tests\func\test_unique_id_4.py::test_unique_id_not_a_duck XPASS tests\func\test_unique_id_4.py::test_unique_id_2 PASSED ==== 1 failed, 44 passed, 2 skipped, 2 xfailed, 1 xpassed in 2.05 seconds ===== 

, .


File/Module


, , pytest:


 $ cd /path/to/code/ch2/tasks_proj $ pytest tests/func/test_add.py =========================== test session starts =========================== collected 2 items tests/func/test_add.py .. ======================== 2 passed in 0.05 seconds ========================= 

.



, :: :


 $ cd /path/to/code/ch2/tasks_proj $ pytest -v tests/func/test_add.py::test_add_returns_valid_id =========================== test session starts =========================== collected 3 items tests/func/test_add.py::test_add_returns_valid_id PASSED ======================== 1 passed in 0.02 seconds ========================= 

-v , , .


Test Class


Here's an example:


— , .
هنا مثال:


ch2/tasks_proj/tests/func/ test_api_exceptions.py

 class TestUpdate(): """    tasks.update().""" def test_bad_id(self): """non-int id   excption.""" with pytest.raises(TypeError): tasks.update(task_id={'dict instead': 1}, task=tasks.Task()) def test_bad_task(self): """A non-Task task   excption.""" with pytest.raises(TypeError): tasks.update(task_id=1, task='not a task') 

, update() , . , , :: , :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v tests/func/test_api_exceptions.py::TestUpdate ============================= test session starts ============================= collected 2 items tests\func\test_api_exceptions.py::TestUpdate::test_bad_id PASSED tests\func\test_api_exceptions.py::TestUpdate::test_bad_task PASSED ========================== 2 passed in 0.12 seconds =========================== 

A Single Test Method of a Test Class


, — :: :


 $ cd /path/to/code/ch2/tasks_proj $ pytest -v tests/func/test_api_exceptions.py::TestUpdate::test_bad_id ===================== test session starts ====================== collected 1 item tests/func/test_api_exceptions.py::TestUpdate::test_bad_id PASSED =================== 1 passed in 0.03 seconds =================== 

,

, , , , . , pytest -v .


-k , . and , or not . , _raises :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -k _raises ============================= test session starts ============================= collected 56 items tests/func/test_api_exceptions.py::test_add_raises PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED tests/func/test_api_exceptions.py::test_delete_raises PASSED tests/func/test_api_exceptions.py::test_start_tasks_db_raises PASSED ============================= 51 tests deselected ============================= =================== 5 passed, 51 deselected in 0.54 seconds =================== 

and not test_delete_raises() :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -k "_raises and not delete" ============================= test session starts ============================= collected 56 items tests/func/test_api_exceptions.py::test_add_raises PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED tests/func/test_api_exceptions.py::test_start_tasks_db_raises PASSED ============================= 52 tests deselected ============================= =================== 4 passed, 52 deselected in 0.44 seconds =================== 

, , , -k . , , .


[Parametrized Testing]:


, , . . - pytest, - .


, , add() :


ch2/tasks_proj/tests/func/ test_add_variety.py

 """  API tasks.add().""" import pytest import tasks from tasks import Task def test_add_1(): """tasks.get ()  id,   add() works.""" task = Task('breathe', 'BRIAN', True) task_id = tasks.add(task) t_from_db = tasks.get(task_id) # ,  ,    assert equivalent(t_from_db, task) def equivalent(t1, t2): """   .""" #  ,   id return ((t1.summary == t2.summary) and (t1.owner == t2.owner) and (t1.done == t2.done)) @pytest.fixture(autouse=True) def initialized_tasks_db(tmpdir): """    ,  .""" tasks.start_tasks_db(str(tmpdir), 'tiny') yield tasks.stop_tasks_db() 

tasks id None . id . == , , . equivalent() , id . autouse , , . , :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_1 ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_1 PASSED ========================== 1 passed in 0.69 seconds =========================== 

. , . , ? . @pytest.mark.parametrize(argnames, argvalues) , :


ch2/tasks_proj/tests/func/ test_add_variety.py

 @pytest.mark.parametrize('task', [Task('sleep', done=True), Task('wake', 'brian'), Task('breathe', 'BRIAN', True), Task('exercise', 'BrIaN', False)]) def test_add_2(task): """    .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

parametrize() — — 'task', . — , Task. pytest :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_2 ============================= test session starts ============================= collected 4 items test_add_variety.py::test_add_2[task0] PASSED test_add_variety.py::test_add_2[task1] PASSED test_add_variety.py::test_add_2[task2] PASSED test_add_variety.py::test_add_2[task3] PASSED ========================== 4 passed in 0.69 seconds =========================== 

parametrize() . , , :


ch2/tasks_proj/tests/func/ test_add_variety.py

 @pytest.mark.parametrize('summary, owner, done', [('sleep', None, False), ('wake', 'brian', False), ('breathe', 'BRIAN', True), ('eat eggs', 'BrIaN', False), ]) def test_add_3(summary, owner, done): """    .""" task = Task(summary, owner, done) task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

, pytest, , :


 (venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_3 ============================= test session starts ============================= platform win32 -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 -- cachedir: ..\.pytest_cache rootdir: ...\bopytest-code\code\ch2\tasks_proj\tests, inifile: pytest.ini collected 4 items test_add_variety.py::test_add_3[sleep-None-False] PASSED [ 25%] test_add_variety.py::test_add_3[wake-brian-False] PASSED [ 50%] test_add_variety.py::test_add_3[breathe-BRIAN-True] PASSED [ 75%] test_add_variety.py::test_add_3[eat eggs-BrIaN-False] PASSED [100%] ========================== 4 passed in 0.37 seconds =========================== 

, , pytest, :


 (venv35) c:\BOOK\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_3[sleep-None-False] ============================= test session starts ============================= test_add_variety.py::test_add_3[sleep-None-False] PASSED [100%] ========================== 1 passed in 0.22 seconds =========================== 

, :


 (venv35) c:\BOOK\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v "test_add_variety.py::test_add_3[eat eggs-BrIaN-False]" ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_3[eat eggs-BrIaN-False] PASSED [100%] ========================== 1 passed in 0.56 seconds =========================== 

, :


ch2/tasks_proj/tests/func/ test_add_variety.py

 tasks_to_try = (Task('sleep', done=True), Task('wake', 'brian'), Task('wake', 'brian'), Task('breathe', 'BRIAN', True), Task('exercise', 'BrIaN', False)) @pytest.mark.parametrize('task', tasks_to_try) def test_add_4(task): """ .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

. :


 (venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_4 ============================= test session starts ============================= collected 5 items test_add_variety.py::test_add_4[task0] PASSED [ 20%] test_add_variety.py::test_add_4[task1] PASSED [ 40%] test_add_variety.py::test_add_4[task2] PASSED [ 60%] test_add_variety.py::test_add_4[task3] PASSED [ 80%] test_add_variety.py::test_add_4[task4] PASSED [100%] ========================== 5 passed in 0.34 seconds =========================== 

, . , ids parametrize() , . ids , . , tasks_to_try , :


ch2/tasks_proj/tests/func/ test_add_variety.py

 task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done) for t in tasks_to_try] @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids) def test_add_5(task): """Demonstrate ids.""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

, :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_5 ============================= test session starts ============================= collected 5 items test_add_variety.py::test_add_5[Task(sleep,None,True)] PASSED test_add_variety.py::test_add_5[Task(wake,brian,False)0] PASSED test_add_variety.py::test_add_5[Task(wake,brian,False)1] PASSED test_add_variety.py::test_add_5[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)] PASSED ========================== 5 passed in 0.45 seconds =========================== 

:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v "test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)]" ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)] PASSED ========================== 1 passed in 0.21 seconds =========================== 

; shell. parametrize() . :


ch2/tasks_proj/tests/func/ test_add_variety.py

 @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids) class TestAdd(): """   .""" def test_equivalent(self, task): """ ,   .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) def test_valid_id(self, task): """          .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert t_from_db.id == task_id 

:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::TestAdd ============================= test session starts ============================= collected 10 items test_add_variety.py::TestAdd::test_equivalent[Task(sleep,None,True)] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(wake,brian,False)0] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(wake,brian,False)1] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(exercise,BrIaN,False)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(sleep,None,True)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(wake,brian,False)0] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(wake,brian,False)1] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(exercise,BrIaN,False)] PASSED ========================== 10 passed in 1.16 seconds ========================== 

, @pytest.mark.parametrize() . pytest.param(<value\>, id="something") :


:


 (venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func $ pytest -v test_add_variety.py::test_add_6 ======================================== test session starts ========================================= collected 3 items test_add_variety.py::test_add_6[just summary] PASSED [ 33%] test_add_variety.py::test_add_6[summary\owner] PASSED [ 66%] test_add_variety.py::test_add_6[summary\owner\done] PASSED [100%] ================================ 3 passed, 6 warnings in 0.35 seconds ================================ 

, id .


تمارين


  1. , task_proj , - , pip install /path/to/tasks_proj .
  2. .
  3. pytest .
  4. pytest , tasks_proj/tests/func . pytest , . . , ?
  5. xfail , pytest tests .
  6. tasks.count() , . API , , , .
  7. ? test_api_exceptions.py . , . ( api.py .)

ما التالي


pytest . , , , . initialized_tasks_db . / .


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


رجوع التالي

Source: https://habr.com/ru/post/ar448788/


All Articles