Тестирование является основой надежной разработки программного обеспечения. Существует много типов тестирования, но наиболее важным типом является модульное тестирование. Модульное тестирование дает вам большую уверенность в том, что вы можете использовать хорошо протестированные фрагменты в качестве примитивов и полагаться на них, когда создаете их для создания своей программы. Они увеличивают ваш инвентарь доверенного кода за пределами встроенных в ваш язык и стандартной библиотеки. Кроме того, Python предоставляет отличную поддержку для написания юнит-тестов.
Пример выполнения
Прежде чем углубиться во все принципы, эвристику и руководящие принципы, давайте посмотрим на репрезентативный модульный тест в действии. Класс SelfDrivingCar
является частичной реализацией логики вождения автомобиля с самостоятельным вождением. В основном это касается контроля скорости автомобиля. Он знает об объектах перед ним, об ограничении скорости и о том, прибыл ли он к месту назначения или нет.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
class SelfDrivingCar(object):
def __init__(self):
self.speed = 0
self.destination = None
def _accelerate(self):
self.speed += 1
def _decelerate(self):
if self.speed > 0:
self.speed -= 1
def _advance_to_destination(self):
distance = self._calculate_distance_to_object_in_front()
if distance < 10:
self.stop()
elif distance < self.speed / 2:
self._decelerate()
elif self.speed < self._get_speed_limit():
self._accelerate()
def _has_arrived(self):
pass
def _calculate_distance_to_object_in_front(self):
pass
def _get_speed_limit(self):
pass
def stop(self):
self.speed = 0
def drive(self, destination):
self.destination = destination
while not self._has_arrived():
self._advance_to_destination()
self.stop()
def __init__(self):
self.speed = 0
self.destination = None
def _accelerate(self):
self.speed += 1
def _decelerate(self):
if self.speed > 0:
self.speed -= 1
def _advance_to_destination(self):
distance = self._calculate_distance_to_object_in_front()
if distance < 10:
self.stop()
elif distance < self.speed / 2:
self._decelerate()
elif self.speed < self._get_speed_limit():
self._accelerate()
def _has_arrived(self):
pass
def _calculate_distance_to_object_in_front(self):
pass
def _get_speed_limit(self):
pass
def stop(self):
self.speed = 0
def drive(self, destination):
self.destination = destination
while not self._has_arrived():
self._advance_to_destination()
self.stop()
|
Вот модульный тест для метода stop()
чтобы подогреть аппетит. Я буду вдаваться в подробности позже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
from unittest import TestCase
class SelfDrivingCarTest(TestCase):
def setUp(self):
self.car = SelfDrivingCar()
def test_stop(self):
self.car.speed = 5
self.car.stop()
# Verify the speed is 0 after stopping
self.assertEqual(0, self.car.speed)
# Verify it is Ok to stop again if the car is already stopped
self.car.stop()
self.assertEqual(0, self.car.speed)
|
Руководство по модульному тестированию
совершить
Написание хороших юнит-тестов — тяжелая работа. Написание юнит-тестов требует времени. Когда вы вносите изменения в свой код, вам, как правило, также нужно менять свои тесты. Иногда у вас будут ошибки в тестовом коде. Это означает, что вы должны быть действительно преданными. Преимущества огромны, даже для небольших проектов, но они не бесплатны.
Быть дисциплинированным
Вы должны быть дисциплинированными. Быть последовательным. Убедитесь, что тесты всегда проходят. Не позволяйте тестам сломаться, потому что вы «знаете», что код в порядке.
Автоматизировать
Чтобы помочь вам быть дисциплинированным, вы должны автоматизировать свои юнит-тесты. Тесты должны запускаться автоматически в важные моменты, такие как предварительная фиксация или предварительное развертывание. В идеале ваша система управления исходным кодом отклоняет код, который не прошел все свои тесты.
Непроверенный код разбит по определению
Если вы не проверяли это, вы не можете сказать, что это работает. Это означает, что вы должны считать это сломанным. Если это критически важный код, не развертывайте его в рабочей среде.
Фон
Что такое юнит?
Единицей для целей юнит-тестирования является файл / модуль, содержащий набор связанных функций или класс. Если у вас есть файл с несколькими классами, вы должны написать модульный тест для каждого класса.
В TDD или не в TDD
Разработка через тестирование — это практика, когда вы пишете тесты перед написанием кода. У этого подхода есть несколько преимуществ, но я рекомендую избегать его, если у вас есть дисциплина, чтобы написать надлежащие тесты позже.
Причина в том, что я проектирую с помощью кода. Я пишу код, смотрю на него, переписываю его, смотрю на него снова и переписываю снова очень быстро. Написание тестов сначала ограничивает меня и замедляет.
Как только я закончу с первоначальным дизайном, я напишу тесты немедленно, прежде чем интегрироваться с остальной системой. Тем не менее, это отличный способ познакомиться с модульными тестами, и он гарантирует, что весь ваш код будет иметь тесты.
Модуль Unittest
Модуль unittest поставляется со стандартной библиотекой Python. Он предоставляет класс TestCase
, из которого вы можете получить свой класс. Затем вы можете переопределить метод setUp()
чтобы подготовить тестовое устройство перед каждым тестом, и / или метод класса classSetUp()
чтобы подготовить тестовое устройство для всех тестов (не сбрасывать между отдельными тестами). Существуют также соответствующие tearDown()
и classTearDown()
которые вы можете переопределить.
Вот соответствующие части из нашего класса SelfDrivingCarTest
. Я использую только метод setUp()
. Я создаю свежий экземпляр SelfDrivingCar
и сохраняю его в self.car
чтобы он был доступен для каждого теста.
1
2
3
4
5
6
7
8
9
|
from unittest import TestCase
class SelfDrivingCarTest(TestCase):
def setUp(self):
self.car = SelfDrivingCar()
|
Следующим шагом является написание специальных тестовых методов для тестирования тестируемого кода, в данном случае класса SelfDrivingCar
который выполняет то, что должен делать. Структура метода тестирования довольно стандартна:
- Подготовьте среду (по желанию).
- Подготовьте ожидаемый результат.
- Назовите тестируемый код.
- Утверждают, что фактический результат соответствует ожидаемому результату.
Обратите внимание, что результат не должен быть результатом метода. Это может быть изменение состояния класса, побочный эффект, такой как добавление новой строки в базу данных, запись файла или отправка электронного письма.
Например, метод stop()
класса SelfDrivingCar
ничего не возвращает, но он изменяет внутреннее состояние, устанавливая скорость равной 0. Метод assertEqual()
предоставляемый базовым классом TestCase
, используется здесь для проверки того, что вызов stop()
работал как ожидалось.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
def test_stop(self):
self.car.speed = 5
self.car.stop()
# Verify the speed is 0 after stopping
self.assertEqual(0, self.car.speed)
# Verify it is Ok to stop again if the car is already stopped
self.car.stop()
self.assertEqual(0, self.car.speed)
|
Здесь на самом деле два теста. Первый тест — убедиться, что если скорость автомобиля равна 5 и вызывается stop()
, то скорость становится равной 0. Затем еще один тест — убедиться, что ничего не происходит, если снова вызвать stop()
когда автомобиль уже остановлен.
Позже я представлю еще несколько тестов для дополнительной функциональности.
Модуль Doctest
Модуль doctest довольно интересный. Это позволяет вам использовать интерактивные примеры кода в вашей строке документации и проверять результаты, включая повышенные исключения.
Я не использую и не рекомендую doctest для крупных систем. Правильное юнит-тестирование требует много работы. Тестовый код обычно намного больше, чем тестируемый код. Строки документации просто не подходят для написания всесторонних тестов. Они крутые, хотя. Вот как выглядит factorial
функция с doc-тестами:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
|
import math
def factorial(n):
«»»Return the factorial of n, an exact integer >= 0.
If the result is small enough to fit in an int, return an int.
Else return a long.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> [factorial(long(n)) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000L
>>> factorial(30L)
265252859812191058636308480000000L
>>> factorial(-1)
Traceback (most recent call last):
…
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
…
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000L
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
…
OverflowError: n too large
«»»
if not n >= 0:
raise ValueError(«n must be >= 0»)
if math.floor(n) != n:
raise ValueError(«n must be exact integer»)
if n+1 == n: # catch a value like 1e300
raise OverflowError(«n too large»)
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == «__main__»:
import doctest
doctest.testmod()
|
Как видите, строка документации намного больше, чем код функции. Это не способствует удобочитаемости.
Запуск тестов
OK. Вы написали свои юнит-тесты. Для большой системы у вас будет десятки / сотни / тысячи модулей и классов в нескольких каталогах. Как вы выполняете все эти тесты?
Модуль unittest предоставляет различные средства для группировки тестов и их программного запуска. Проверьте загрузку и запуск тестов . Но самый простой способ — это тестовое открытие. Эта опция была добавлена только в Python 2.7. До 2.7 вы могли использовать нос, чтобы обнаружить и запустить тесты. У Nose есть несколько других преимуществ, таких как запуск тестовых функций без необходимости создавать класс для ваших тестовых случаев. Но для целей этой статьи давайте придерживаться unittest.
Чтобы обнаружить и запустить свои тесты, основанные на unittest, просто введите в командной строке:
python -m unittest discover
unittest просканирует все файлы и подкаталоги, запустит все найденные тесты и предоставит хороший отчет, а также время выполнения. Если вы хотите посмотреть, какие тесты он выполняет, вы можете добавить флаг -v:
python -m unittest discover -v
Есть несколько флагов, которые управляют операцией:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
python -m unittest -h
Usage: python -m unittest [options] [tests]
Options:
-h, —help Show this message
-v, —verbose Verbose output
-q, —quiet Minimal output
-f, —failfast Stop on first failure
-c, —catch Catch control-C and display results
-b, —buffer Buffer stdout and stderr during test runs
Examples:
python -m unittest test_module — run tests from test_module
python -m unittest module.TestClass — run tests from module.TestClass
python -m unittest module.Class.test_method — run specified test method
[tests] can be a list of any number of test modules, classes and test
methods.
Alternative Usage: python -m unittest discover [options]
Options:
-v, —verbose Verbose output
-f, —failfast Stop on first failure
-c, —catch Catch control-C and display results
-b, —buffer Buffer stdout and stderr during test runs
-s directory Directory to start discovery (‘.’ default)
-p pattern Pattern to match test files (‘test*.py’ default)
-t directory Top level directory of project (default to
start directory)
For test discovery all test modules must be importable from the top
level directory of the project.
|
Тестовое покрытие
Тестовое покрытие — это поле, которым часто пренебрегают. Охват означает, сколько кода на самом деле проверено вашими тестами. Например, если у вас есть функция с оператором if-else
и вы тестируете только ветку if
, тогда вы не знаете, работает ли ветвь else
или нет. В следующем примере кода функция add()
проверяет тип своих аргументов. Если оба являются целыми числами, он просто добавляет их.
Если оба являются строками, он пытается преобразовать их в целые числа и добавляет их. В противном случае это вызывает исключение. Функция test_add()
тестирует функцию add()
с аргументами, которые являются как целыми числами, так и с аргументами, которые являются плавающими, и проверяет правильное поведение в каждом случае. Но тестовое покрытие является неполным. Случай строковых аргументов не был проверен. В результате тест проходит успешно, но опечатка в ветви, где аргументы являются обеими строками, не была обнаружена (см. Там «intg»?).
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import unittest
def add(a, b):
«»»This function adds two numbers a, b and returns their sum
a and b may integers
«»»
if isinstance(a, int) and isinstance(b, int):
return a + b
elseif isinstance(a, str) and isinstance(b, str):
return int(a) + intg(b)
else:
raise Exception(‘Invalid arguments’)
class Test(unittest.TestCase):
def test_add(self):
self.assertEqual(5, add(2, 3))
self.assertEqual(15, add(-6, 21))
self.assertRaises(Exception, add, 4.0, 5.0)
unittest.main()
|
Вот вывод:
01
02
03
04
05
06
07
08
09
10
11
|
———————————————————————-
Ran 1 test in 0.000s
OK
Process finished with exit code 0
|
Практические юнит-тесты
Написание промышленных тестовых модулей не просто и не просто. Есть несколько вещей, которые следует учитывать, и которые необходимо сделать.
Дизайн для тестируемости
Если ваш код — это то, что называется формально-спагетти-кодом или большой шарик грязи, где разные уровни абстракции смешаны вместе, и каждый фрагмент кода зависит от каждого другого фрагмента кода, вам будет сложно его протестировать. Кроме того, всякий раз, когда вы что-то меняете, вам также нужно обновить кучу тестов.
Хорошая новость заключается в том, что правильный программный дизайн общего назначения — это именно то, что вам нужно для тестирования. В частности, хорошо продуманный модульный код, где каждый компонент несет четкую ответственность и взаимодействует с другими компонентами через четко определенные интерфейсы, сделает написание хороших модульных тестов удовольствием.
Например, наш класс SelfDrivingCar
отвечает за работу машины на высоком уровне: идти, останавливаться, перемещаться. У него есть метод calculate_distance_to_object_in_front()
который еще не был реализован. Эта функциональность, вероятно, должна быть реализована совершенно отдельной подсистемой. Он может включать в себя считывание данных с различных датчиков, взаимодействие с другими автомобилями с автоматическим управлением, целый стек машинного зрения для анализа изображений с нескольких камер.
Посмотрим, как это работает на практике. SelfDrivingCar
примет аргумент с именем object_detector
, у которого есть метод object_detector
calculate_distance_to_object_in_front()
, и делегирует эту функциональность этому объекту. Теперь нет необходимости в модульном тестировании, потому что object_detector
отвечает (и должен быть проверен) за это. Вы все еще хотите провести модульное тестирование того факта, что вы правильно используете object_detector
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
class SelfDrivingCar(object):
def __init__(self, object_detector):
self.object_detector
self.speed = 0
self.destination = None
def _calculate_distance_to_object_in_front(self):
return self.object_detector.calculate_distance_to_object_in_front()
|
Затрат и выгод
Количество усилий, которое вы вкладываете в тестирование, должно соотноситься со стоимостью сбоя, стабильностью кода и тем, насколько легко его исправить, если в процессе обнаруживаются проблемы.
Например, наш класс для самостоятельного вождения очень важен. Если метод stop()
не работает должным образом, наша машина с автоматическим управлением может убить людей, уничтожить имущество и сорвать весь рынок автомобилей с автоматическим управлением. Если вы разрабатываете автомобиль с автоматическим управлением, я подозреваю, что ваши юнит-тесты для метода stop()
будут немного более строгими, чем мой.
С другой стороны, если одна кнопка в вашем веб-приложении на странице, которая скрыта на три уровня ниже вашей главной страницы, немного мигает, когда кто-то нажимает на нее, вы можете исправить это, но, вероятно, не добавите специальный модульный тест для этого случая. Экономика просто не оправдывает это.
Тестирование мышления
Тестирование мышления важно. Один принцип, который я использую, состоит в том, что у каждого куска кода есть как минимум два пользователя: другой код, который его использует, и тест, который его тестирует. Это простое правило очень помогает с дизайном и зависимостями. Если вы помните, что вам нужно написать тест для своего кода, вы не добавите много зависимостей, которые сложно восстановить во время тестирования.
Например, предположим, что ваш код должен что-то вычислять. Для этого ему необходимо загрузить некоторые данные из базы данных, прочитать файл конфигурации и динамически обращаться к API REST для получения актуальной информации. Все это может потребоваться по разным причинам, но объединение всего этого в одну функцию значительно усложнит модульное тестирование. Это все еще возможно с помощью макетов, но гораздо лучше правильно структурировать ваш код.
Чистые функции
Самый простой код для тестирования — это чистые функции. Чистые функции — это функции, которые получают доступ только к значениям своих параметров, не имеют побочных эффектов и возвращают один и тот же результат при вызове с одинаковыми аргументами. Они не изменяют состояние вашей программы, не обращаются к файловой системе или сети. Их преимущества слишком велики, чтобы считать их здесь.
Почему их легко проверить? Потому что нет необходимости устанавливать специальную среду для тестирования. Вы просто передаете аргументы и проверяете результат. Вы также знаете, что пока тестируемый код не изменяется, ваш тест не должен меняться.
Сравните это с функцией, которая читает файл конфигурации XML. Ваш тест должен будет создать файл XML и передать его имя файла тестируемому коду. Ничего страшного. Но предположим, что кто-то решил, что XML отвратителен, и все файлы конфигурации должны быть в формате JSON. Они занимаются своими делами и конвертируют все файлы конфигурации в JSON. Они запускают все тесты, включая ваши тесты, и все они проходят!
Почему? Потому что код не изменился. Он по-прежнему ожидает файл конфигурации XML, а ваш тест все еще создает для него файл XML. Но в производстве ваш код получит файл JSON, который он не сможет проанализировать.
Тестирование обработки ошибок
Обработка ошибок — еще одна важная вещь для тестирования. Это тоже часть дизайна. Кто несет ответственность за правильность ввода? Каждая функция и метод должны быть понятны об этом. Если это ответственность функции, она должна проверить свои входные данные, но если это ответственность вызывающего, то функция может просто заняться своими делами и предположить, что ввод правильный. Общая правильность системы будет обеспечена с помощью тестов для вызывающей стороны, чтобы убедиться, что она передает только правильные данные вашей функции.
Как правило, вы хотите проверить ввод в открытом интерфейсе вашего кода, потому что вы не обязательно знаете, кто будет вызывать ваш код. Давайте посмотрим на метод drive()
самостоятельного вождения автомобиля. Этот метод ожидает параметр ‘destination’. Параметр ‘destination’ будет использоваться позже в навигации, но метод привода ничего не делает для проверки его правильности.
Давайте предположим, что пункт назначения должен быть кортежем широты и долготы. Существуют всевозможные тесты, которые можно проверить, чтобы убедиться, что он действителен (например, пункт назначения посреди моря). Для наших целей давайте просто убедитесь, что это кортеж с плавающей точкой в диапазоне от 0,0 до 90,0 для широты и от -180,0 до 180,0 для долготы.
Вот обновленный класс SelfDrivingCar
. Я реализовал тривиально некоторые из не реализованных методов, потому что метод drive()
вызывает некоторые из этих методов прямо или косвенно.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
class SelfDrivingCar(object):
def __init__(self, object_detector):
self.object_detector = object_detector
self.speed = 0
self.destination = None
def _accelerate(self):
self.speed += 1
def _decelerate(self):
if self.speed > 0:
self.speed -= 1
def _advance_to_destination(self):
distance = self._calculate_distance_to_object_in_front()
if distance < 10:
self.stop()
elif distance < self.speed / 2:
self._decelerate()
elif self.speed < self._get_speed_limit():
self._accelerate()
def _has_arrived(self):
return True
def _calculate_distance_to_object_in_front(self):
return self.object_detector.calculate_distance_to_object_in_front()
def _get_speed_limit(self):
return 65
def stop(self):
self.speed = 0
def drive(self, destination):
self.destination = destination
while not self._has_arrived():
self._advance_to_destination()
self.stop()
|
Чтобы проверить обработку ошибок в тесте, я передам недопустимые аргументы и проверю, что они правильно отклонены. Вы можете сделать это с помощью self.assertRaises()
unittest.TestCase
. Этот метод завершается успешно, если тестируемый код действительно вызывает исключение.
Давайте посмотрим на это в действии. Метод test_drive()
передает широту и долготу за пределы допустимого диапазона и ожидает, что метод drive()
вызовет исключение.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
from unittest import TestCase
from self_driving_car import SelfDrivingCar
class MockObjectDetector(object):
def calculate_distance_to_object_in_front(self):
return 20
class SelfDrivingCarTest(TestCase):
def setUp(self):
self.car = SelfDrivingCar(MockObjectDetector())
def test_stop(self):
self.car.speed = 5
self.car.stop()
# Verify the speed is 0 after stopping
self.assertEqual(0, self.car.speed)
# Verify it is Ok to stop again if the car is already stopped
self.car.stop()
self.assertEqual(0, self.car.speed)
def test_drive(self):
# Valid destination
self.car.drive((55.0, 66.0))
# Invalid destination wrong range
self.assertRaises(Exception, self.car.drive, (-55.0, 200.0))
|
Тест не пройден, поскольку метод drive()
не проверяет правильность своих аргументов и не вызывает исключения. Вы получите хороший отчет с полной информацией о том, что не удалось, где и почему.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
python -m unittest discover -v
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) … FAIL
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) … ok
======================================================================
FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)
———————————————————————-
Traceback (most recent call last):
File «/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py», line 29, in test_drive
self.assertRaises(Exception, self.car.drive, (-55.0, 200.0))
AssertionError: Exception not raised
———————————————————————-
Ran 2 tests in 0.000s
FAILED (failures=1)
|
Чтобы исправить это, давайте обновим метод drive()
чтобы фактически проверить диапазон его аргументов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
def drive(self, destination):
lat, lon = destination
if not (0.0 <= lat <= 90.0):
raise Exception(‘Latitude out of range’)
if not (-180.0 <= lon <= 180.0):
raise Exception(‘Latitude out of range’)
self.destination = destination
while not self._has_arrived():
self._advance_to_destination()
self.stop()
|
Теперь все испытания пройдены.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
python -m unittest discover -v
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) … ok
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) … ok
———————————————————————-
Ran 2 tests in 0.000s
OK
|
Тестирование частных методов
Вы должны проверить каждую функцию и метод? В частности, стоит ли тестировать закрытые методы, вызываемые только вашим кодом? Обычно неудовлетворительный ответ: «Это зависит».
Я постараюсь быть полезным здесь и скажу вам, от чего это зависит. Вы точно знаете, кто вызывает ваш приватный метод — это ваш собственный код. Если ваши тесты для открытых методов, которые вызывают ваш приватный метод, являются всеобъемлющими, то вы уже полностью тестировали свои приватные методы. Но если приватный метод очень сложен, вы можете проверить его самостоятельно. Используйте свое суждение.
Как организовать свои юнит-тесты
В большой системе не всегда понятно, как организовать тесты. Нужно ли иметь один большой файл со всеми тестами для пакета или один тестовый файл для каждого класса? Должны ли тесты находиться в том же файле, что и тестируемый код, или в том же каталоге?
Вот система, которую я использую. Тесты должны быть полностью отделены от тестируемого кода (поэтому я не использую doctest). В идеале ваш код должен быть в пакете. Тесты для каждого пакета должны находиться в каталоге вашего пакета. В каталоге tests должен быть один файл для каждого модуля вашего пакета с именем test_<module name>
.
Например, если в вашем пакете три модуля: module_1.py
, module_2.py
и module_3.py
, у вас должно быть три тестовых файла: test_module_1.py
, test_module_2.py
и test_module_3.py
в каталоге tests.
Это соглашение имеет несколько преимуществ. Просто просматривая каталоги, вы понимаете, что не забыли полностью протестировать какой-либо модуль. Это также помогает организовать тесты в разумных размерах. Если предположить, что ваши модули имеют разумный размер, тогда тестовый код для каждого модуля будет находиться в своем собственном файле, который может быть немного больше, чем тестируемый модуль, но все же что-то, что удобно помещается в одном файле.
Вывод
Модульные тесты являются основой твердого кода. В этом уроке я рассмотрел некоторые принципы и рекомендации для модульного тестирования и объяснил причины нескольких лучших практик. Чем больше система, которую вы строите, тем важнее становятся юнит-тесты. Но юнит-тестов недостаточно. Другие типы тестов также необходимы для крупномасштабных систем: интеграционные тесты, тесты производительности, нагрузочные тесты, тесты на проникновение, приемочные тесты и т. Д.