() translation by (you can also view the original English article)
اختبار هو الأساس لتطوير البرمجيات الصلبة. هناك أنواع عديدة من التجارب، ولكن النوع الأكثر أهمية اختبار وحدة. اختبار وحدة يعطيك الكثير من الثقة التي يمكنك استخدام القطع المجربة الأوليات والاعتماد عليها عند إنشاء لإنشاء البرنامج الخاص بك. زيادة المخزون الخاص بك من التعليمات البرمجية الموثوق بها تتجاوز اللغة الخاصة بك بويلتينس والمكتبة القياسية. وبالإضافة إلى ذلك، يوفر بيثون الدعم الكبير لكتابة اختبارات الوحدة.
تشغيل المثال
قبل الغوص في جميع المبادئ والأساليب البحثية والمبادئ التوجيهية، دعونا نرى وحدة ممثل لاختبار في العمل. الفئة SelfDrivingCar
تطبيق جزئي لمنطق يقود سيارة ذاتية القيادة. أنها تتعامل أساسا مع التحكم في سرعة السيارة. وتدرك الأشياء أمامه، الحد الأقصى للسرعة، وأم لا وصلت إلى وجهتها.
1 |
class SelfDrivingCar(object): |
2 |
|
3 |
def __init__(self): |
4 |
|
5 |
self.speed = 0 |
6 |
|
7 |
self.destination = None |
8 |
|
9 |
|
10 |
|
11 |
def _accelerate(self): |
12 |
|
13 |
self.speed += 1 |
14 |
|
15 |
|
16 |
|
17 |
def _decelerate(self): |
18 |
|
19 |
if self.speed > 0: |
20 |
|
21 |
self.speed -= 1 |
22 |
|
23 |
|
24 |
|
25 |
def _advance_to_destination(self): |
26 |
|
27 |
distance = self._calculate_distance_to_object_in_front() |
28 |
|
29 |
if distance < 10: |
30 |
|
31 |
self.stop() |
32 |
|
33 |
|
34 |
|
35 |
elif distance < self.speed / 2: |
36 |
|
37 |
self._decelerate() |
38 |
|
39 |
elif self.speed < self._get_speed_limit(): |
40 |
|
41 |
self._accelerate() |
42 |
|
43 |
|
44 |
|
45 |
def _has_arrived(self): |
46 |
|
47 |
pass
|
48 |
|
49 |
|
50 |
|
51 |
def _calculate_distance_to_object_in_front(self): |
52 |
|
53 |
pass
|
54 |
|
55 |
|
56 |
|
57 |
def _get_speed_limit(self): |
58 |
|
59 |
pass
|
60 |
|
61 |
|
62 |
|
63 |
def stop(self): |
64 |
|
65 |
self.speed = 0 |
66 |
|
67 |
|
68 |
|
69 |
def drive(self, destination): |
70 |
|
71 |
self.destination = destination |
72 |
|
73 |
while not self._has_arrived(): |
74 |
|
75 |
self._advance_to_destination() |
76 |
|
77 |
|
78 |
self.stop() |
79 |
|
80 |
def __init__(self): |
81 |
|
82 |
self.speed = 0 |
83 |
|
84 |
self.destination = None |
85 |
|
86 |
|
87 |
|
88 |
def _accelerate(self): |
89 |
|
90 |
self.speed += 1 |
91 |
|
92 |
|
93 |
|
94 |
def _decelerate(self): |
95 |
|
96 |
if self.speed > 0: |
97 |
|
98 |
self.speed -= 1 |
99 |
|
100 |
|
101 |
|
102 |
def _advance_to_destination(self): |
103 |
|
104 |
distance = self._calculate_distance_to_object_in_front() |
105 |
|
106 |
if distance < 10: |
107 |
|
108 |
self.stop() |
109 |
|
110 |
|
111 |
|
112 |
elif distance < self.speed / 2: |
113 |
|
114 |
self._decelerate() |
115 |
|
116 |
elif self.speed < self._get_speed_limit(): |
117 |
|
118 |
self._accelerate() |
119 |
|
120 |
|
121 |
|
122 |
def _has_arrived(self): |
123 |
|
124 |
pass
|
125 |
|
126 |
|
127 |
|
128 |
def _calculate_distance_to_object_in_front(self): |
129 |
|
130 |
pass
|
131 |
|
132 |
|
133 |
|
134 |
def _get_speed_limit(self): |
135 |
|
136 |
pass
|
137 |
|
138 |
|
139 |
|
140 |
def stop(self): |
141 |
|
142 |
self.speed = 0 |
143 |
|
144 |
|
145 |
|
146 |
def drive(self, destination): |
147 |
|
148 |
self.destination = destination |
149 |
|
150 |
while not self._has_arrived(): |
151 |
|
152 |
self._advance_to_destination() |
153 |
|
154 |
self.stop() |
155 |
هنا اختبار وحدة لأسلوب stop ()
لشحذ شهيتك. سوف ندخل التفاصيل لاحقاً.
1 |
from unittest import TestCase |
2 |
|
3 |
|
4 |
|
5 |
class SelfDrivingCarTest(TestCase): |
6 |
|
7 |
def setUp(self): |
8 |
|
9 |
self.car = SelfDrivingCar() |
10 |
|
11 |
|
12 |
|
13 |
def test_stop(self): |
14 |
|
15 |
self.car.speed = 5 |
16 |
|
17 |
self.car.stop() |
18 |
|
19 |
# Verify the speed is 0 after stopping
|
20 |
|
21 |
self.assertEqual(0, self.car.speed) |
22 |
|
23 |
|
24 |
|
25 |
# Verify it is Ok to stop again if the car is already stopped
|
26 |
|
27 |
self.car.stop() |
28 |
|
29 |
self.assertEqual(0, self.car.speed) |
المبادئ التوجيهية لاختبار وحدة
ارتكاب
كتابة اختبارات الوحدة جيدة من العمل الشاق. كتابة اختبارات الوحدة وقتاً. عند إجراء تغييرات على التعليمة البرمجية الخاصة بك، سوف تحتاج عادة تغيير الاختبارات الخاصة بك أيضا. في بعض الأحيان عليك الأخطاء في التعليمات البرمجية الخاصة بك للاختبار. وهذا يعني أنك يجب أن تكون ملتزمة حقاً. الفوائد باهظة، حتى بالنسبة للمشاريع الصغيرة، لكنها ليست مجانية.
تكون منضبطة
يجب أن تكون منضبطة. أن تكون متسقة. تأكد من اجتياز الاختبارات دائماً. لا تدع الاختبارات تكون مكسورة لأنك "تعرف" التعليمة البرمجية هو موافق.
أتمتة
لمساعدتك على أن تكون منضبطة، وينبغي أن تقوم بالتنفيذ التلقائي اختبارات الوحدة الخاصة بك. وينبغي تشغيل الاختبارات تلقائياً عند نقاط هامة مثل ارتكاب ما قبل أو ما قبل النشر. ومن الناحية المثالية، يرفض نظام إدارة التحكم المصدر الخاص بك التعليمات البرمجية التي لم تمرر جميع تجاربها.
يتم تقسيم التعليمات البرمجية التي لم يتم اختبارها بتعريف
إذا لم يمكنك اختبار ذلك، لا يمكنك القول أنه يعمل. وهذا يعني أن عليك أن تنظر في ذلك كسر. إذا كانت التعليمات البرمجية الحرجة، لا نشر ذلك للإنتاج.
معلومات أساسية
ما هي وحدة؟
وحدة لغرض اختبار وحدة ملف/وحدة تحتوي على مجموعة من المهام ذات الصلة أو فئة. إذا كان لديك ملف مع فئات متعددة، يجب عليك كتابة اختبار وحدة لكل فئة.
TDD أو عدم TDD
تطوير اختبار يحركها ممارسة حيث يمكنك كتابة الاختبارات قبل أن يمكنك كتابة التعليمات البرمجية. هناك عدة فوائد لهذا النهج، ولكن أوصى بتجنب ذلك إذا كان لديك الانضباط لكتابة الاختبارات المناسبة في وقت لاحق.
والسبب أن أنا تصميم مع التعليمات البرمجية. اكتب التعليمات البرمجية، ننظر في الأمر وكتابتها، وننظر في الأمر مرة أخرى وإعادة كتابتها مرة أخرى بشكل سريع جداً. كتابة اختبارات أول تحد لي ويبطئ لي.
مرة واحدة وأنا فعلت مع التصميم الأولى، أنا اكتب الاختبارات على الفور، قبل الاندماج مع بقية النظام. وقال أن، أنها طريقة رائعة لتقديم نفسك لاختبارات الوحدة، ويضمن أنه سيكون كافة التعليمات البرمجية الخاصة بك للاختبارات.
الوحدة النمطية أونيتيست
الوحدة unittest أونيتيست يأتي مع مكتبة بيثون القياسية. يوفر فئة تسمى TestCase
التي يمكنك اشتقاق الفئة الخاصة بك من. ثم يمكنك تجاوز أسلوب ()setUp
أن تعد لاعبا اختبار قبل كل اختبار و/أو أسلوب فئة ()classSetUp
لإعداد لاعبا اختبار لكافة الاختبارات (عدم إعادة تعيين بين الاختبارات الفردية). هي المقابلة هناك أساليب ()tearDown
و ()classTearDown
ويمكنك تجاوز كذلك.
وفيما يلي الأجزاء ذات الصلة من فئة SelfDrivingCarTest
لدينا. استخدام الأسلوب ()setUp
فقط. قم بإنشاء مثيل SelfDrivingCar
طازجة، وتخزينها في self.car
حيث أنها متاحة لكل اختبار.
1 |
from unittest import TestCase |
2 |
|
3 |
|
4 |
|
5 |
class SelfDrivingCarTest(TestCase): |
6 |
|
7 |
def setUp(self): |
8 |
|
9 |
self.car = SelfDrivingCar() |
والخطوة التالية كتابة أساليب الاختبار المحددة لاختبار التعليمات البرمجية تحت الاختبار-الدرجة SelfDrivingCar
في هذه الحالة – القيام بما أنه من المفترض أن تفعل. الهيكل لأسلوب الاختبار معيار جميلة:
- إعداد البيئة (اختياري).
- إعداد النتيجة المتوقعة.
- استدعاء التعليمات البرمجية تحت الاختبار.
- ويؤكد أن النتيجة الفعلية تطابق النتيجة المتوقعة.
لاحظ أن النتيجة لا يجب أن يكون الإخراج من أسلوب. يمكن أن يكون تغيير دولة من فئة، وآثار الجانبية مثل إضافة صف جديد في قاعدة بيانات أو كتابة ملف أو إرسال رسالة بريد إلكتروني.
على سبيل المثال، لا يرجع الأسلوب ()stop
من فئة SelfDrivingCar
أي شيء، ولكن تتغير الحالة الداخلية بتعيين السرعة إلى 0. يتم استخدام الأسلوب ()assertEqual
المقدمة من الفئة الأساسية في TestCase
هنا للتحقق من أن تطلق على ()stop
يعمل كما هو متوقع.
1 |
def test_stop(self): |
2 |
|
3 |
self.car.speed = 5 |
4 |
|
5 |
self.car.stop() |
6 |
|
7 |
# Verify the speed is 0 after stopping
|
8 |
|
9 |
self.assertEqual(0, self.car.speed) |
10 |
|
11 |
|
12 |
|
13 |
# Verify it is Ok to stop again if the car is already stopped
|
14 |
|
15 |
self.car.stop() |
16 |
|
17 |
self.assertEqual(0, self.car.speed) |
وهناك فعلا من اختبارين هنا. أول اختبار للتأكد من أنه إذا كانت سرعة السيارة 5 و ()stop
يسمى، ثم يصبح السرعة 0. ثم، اختبار آخر التأكد من أي شيء يذهب على نحو خاطئ في حالة استدعاء ()stop
مرة أخرى عند إيقاف السيارة الفعل.
في وقت لاحق، سوف أعرض عدة اختبارات أكثر للحصول على وظائف إضافية.
الوحدة النمطية دوكتيست
الوحدة النمطية دوكتيست مثيرة جداً للاهتمام. وهو يتيح لك استخدام نماذج التعليمات البرمجية التفاعلية في دوكسترينج الخاص بك وتحقق من النتائج، بما في ذلك الاستثناءات التي آثارها.
أنا لا استخدم أو يوصي دوكتيست لنظم على نطاق واسع. اختبار وحدة السليم يتطلب الكثير من العمل. اختبار التعليمات البرمجية عادة أكبر بكثير من التعليمات البرمجية تحت الاختبار. دوكسترينجس ليست فقط المتوسط الصحيح لكتابة الاختبارات الشاملة. فباردة، على الرغم. هنا ما يبدو وكأنه وظيفة factorial
مع الاختبارات doc:
1 |
import math |
2 |
|
3 |
|
4 |
|
5 |
def factorial(n): |
6 |
|
7 |
"""Return the factorial of n, an exact integer >= 0.
|
8 |
|
9 |
|
10 |
|
11 |
If the result is small enough to fit in an int, return an int.
|
12 |
|
13 |
Else return a long.
|
14 |
|
15 |
|
16 |
|
17 |
>>> [factorial(n) for n in range(6)]
|
18 |
|
19 |
[1, 1, 2, 6, 24, 120]
|
20 |
|
21 |
>>> [factorial(long(n)) for n in range(6)]
|
22 |
|
23 |
[1, 1, 2, 6, 24, 120]
|
24 |
|
25 |
>>> factorial(30)
|
26 |
|
27 |
265252859812191058636308480000000L
|
28 |
|
29 |
>>> factorial(30L)
|
30 |
|
31 |
265252859812191058636308480000000L
|
32 |
|
33 |
>>> factorial(-1)
|
34 |
|
35 |
Traceback (most recent call last):
|
36 |
|
37 |
...
|
38 |
|
39 |
ValueError: n must be >= 0
|
40 |
|
41 |
|
42 |
|
43 |
Factorials of floats are OK, but the float must be an exact integer:
|
44 |
|
45 |
>>> factorial(30.1)
|
46 |
|
47 |
Traceback (most recent call last):
|
48 |
|
49 |
...
|
50 |
|
51 |
ValueError: n must be exact integer
|
52 |
|
53 |
>>> factorial(30.0)
|
54 |
|
55 |
265252859812191058636308480000000L
|
56 |
|
57 |
|
58 |
|
59 |
It must also not be ridiculously large:
|
60 |
|
61 |
>>> factorial(1e100)
|
62 |
|
63 |
Traceback (most recent call last):
|
64 |
|
65 |
...
|
66 |
|
67 |
OverflowError: n too large
|
68 |
|
69 |
"""
|
70 |
|
71 |
if not n >= 0: |
72 |
|
73 |
raise ValueError("n must be >= 0") |
74 |
|
75 |
if math.floor(n) != n: |
76 |
|
77 |
raise ValueError("n must be exact integer") |
78 |
|
79 |
if n+1 == n: # catch a value like 1e300 |
80 |
|
81 |
raise OverflowError("n too large") |
82 |
|
83 |
result = 1 |
84 |
|
85 |
factor = 2 |
86 |
|
87 |
while factor <= n: |
88 |
|
89 |
result *= factor |
90 |
|
91 |
factor += 1 |
92 |
|
93 |
return result |
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
if __name__ == "__main__": |
100 |
|
101 |
import doctest |
102 |
|
103 |
doctest.testmod() |
كما ترون، أكبر بكثير من التعليمات البرمجية الدالة دوكسترينج. أنها لا تعزز قابلية القراءة.
تشغيل الاختبارات
كيلر وكتب لك اختبارات الوحدة الخاصة بك. لنظام كبيرة، سيكون لديك عشرات/مئات/آلاف من وحدات وفئات عبر ربما دلائل متعددة. كيف يمكنك تشغيل جميع هذه الاختبارات؟
الوحدة النمطية أونيتيست يوفر مرافق مختلفة لتجميع الاختبارات وتشغيلها برمجياً. تحقق من تحميل وتشغيل الاختبارات. ولكن أسهل طريقة اكتشاف الاختبار. تمت إضافة هذا الخيار فقط في 2.7 بيثون. ما قبل-2.7 يمكن استخدام nose لاكتشاف وتشغيل الاختبارات. وقد آنف بعض المزايا الأخرى مثل تشغيل اختبار وظائف دون الحاجة إلى إنشاء فئة لحالات الاختبار الخاصة بك. ولكن غرض هذه المقالة، دعونا العصا مع أونيتيست.
لاكتشاف وتشغيل الاختبارات الخاصة بك المستندة إلى أونيتيست، ببساطة اكتب في سطر الأوامر:
python -m unittest discover
سيتم مسح كافة الملفات والدلائل الفرعية أونيتيست وتشغيل أي الاختبارات التي يجدها وتقديم التقرير الجميل، فضلا عن وقت التشغيل. إذا كنت تريد أن ترى ما هي اختبارات تشغيله، يمكنك إضافة العلامة-v:
python -m unittest discover -v
هناك العديد من الإعلام التي تتحكم في هذه العملية:
1 |
python -m unittest -h |
2 |
|
3 |
Usage: python -m unittest [options] [tests] |
4 |
|
5 |
|
6 |
|
7 |
Options: |
8 |
|
9 |
-h, --help Show this message |
10 |
|
11 |
-v, --verbose Verbose output |
12 |
|
13 |
-q, --quiet Minimal output |
14 |
|
15 |
-f, --failfast Stop on first failure |
16 |
|
17 |
-c, --catch Catch control-C and display results |
18 |
|
19 |
-b, --buffer Buffer stdout and stderr during test runs |
20 |
|
21 |
|
22 |
|
23 |
Examples: |
24 |
|
25 |
python -m unittest test_module - run tests from test_module |
26 |
|
27 |
python -m unittest module.TestClass - run tests from module.TestClass |
28 |
|
29 |
python -m unittest module.Class.test_method - run specified test method |
30 |
|
31 |
|
32 |
|
33 |
[tests] can be a list of any number of test modules, classes and test |
34 |
|
35 |
methods. |
36 |
|
37 |
|
38 |
|
39 |
Alternative Usage: python -m unittest discover [options] |
40 |
|
41 |
|
42 |
|
43 |
Options: |
44 |
|
45 |
-v, --verbose Verbose output |
46 |
|
47 |
-f, --failfast Stop on first failure |
48 |
|
49 |
-c, --catch Catch control-C and display results |
50 |
|
51 |
-b, --buffer Buffer stdout and stderr during test runs |
52 |
|
53 |
-s directory Directory to start discovery ('.' default) |
54 |
|
55 |
-p pattern Pattern to match test files ('test*.py' default) |
56 |
|
57 |
-t directory Top level directory of project (default to |
58 |
|
59 |
start directory) |
60 |
|
61 |
|
62 |
|
63 |
For test discovery all test modules must be importable from the top |
64 |
|
65 |
level directory of the project. |
اختبار التغطية
تغطية الاختبار حقل غالباً ما تهمل. التغطية يعني كم من التعليمات البرمجية الخاصة بك يتم فعلا اختبار من الاختبارات الخاصة بك. على سبيل المثال، إذا كان لديك دالة مع عبارة if-else
ويمكنك اختبار فقط if
كان فرع، ثم كنت لا تعرف ما إذا كان يعمل فرع else
أم لا. في مثال التعليمة البرمجية التالي، يتحقق ()add
الدالة نوع الوسيطات الخاصة بها. إذا كان كلا من الإعداد الصحيحة، فإنه يضيف فقط منهم.
إذا كان كلاهما السلاسل، فإنه يحاول تحويلها إلى إعداد صحيحة وتضيف إليها. إلا أنها تقوم بإصدار استثناء. الدالة ()test_add
اختبار الدالة ()add
مع الحجج التي إعداد صحيحة على حد سواء ومع الحجج التي يطفو والتحقق من السلوك الصحيح في كل حالة على حدة. ولكن تغطية الاختبار غير مكتملة. لم أكن اختبار حالة سلسلة الحجج. نتيجة لذلك ينجح الاختبار بنجاح، ولكن لم يكن اكتشف الخطأ المطبعي في الفرع حيث الحجج كلا سلاسل (انظر 'إينتج' هناك؟).
1 |
import unittest |
2 |
|
3 |
|
4 |
|
5 |
def add(a, b): |
6 |
|
7 |
"""This function adds two numbers a, b and returns their sum
|
8 |
|
9 |
|
10 |
|
11 |
a and b may integers
|
12 |
|
13 |
"""
|
14 |
|
15 |
if isinstance(a, int) and isinstance(b, int): |
16 |
|
17 |
return a + b |
18 |
|
19 |
elseif isinstance(a, str) and isinstance(b, str): |
20 |
|
21 |
return int(a) + intg(b) |
22 |
|
23 |
else: |
24 |
|
25 |
raise Exception('Invalid arguments') |
26 |
|
27 |
|
28 |
|
29 |
class Test(unittest.TestCase): |
30 |
|
31 |
def test_add(self): |
32 |
|
33 |
self.assertEqual(5, add(2, 3)) |
34 |
|
35 |
self.assertEqual(15, add(-6, 21)) |
36 |
|
37 |
self.assertRaises(Exception, add, 4.0, 5.0) |
38 |
|
39 |
|
40 |
|
41 |
unittest.main() |
هنا هو الإخراج:
1 |
---------------------------------------------------------------------- |
2 |
|
3 |
Ran 1 test in 0.000s |
4 |
|
5 |
|
6 |
|
7 |
OK |
8 |
|
9 |
|
10 |
|
11 |
Process finished with exit code 0 |
اختبارات وحدة التدريب العملي
كتابة اختبارات الوحدة القوة الصناعية ليست سهلة أو بسيطة. وهناك العديد من الأشياء للنظر والمقايضات بذل.
تصميم Testability
إذا كانت التعليمة البرمجية الخاصة بك ما يسمى رسميا رمز السباغيتي أو كرة كبيرة من الطين فيها مستويات مختلفة من التجريد تمتزج معا وكل قطعة من التعليمات البرمجية التي تعتمد على كل قطعة أخرى من التعليمات البرمجية، سيكون لديك وقتاً عصيبا في اختباره. أيضا، عندما تقوم بتغيير شيء ما، سيكون لديك لتحديث مجموعة من الاختبارات أيضا.
والخبر السار أن تصميم البرامج المناسبة ذات الأغراض العامة بالضبط ما تحتاجه ل testability. على وجه الخصوص، سوف تجعل التعليمات البرمجية وحدات يؤخذ جيدا، حيث أن كل مكون من مكونات مسؤولية واضحة ويتفاعل مع المكونات الأخرى عبر واجهات محددة تحديداً جيدا، كتابة اختبارات الوحدة جيدة من دواعي سروري.
على سبيل المثال، لدينا فئة SelfDrivingCar
مسؤولة عن عملية رفيعة المستوى للسيارة: الذهاب، وقف، والتنقل. أنها طريقة ()calculate_distance_to_object_in_front
التي لم تنفذ حتى الآن. ربما ينبغي أن تنفذها هذه الوظيفة نظام فرعي منفصل تماما. وقد تشمل قراءة البيانات من مختلف أجهزة الاستشعار، وتتفاعل مع غيرها من السيارات ذاتية القيادة، كدسة رؤية جهاز كله لتحليل الصور من الكاميرات متعددة.
دعونا نرى كيف يعمل هذا في الممارسة العملية. SelfDrivingCar
سوف تقبل وسيطة تسمى object_detector
التي تحتوي على أسلوب يسمى ()calculate_distance_to_object_in_front
، وأنه سيتم تفويض هذه الوظيفة لهذا الكائن. الآن، ليس هناك أي حاجة لوحدة اختبار هذا نظراً object_detector
هو المسؤول (وينبغي اختبار) لأنه. كنت لا تزال تريد وحدة اختبار حقيقة أن كنت تستخدم في object_detector
بشكل صحيح.
1 |
class SelfDrivingCar(object): |
2 |
|
3 |
def __init__(self, object_detector): |
4 |
|
5 |
self.object_detector |
6 |
|
7 |
self.speed = 0 |
8 |
|
9 |
self.destination = None |
10 |
|
11 |
|
12 |
|
13 |
def _calculate_distance_to_object_in_front(self): |
14 |
|
15 |
return self.object_detector.calculate_distance_to_object_in_front() |
التكاليف والفوائد
ينبغي أن ترتبط مقدار الجهد لك موضع الاختبار بتكلفة الفشل والتعليمات البرمجية كيفية مستقرة، وكيف أنه من السهل لإصلاح إذا تم الكشف عن مشاكل أسفل الخط.
على سبيل المثال، لدينا فئة السيارات ذاتية القيادة أمر بالغ الأهمية الفائقة. إذا كان الأسلوب ()stop
لا يعمل بشكل صحيح، لدينا سيارة ذاتية القيادة قد قتل الناس، وتدمير الممتلكات، وإفشال كل ذاتية القيادة سوق السيارات. إذا قمت بتطوير سيارة ذاتية قيادة، وأظن اختبارات الوحدة الخاصة بك للأسلوب ()stop
سوف يكون قليلاً أكثر صرامة من الألغام.
من ناحية أخرى، إذا كان زر واحد في تطبيق الويب الخاص بك على صفحة وقد دفن ثلاثة مستويات أسفل الصفحة الرئيسية الخاصة بك ومضات قليلاً عندما يقوم شخص ما بالنقر فوقه، يمكنك يمكن إصلاحه، لكن ربما لن بإضافة اختبار وحدة مخصصة لهذه القضية. الاقتصاديات فقط لا تبرر ذلك.
اختبار عقلية
من المهم اختبار عقلية. مبدأ واحد استعمل أن كل قطعة من التعليمات البرمجية على الأقل اثنين من المستخدمين: التعليمات البرمجية الأخرى التي يتم استخدامه والاختبار الذي يتم اختباره. هذه قاعدة بسيطة تساعد كثيرا مع التصميم والتبعيات. إذا كنت تتذكر أن لديك لكتابة اختبار التعليمات البرمجية الخاصة بك، سوف لا إضافة الكثير من التبعيات التي يصعب إعادة إعمار أثناء الاختبار.
على سبيل المثال، افترض أن التعليمات البرمجية الخاصة بك يحتاج إلى حساب شيء. من أجل القيام بذلك، أنه يحتاج إلى تحميل بعض البيانات من قاعدة بيانات، قراءة ملف تكوين، وديناميكيا استشارة بعض REST API للحصول على معلومات محدثة. كل هذا قد تكون مطلوبة لأسباب مختلفة، ولكن وضع كل ذلك في دالة واحدة سيجعل من الصعب جداً لاختبار وحدة. لا يزال من الممكن مع ساخرا، ولكن من الأفضل كثيرا أن بنية التعليمات البرمجية الخاصة بك بشكل صحيح.
وظائف نقي
التعليمات البرمجية أسهل لاختبار وظائف محض. نقية مهام الوظائف التي الوصول فقط قيم المعلمات الخاصة بهم، ولها أي آثار جانبية وإرجاع النتيجة نفسها كلما دعا مع نفس الحجج. أنها لم تقم بتغيير حالة البرنامج الخاص بك، ولا الوصول إلى نظام الملف أو شبكة الاتصال. فوائدها كثيرة جداً لعد هنا.
لماذا سهلة لاختبار؟ لأنه ليس هناك حاجة لتعيين بيئة خاصة لاختبار. يمكنك فقط تمرير الوسائط والنتيجة الاختبار. وتعلمون أيضا أن طالما لم يتم تغيير التعليمات البرمجية تحت الاختبار، اختبار الخاص بك لا يملك تغيير.
قارن ذلك إلى دالة تقوم بقراءة ملف تكوين XML. وسيكون الاختبار الخاص بك لإنشاء ملف XML وتمرير الملف به إلى التعليمات البرمجية تحت الاختبار. ليست صفقة كبيرة. ولكن لنفترض أن أحدهم قرر أن XML البغيضة، ويجب أن تكون كافة ملفات التكوين في JSON. أنها ممارسة أعمالهم وتحويل كافة ملفات التكوين إلى JSON. أنهم تشغيل كافة الاختبارات بما في ذلك الاختبارات الخاصة بك، وأنهم جميعا تمرير!
لماذا؟ لأنه لم يكن تغيير التعليمات البرمجية. أنها لا تزال تتوقع ملف تكوين XML، واختبار الخاص بك لا يزال يبني ملف XML لذلك. ولكن في الإنتاج، التعليمات البرمجية الخاصة بك سوف تحصل على ملف JSON، وأنها سوف تفشل في تحليل.
اختبار معالجة الخطأ
معالجة الخطأ هو آخر شيء ضروري لاختبار. كما أنها جزء من التصميم. من هو المسؤول عن صحة الإدخال؟ كل دالة والأسلوب ينبغي أن يكون واضحا حول هذا الموضوع. إذا كانت المسؤولية للدالة، فإنه يجب التحقق من مدخلاتها، ولكن إذا هو المسؤولية للمتصل ثم الدالة يمكن اذهبوا حول أعماله التجارية وتحمل الإدخال بشكل صحيح. سيتم ضمان صحة النظام العام قبل وبعد الاختبارات للطالب للتأكد من أن يمر فقط الإدخال الصحيح لوظيفة الخاص بك.
عادة، تحتاج إلى التحقق من الإدخال على الواجهة العمومية للتعليمات البرمجية الخاصة بك لأنك لا تعرف بالضرورة الذي يحدث لاستدعاء التعليمات البرمجية الخاصة بك. دعونا ننظر في الطريقة ()drive
سيارة ذاتية القيادة. يتوقع هذا الأسلوب معلمة 'وجهة'. سيتم استخدام المعلمة 'وجهة' لاحقاً في التنقل، ولكن الأسلوب محرك الأقراص لا يفعل شيئا للتحقق من أنها صحيحة.
دعنا نفترض أن الوجهة من المفترض أن تكون مجموعة من خطوط الطول والعرض. هناك كل أنواع الاختبارات التي يمكن القيام به للتحقق من أنها صحيحة (مثل هو الوجهة في وسط البحر). لأغراضنا، دعونا فقط التأكد من أنها مجموعة عوامات في نطاق 0.0 إلى 90.0-180.0 إلى 180.0 لخط الطول والعرض.
هنا هو تحديث فئة SelfDrivingCar
. أنا نفذت مسلي بعض الأساليب حبراً على ورق لأن استدعاء الأسلوب ()drive
بعض هذه الطرق مباشرة أو غير مباشرة.
1 |
class SelfDrivingCar(object): |
2 |
|
3 |
def __init__(self, object_detector): |
4 |
|
5 |
self.object_detector = object_detector |
6 |
|
7 |
self.speed = 0 |
8 |
|
9 |
self.destination = None |
10 |
|
11 |
|
12 |
|
13 |
def _accelerate(self): |
14 |
|
15 |
self.speed += 1 |
16 |
|
17 |
|
18 |
|
19 |
def _decelerate(self): |
20 |
|
21 |
if self.speed > 0: |
22 |
|
23 |
self.speed -= 1 |
24 |
|
25 |
|
26 |
|
27 |
def _advance_to_destination(self): |
28 |
|
29 |
distance = self._calculate_distance_to_object_in_front() |
30 |
|
31 |
if distance < 10: |
32 |
|
33 |
self.stop() |
34 |
|
35 |
|
36 |
|
37 |
elif distance < self.speed / 2: |
38 |
|
39 |
self._decelerate() |
40 |
|
41 |
elif self.speed < self._get_speed_limit(): |
42 |
|
43 |
self._accelerate() |
44 |
|
45 |
|
46 |
|
47 |
def _has_arrived(self): |
48 |
|
49 |
return True |
50 |
|
51 |
|
52 |
|
53 |
def _calculate_distance_to_object_in_front(self): |
54 |
|
55 |
return self.object_detector.calculate_distance_to_object_in_front() |
56 |
|
57 |
|
58 |
|
59 |
def _get_speed_limit(self): |
60 |
|
61 |
return 65 |
62 |
|
63 |
|
64 |
|
65 |
def stop(self): |
66 |
|
67 |
self.speed = 0 |
68 |
|
69 |
|
70 |
|
71 |
def drive(self, destination): |
72 |
|
73 |
self.destination = destination |
74 |
|
75 |
while not self._has_arrived(): |
76 |
|
77 |
self._advance_to_destination() |
78 |
|
79 |
self.stop() |
لاختبار خطأ معالجة في الاختبار، وسوف تمرير وسيطة غير صالحة والتحقق من أنها مرفوضة بشكل صحيح. يمكنك القيام بذلك باستخدام في ()self.assertRaises
أسلوب unittest.TestCase
. ينجح هذا الأسلوب إذا كانت التعليمات البرمجية تحت الاختبار في الواقع تقوم بإصدار استثناء.
دعونا نرى ذلك في العمل. الأسلوب ()test_drive
بتمرير خطوط الطول والعرض خارج النطاق الصالح وتتوقع الأسلوب ()drive
لرفع استثناء.
1 |
from unittest import TestCase |
2 |
|
3 |
from self_driving_car import SelfDrivingCar |
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
class MockObjectDetector(object): |
10 |
|
11 |
def calculate_distance_to_object_in_front(self): |
12 |
|
13 |
return 20 |
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
class SelfDrivingCarTest(TestCase): |
20 |
|
21 |
def setUp(self): |
22 |
|
23 |
self.car = SelfDrivingCar(MockObjectDetector()) |
24 |
|
25 |
|
26 |
|
27 |
def test_stop(self): |
28 |
|
29 |
self.car.speed = 5 |
30 |
|
31 |
self.car.stop() |
32 |
|
33 |
# Verify the speed is 0 after stopping
|
34 |
|
35 |
self.assertEqual(0, self.car.speed) |
36 |
|
37 |
|
38 |
|
39 |
# Verify it is Ok to stop again if the car is already stopped
|
40 |
|
41 |
self.car.stop() |
42 |
|
43 |
self.assertEqual(0, self.car.speed) |
44 |
|
45 |
|
46 |
|
47 |
def test_drive(self): |
48 |
|
49 |
# Valid destination
|
50 |
|
51 |
self.car.drive((55.0, 66.0)) |
52 |
|
53 |
|
54 |
|
55 |
# Invalid destination wrong range
|
56 |
|
57 |
self.assertRaises(Exception, self.car.drive, (-55.0, 200.0)) |
فشل الاختبار، لأن الأسلوب ()drive
لا تحقق الوسيطات الخاصة بها لصحة ولا تقوم بإصدار استثناء. يمكنك الحصول على تقرير لطيفة مع معلومات كاملة حول ما فشلت، أين ولماذا.
1 |
python -m unittest discover -v |
2 |
|
3 |
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... FAIL |
4 |
|
5 |
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok |
6 |
|
7 |
|
8 |
|
9 |
====================================================================== |
10 |
|
11 |
FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) |
12 |
|
13 |
---------------------------------------------------------------------- |
14 |
|
15 |
Traceback (most recent call last): |
16 |
|
17 |
File "/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py", line 29, in test_drive |
18 |
|
19 |
self.assertRaises(Exception, self.car.drive, (-55.0, 200.0)) |
20 |
|
21 |
AssertionError: Exception not raised |
22 |
|
23 |
|
24 |
|
25 |
---------------------------------------------------------------------- |
26 |
|
27 |
Ran 2 tests in 0.000s |
28 |
|
29 |
|
30 |
|
31 |
FAILED (failures=1) |
لإصلاح ذلك دعونا تحديث الأسلوب ()drive
الذي تحقق فعلا من نطاق الوسيطات الخاصة بها:
1 |
def drive(self, destination): |
2 |
|
3 |
lat, lon = destination |
4 |
|
5 |
if not (0.0 <= lat <= 90.0): |
6 |
|
7 |
raise Exception('Latitude out of range') |
8 |
|
9 |
if not (-180.0 <= lon <= 180.0): |
10 |
|
11 |
raise Exception('Latitude out of range') |
12 |
|
13 |
|
14 |
|
15 |
self.destination = destination |
16 |
|
17 |
while not self._has_arrived(): |
18 |
|
19 |
self._advance_to_destination() |
20 |
|
21 |
self.stop() |
والآن، تمرير كافة الاختبارات.
1 |
python -m unittest discover -v |
2 |
|
3 |
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok |
4 |
|
5 |
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok |
6 |
|
7 |
|
8 |
|
9 |
---------------------------------------------------------------------- |
10 |
|
11 |
Ran 2 tests in 0.000s |
12 |
|
13 |
|
14 |
|
15 |
OK |
16 |
اختبار الأساليب الخاصة
وينبغي اختبار كل دالة والأسلوب؟ على وجه الخصوص، ينبغي اختبار أساليب خاصة تسمى فقط بواسطة التعليمات البرمجية الخاصة بك؟ الإجابة عادة غير مرضية: "الأمر يتوقف".
سأحاول أن يكون من المفيد هنا وأقول لكم ما يتوقف على. كنت تعرف بالضبط الذي يستدعي الأسلوب الخاص بك — هو التعليمات البرمجية الخاصة بك. إذا كانت الاختبارات الخاصة بك للطرق العامة التي استدعاء الأسلوب الخاص بك الشامل ثم يمكنك فعلا اختبار الأساليب الخاصة بك مستفيض. ولكن إذا كان أسلوب خاص معقد للغاية، قد ترغب في اختباره بشكل مستقل. استخدام الحكم الخاص بك.
كيفية تنظيم اختبارات الوحدة الخاصة بك
في نظام كبير، أنها ليست دائماً واضحة كيفية تنظيم الاختبارات الخاصة بك. يجب أن يكون لديك ملف واحد كبير مع كافة الاختبارات لمجموعة، أو ملف اختبار واحد لكل فئة؟ ينبغي أن تكون الاختبارات في نفس ملف التعليمات البرمجية تحت الاختبار، أو في نفس الدليل؟
هنا هو استخدام النظام. الاختبارات يجب أن تكون منفصلة تماما عن التعليمات البرمجية تحت الاختبار (ومن ثم لا تستخدم دوكتيست). ومن الناحية المثالية، ينبغي أن تكون التعليمات البرمجية الخاصة بك في مجموعة. وينبغي أن الاختبارات لكل حزمة في دليل مشابهة للحزمة الخاصة بك. في دليل الاختبارات، ينبغي أن يكون هناك ملف واحد لكل وحدة نمطية من الحزمة الخاصة بك اسمه <test_<module name
على سبيل المثال، إذا كان لديك ثلاث وحدات في الحزمة الخاصة بك: module_1.py
و ، module_2.py
و module_3.py
، يجب عليك اختبار ثلاثة ملفات: test_module_1.py
و test_module_2.py
و test_module_3.py
تحت الدليل الاختبارات.
هذه الاتفاقية مزايا عديدة. فإنه يجعل من الواضح تماما استعراض الدلائل التي لم أكن نسيت لاختبار بعض وحدة تماما. كما أنه يساعد على تنظيم الاختبارات في قطع حجم معقول. وعلى افتراض أن هي معقول الحجم الوحدات النمطية الخاص بك ثم سيتم اختبار التعليمات البرمجية لكل وحدة نمطية في ملف خاص بها، التي قد تكون أكبر قليلاً من الوحدة النمطية تحت الاختبار، ولكن لا يزال شيئا يلائم بشكل مريح في ملف واحد.
الاستنتاج
اختبارات الوحدة هي الأساس لرمز الصلبة. في هذا البرنامج التعليمي، وبحثت في بعض المبادئ والخطوط التوجيهية لوحدة اختبار وشرح الأسباب الكامنة وراء العديد من أفضل الممارسات. كنت بناء أكبر النظام، تصبح اختبارات الوحدة أكثر أهمية. ولكن اختبارات وحدة لا يكفي. أنواع أخرى من الاختبارات ضرورية جداً لنظم واسعة النطاق: اختبارات التكامل، اختبارات الأداء، واختبارات التحميل، واختبارات الاختراق، واختبارات القبول، إلخ.