Как написать модульный тест на Python: простое руководство

Знание того, как писать модульные тесты на Python, имеет решающее значение для разработчиков. Простого написания кода приложения недостаточно, написание тестов является обязательным.

Модульные тесты позволяют вам тестировать автономные блоки вашего кода независимо друг от друга. Python предоставляет фреймворк unittest, который помогает писать модульные тесты в соответствии с предопределенным форматом. Чтобы протестировать свой код с помощью фреймворка unittest, вы создаете тестовые классы и тестовые методы в каждом тестовом классе.

В этом уроке вы напишете модульные тесты для простого класса, представляющего пользователя в видеоигре.

Давайте научимся писать и запускать тесты на Python!

Почему стоит писать тесты на Python?

Когда вы пишете код, вы можете предположить, что он работает нормально, основываясь только на быстрых ручных тестах, которые вы выполняете после написания этого кода.

Но будете ли вы запускать одни и те же тесты вручную каждый раз, когда меняете код? Маловероятно!

Чтобы протестировать фрагмент кода, вам необходимо охватить все возможные способы использования кода в вашей программе (например, передачу различных типов значений для аргументов, если вы тестируете функцию Python).

Кроме того, вы хотите быть уверены, что тесты, которые вы выполняете в первый раз после написания кода, всегда будут выполняться автоматически в будущем при изменении этого кода. Вот почему вы создаете набор тестов для своего кода.

Модульные тесты — это один из типов тестов, которые вы можете написать для проверки своего кода. Каждый модульный тест охватывает определенный вариант использования этого кода и может выполняться каждый раз, когда вы изменяете свой код.

Почему рекомендуется всегда внедрять модульные тесты и выполнять модульные тесты при изменении кода?

Выполнение модульных тестов помогает вам убедиться, что вы не нарушаете существующую логику в вашем коде, когда вы вносите в него изменения (например, чтобы предоставить новую функцию). С помощью модульных тестов вы тестируете код непрерывно и, следовательно, вы сразу замечаете, во время написания кода, если вы нарушаете существующую функциональность. Это позволяет вам немедленно исправлять ошибки и поддерживать стабильность исходного кода Python.

Класс, для которого мы будем писать модульные тесты

Следующий класс представляет пользователя, который играет в видеоигру. Этот класс имеет следующие функции:

  • Активируйте учетную запись пользователя.
  • Проверьте, активна ли учетная запись пользователя.
  • Добавьте баллы пользователю.
  • Извлечь баллы, назначенные пользователю.
  • Узнать уровень, которого достиг пользователь в игре (зависит от количества очков).

Единственным атрибутом класса является словарь профиля, в котором хранятся все данные, связанные с пользователем.

class User:

    def __init__(self):
        self.profile = {'active': False, 'level': 1, 'points': 0}

    def activate(self):
        self.profile['active'] = True

    def is_active(self):
        return self.profile['active']

    def get_level(self):
        return self.profile['level']

    def get_points(self):
        return self.profile['points']

    def add_points(self, additional_points):
        self.profile['points'] += additional_points

        if self.get_points() > 300:
            self.profile['level'] = 3
        elif self.get_points() > 200:
            self.profile['level'] = 2

Давайте создадим экземпляр этого класса и выполним несколько ручных тестов, чтобы убедиться, что он работает так, как ожидается.

Что такое ручное тестирование в Python?

Ручное тестирование — это процесс проверки функциональности вашего приложения путем последовательного рассмотрения вариантов использования.

Подумайте об этом как о списке тестов, которые вы вручную запускаете против своего приложения, чтобы убедиться, что оно ведет себя так, как ожидается. Это также называется исследовательским тестированием.

Вот пример…

Мы протестируем три различных варианта использования для нашего класса. Первый шаг перед этим — создать экземпляр вашего класса:

user1 = User()
print(user1.__dict__)

[output]
{'profile': {'active': False, 'level': 1, 'points': 0}}

Как видите, профиль пользователя инициализирован правильно.

1-й вариант использования: Состояние пользователя активно после завершения активации – УСПЕХ

user1.activate()
print(user1.is_active())

[output]
True

2-й вариант использования: пользовательские баллы увеличиваются правильно – УСПЕХ

user1.add_points(25)
print(user1.get_points())

[output]
25

3-й вариант использования: уровень пользователя меняется с 1 на 2, когда количество баллов превышает 200 – УСПЕХ

print("User total points: {}".format(user1.get_points()))
print("User level: {}".format(user1.get_level()))
user1.add_points(205)
print("User total points: {}".format(user1.get_points()))
print("User level: {}".format(user1.get_level()))

[output]
User total points: 0
User level: 1
User total points: 205
User level: 2

Эти тесты дают нам некоторое подтверждение того, что код выполняет то, для чего он был разработан.

Однако проблема в том, что вам придется запускать каждый отдельный тест вручную каждый раз при изменении кода, учитывая, что любое изменение может нарушить существующий код.

Это не лучший подход, ведь это всего лишь три теста. Представьте, что вам пришлось бы запускать сотни тестов каждый раз, когда ваш код изменяется.

Вот почему модульные тесты важны как форма автоматизированного тестирования.

Как написать модульный тест для класса на Python

Теперь мы увидим, как использовать фреймворк Python unittest для написания трех тестов, выполненных в предыдущем разделе.

Во-первых, предположим, что основной код приложения находится в файле user.py. Вы будете писать свои модульные тесты в файле с именем test_user.py.

Общее соглашение об именовании для модульных тестов: имя файла, используемого для модульных тестов, просто добавляет «test_» к имени файла.py, в котором находится тестируемый код Python.

Чтобы использовать фреймворк unittest, нам необходимо сделать следующее:

  • импортировать модуль unittest
  • создайте тестовый класс, который наследует unittest.TestCase. Назовем его TestUser.
  • добавьте один метод для каждого теста.
  • добавьте точку входа для выполнения тестов из командной строки с помощью unittest.main.

Вот пример юнит-теста на Python:

import unittest

class TestUser(unittest.TestCase):

    def test_user_activation(self):
        pass

    def test_user_points_update(self):
        pass

    def test_user_level_change(self):
        pass

if __name__ == '__main__':
    unittest.main()

Вы создали структуру тестового класса. Перед добавлением реализации к каждому методу класса модульного теста давайте попробуем выполнить тесты, чтобы посмотреть, что получится.

Для запуска модульных тестов в Python можно использовать следующий синтаксис:

$ python test_user.py
...
---------------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Значение __name__ проверяется при запуске файла test_user.py через командную строку.

Примечание: во всех примерах мы используем версию Python 3.

Как написать модульный тест на Python?

Теперь, когда у нас есть структура нашего тестового класса, мы можем реализовать каждый тестовый метод.

Модульные тесты имеют такое название, поскольку они проверяют блоки вашего кода Python, в данном случае поведение методов в классе User.

Каждый модульный тест должен быть разработан для проверки того, что поведение нашего класса является правильным, когда происходит определенная последовательность событий. В рамках каждого модульного теста вы предоставляете набор входных данных, а затем проверяете, что выходные данные совпадают с вашими ожиданиями, используя концепцию утверждений.

Другими словами, каждый модульный тест автоматизирует ручные тесты, которые мы выполнили ранее.

Технически вы можете использовать оператор assert для проверки значения, возвращаемого методами нашего класса User.

На практике фреймворк unittest предоставляет свои методы утверждения. Мы будем использовать в наших тестах следующее:

  • assertEqual
  • assertTrue

Начнем с первого тестового случая…

…на самом деле, прежде чем это сделать, нам нужно увидеть класс User из нашего тестового класса.

Как мы можем это сделать?

Вот содержимое текущего каталога:

$ ls
test_user.py user.py 

Чтобы использовать класс User в наших тестах, добавьте следующий импорт после импорта unittest в test_user.py:

from user import User

А теперь давайте реализуем три модульных теста.

1-й вариант использования: состояние пользователя становится активным после завершения активации.

def test_user_activation(self):
    user1 = User()
    user1.activate()
    self.assertTrue(user1.is_active())

В этом тесте мы активируем пользователя, а затем утверждаем, что метод is_active() возвращает True.

2-й вариант использования: пользовательские баллы увеличиваются правильно

def test_user_points_update(self):
    user1 = User()
    user1.add_points(25)
    self.assertEqual(user1.get_points(), 25)

На этот раз вместо assertTrue мы использовали assertEqual для проверки количества баллов, назначенных пользователю.

3-й вариант использования: уровень пользователя меняется с 1 на 2, когда количество баллов превышает 200.

def test_user_level_change(self):
    user1 = User()
    user1.add_points(205)
    self.assertEqual(user1.get_level(), 2)

Реализация этого модульного теста аналогична предыдущей, с той лишь разницей, что мы утверждаем значение уровня для пользователя.

И теперь настало время провести наши тесты…

$ python test_user.py
...
---------------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Все тесты прошли успешно!

Пример неудачного модульного теста

Прежде чем завершить этот урок, я хочу показать вам, что произойдет, если один из тестов не будет пройден.

Прежде всего, предположим, что в методе is_active() есть опечатка:

def is_active(self):
    return self.profile['active_user']

Я заменил атрибут active профиля пользователя на active_user, которого нет в словаре профилей.

Теперь проведите тесты еще раз…

$ python test_user.py
E..
===========================================================================
ERROR: test_user_activation (__main__.TestUser)
---------------------------------------------------------------------------
Traceback (most recent call last):
  File "test_user.py", line 9, in test_user_activation
    self.assertTrue(user1.is_active())
  File "/opt/Python/Tutorials/user.py", line 9, in is_active
    return self.profile['active_user']
KeyError: 'active_user'

---------------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (errors=1)

В первой строке выполнения теста вы можете увидеть:

E..

Каждый символ представляет собой выполнение теста. E обозначает ошибку, а точка обозначает успех.

Это означает, что первый тест в тестовом классе провален, а два других пройдены успешно.

Вывод тестового раннера также говорит нам, что ошибка вызвана частью assertTrue метода test_user_activation.

Это поможет вам определить неисправность кода и исправить ее.

Заключение

Это было интересное путешествие по модульному тестированию на Python.

Мы увидели, как использовать unittest, одну из тестовых сред, предоставляемых Python, для написания модульных тестов для ваших программ.

Теперь у вас есть все необходимое, чтобы начать писать тесты на Python для вашего приложения, если вы еще этого не сделали 🙂

Автор

Фото аватара

Владимир Михайлов

Программист на Python с большим количеством опыта и разнообразных проектов.