مقدمة بسيطة لاختبار التطوير المدفوع باستخدام بايثون

أنا مطور مبتدئ علمي ذاتيًا وقادر على كتابة تطبيقات بسيطة. لكن لدي اعتراف. من المستحيل أن أتذكر كيف أن كل شيء مترابط في رأسي.

يزداد هذا الموقف سوءًا إذا عدت إلى الكود الذي كتبته بعد بضعة أيام. تبين أنه يمكن التغلب على هذه المشكلة باتباع منهجية التطوير المستند إلى الاختبار (TDD).

ما هو TDD ولماذا هو مهم؟

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

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

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

كيف تبدأ؟

لبدء كتابة الاختبارات في Python ، سنستخدم unittestالوحدة التي تأتي مع Python. للقيام بذلك نقوم بإنشاء ملف جديد mytests.pyيحتوي على جميع اختباراتنا.

لنبدأ بـ "hello world" المعتاد:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world')

لاحظ أننا نستورد helloworld()وظيفة من mycodeملف. في الملف ، mycode.pyسنقوم في البداية بتضمين الكود أدناه فقط ، والذي ينشئ الوظيفة ولكنه لا يُرجع أي شيء في هذه المرحلة:

def hello_world(): pass

python mytests.pyسيؤدي التشغيل إلى إنشاء الإخراج التالي في سطر الأوامر:

F
====================================================================
FAIL: test_hello (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 7, in test_hello
self.assertEqual(hello_world(), 'hello world')
AssertionError: None != 'hello world'
--------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)

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

للتأكد من مرور الرمز ، دعنا نغير mycode.pyما يلي:

def hello_world(): return 'hello world'

عند التشغيل python mytests.pyمرة أخرى ، نحصل على الإخراج التالي في سطر الأوامر:

.
--------------------------------------------------------------------
Ran 1 test in 0.000s
OK

مبروك! لقد كتبت للتو اختبارك الأول. دعنا الآن ننتقل إلى تحدٍ أكثر صعوبة قليلاً. سننشئ وظيفة تسمح لنا بإنشاء قائمة رقمية مخصصة لفهم لغة بايثون.

لنبدأ بكتابة اختبار لدالة من شأنها إنشاء قائمة بطول معين.

في ملف mytests.pyهذا من شأنه أن يكون وسيلة test_custom_num_list:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world') def test_custom_num_list(self): self.assertEqual(len(create_num_list(10)), 10)

سيختبر هذا أن الدالة create_num_listتُرجع قائمة بالطول 10. لنقم بإنشاء دالة create_num_listفي mycode.py:

def hello_world(): return 'hello world'
def create_num_list(length): pass

python mytests.pyسيؤدي التشغيل إلى إنشاء الإخراج التالي في سطر الأوامر:

E.
====================================================================
ERROR: test_custom_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 14, in test_custom_num_list
self.assertEqual(len(create_num_list(10)), 10)
TypeError: object of type 'NoneType' has no len()
--------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)

هذا كما هو متوقع، لذلك دعونا المضي قدما وتغيير وظيفة create_num_listفي mytest.pyلاجتياز الاختبار:

def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]

python mytests.pyيوضح التنفيذ في سطر الأوامر أن الاختبار الثاني قد اجتاز الآن أيضًا:

..
--------------------------------------------------------------------
Ran 2 tests in 0.000s
OK

Let’s now create a custom function that would transform each value in the list like this: const * ( X ) ^ power . First let’s write the test for this, using method test_custom_func_ that would take value 3 as X, take it to the power of 3, and multiply by a constant of 2, resulting in the value 54:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self): self.assertEqual(len(create_num_list(10)), 10) def test_custom_func_x(self): self.assertEqual(custom_func_x(3,2,3), 54)

Let’s create the function custom_func_x in the file mycode.py:

def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): pass

As expected, we get a fail:

F..
====================================================================
FAIL: test_custom_func_x (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 17, in test_custom_func_x
self.assertEqual(custom_func_x(3,2,3), 54)
AssertionError: None != 54
--------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (failures=1)

Updating function custom_func_x to pass the test, we have the following:

def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): return const * (x) ** power

Running the tests again we get a pass:

...
--------------------------------------------------------------------
Ran 3 tests in 0.000s
OK

Finally, let’s create a new function that would incorporate custom_func_x function into the list comprehension. As usual, let’s begin by writing the test. Note that just to be certain, we include two different cases:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self): self.assertEqual(len(create_num_list(10)), 10)
def test_custom_func_x(self): self.assertEqual(custom_func_x(3,2,3), 54)
def test_custom_non_lin_num_list(self): self.assertEqual(custom_non_lin_num_list(5,2,3)[2], 16) self.assertEqual(custom_non_lin_num_list(5,3,2)[4], 48)

Now let’s create the function custom_non_lin_num_list in mycode.py:

def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): return const * (x) ** power
def custom_non_lin_num_list(length, const, power): pass

As before, we get a fail:

.E..
====================================================================
ERROR: test_custom_non_lin_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 20, in test_custom_non_lin_num_list
self.assertEqual(custom_non_lin_num_list(5,2,3)[2], 16)
TypeError: 'NoneType' object has no attribute '__getitem__'
--------------------------------------------------------------------
Ran 4 tests in 0.000s
FAILED (errors=1)

In order to pass the test, let’s update the mycode.py file to the following:

def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): return const * (x) ** power
def custom_non_lin_num_list(length, const, power): return [custom_func_x(x, const, power) for x in range(length)]

Running the tests for the final time, we pass all of them!

....
--------------------------------------------------------------------
Ran 4 tests in 0.000s
OK

Congrats! This concludes this introduction to testing in Python. Make sure you check out the resources below for more information on testing in general.

The code is available here on GitHub.

Useful resources for further learning!

Web resources

Below are links to some of the libraries focusing on testing in Python

25.3. unittest - Unit testing framework - Python 2.7.14 documentation

The Python unit testing framework, sometimes referred to as "PyUnit," is a Python language version of JUnit, by Kent…docs.python.orgpytest: helps you write better programs - pytest documentation

يسهّل إطار العمل كتابة اختبارات صغيرة ، إلا أن المقاييس تدعم الاختبارات الوظيفية المعقدة للتطبيقات و… docs.pytest.org مرحبًا بك في Hypothesis! - الفرضية 3.45.2 التوثيق

إنه يعمل عن طريق إنشاء بيانات عشوائية تطابق المواصفات الخاصة بك والتحقق من أن الضمان الخاص بك لا يزال ساريًا في ... فرضية .readthedocs.io unittest2 1.1.0: فهرس حزمة Python

الميزات الجديدة في unittest backported إلى Python 2.4+. pypi.python.org

أشرطة فيديو يوتيوب

إذا كنت تفضل عدم القراءة ، فإنني أوصي بمشاهدة مقاطع الفيديو التالية على YouTube.