Модуль unittest в Python

Beginner

This tutorial is from open-source community. Access the source code

Введение

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

Вы также научитесь создавать и запускать базовые тестовые случаи (тест-кейсы) и проверять на наличие ожидаемых ошибок и исключений. Модуль unittest упрощает процесс создания тестовых наборов (тест-сьютов), запуска тестов и проверки результатов. Файл teststock.py будет создан в рамках этого практического занятия.

Создание вашего первого модульного теста (юнит-теста)

Модуль unittest языка Python представляет собой мощный инструмент, который предоставляет структурированный способ организации и выполнения тестов. Прежде чем мы приступим к написанию нашего первого модульного теста, давайте разберемся с некоторыми ключевыми концепциями. Тестовые фикстуры (test fixtures) - это методы, такие как setUp и tearDown, которые помогают подготовить окружение перед выполнением теста и очистить его после завершения. Тестовые случаи (test cases) - это отдельные единицы тестирования, тестовые наборы (test suites) - это коллекции тестовых случаев, а тестовые запускающие механизмы (test runners) отвечают за выполнение этих тестов и представление результатов.

В этом первом шаге мы создадим базовый тестовый файл для класса Stock, который уже определен в файле stock.py.

  1. Сначала откройте файл stock.py. Это поможет нам понять, какой класс Stock мы будем тестировать. Посмотрев на код в файле stock.py, мы можем увидеть, как устроен класс, какие у него атрибуты и методы. Чтобы просмотреть содержимое файла stock.py, выполните следующую команду в терминале:
cat stock.py
  1. Теперь пора создать новый файл с именем teststock.py с помощью вашего предпочитаемого текстового редактора. Этот файл будет содержать наши тестовые случаи для класса Stock. Вот код, который вам нужно написать в файле teststock.py:
## teststock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

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

Разберем основные компоненты этого кода:

  • import unittest: Эта строка импортирует модуль unittest, который предоставляет необходимые инструменты и классы для написания и запуска тестов на Python.
  • import stock: Это импортирует модуль, содержащий наш класс Stock. Без этого импорта мы не сможем получить доступ к классу Stock в нашем тестовом коде.
  • class TestStock(unittest.TestCase): Мы создаем новый класс с именем TestStock, который наследуется от unittest.TestCase. Это делает наш класс TestStock классом тестового случая, который может содержать несколько тестовых методов.
  • def test_create(self): Это тестовый метод. В фреймворке unittest все тестовые методы должны начинаться с префикса test_. Этот метод создает экземпляр класса Stock, а затем использует метод assertEqual для проверки, соответствуют ли атрибуты экземпляра Stock ожидаемым значениям.
  • assertEqual: Это метод, предоставляемый классом TestCase. Он проверяет, равны ли два значения. Если они не равны, тест завершится с ошибкой.
  • unittest.main(): Когда этот скрипт выполняется напрямую, unittest.main() запустит все тестовые методы в классе TestStock и отобразит результаты.
  1. После написания кода в файле teststock.py сохраните его. Затем выполните следующую команду в терминале для запуска теста:
python3 teststock.py

Вы должны увидеть вывод, похожий на следующий:

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

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

Расширение ваших тестовых случаев

Теперь, когда вы создали базовый тестовый случай, пришло время расширить объем тестирования. Добавление дополнительных тестов поможет вам охватить оставшуюся функциональность класса Stock. Таким образом, вы можете убедиться, что все аспекты класса работают как ожидается. Мы изменим класс TestStock, чтобы включить тесты для нескольких методов и свойств.

  1. Откройте файл teststock.py. Внутри класса TestStock мы добавим несколько новых тестовых методов. Эти методы будут тестировать разные части класса Stock. Вот код, который вам нужно добавить:
def test_create_keyword_args(self):
    s = stock.Stock(name='GOOG', shares=100, price=490.1)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_cost(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s.cost, 49010.0)

def test_sell(self):
    s = stock.Stock('GOOG', 100, 490.1)
    s.sell(20)
    self.assertEqual(s.shares, 80)

def test_from_row(self):
    row = ['GOOG', '100', '490.1']
    s = stock.Stock.from_row(row)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_repr(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)")

def test_eq(self):
    s1 = stock.Stock('GOOG', 100, 490.1)
    s2 = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s1, s2)

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

  • test_create_keyword_args: Этот тест проверяет, можно ли создать объект Stock с использованием именованных аргументов (keyword arguments). Он проверяет, что атрибуты объекта установлены правильно.
  • test_cost: Этот тест проверяет, возвращает ли свойство cost объекта Stock правильное значение, которое вычисляется как количество акций, умноженное на цену.
  • test_sell: Этот тест проверяет, правильно ли метод sell() объекта Stock обновляет количество акций после продажи части из них.
  • test_from_row: Этот тест проверяет, может ли метод класса from_row() создать новый экземпляр Stock из строки данных.
  • test_repr: Этот тест проверяет, возвращает ли метод __repr__() объекта Stock ожидаемое строковое представление.
  • test_eq: Этот тест проверяет, правильно ли метод __eq__() сравнивает два объекта Stock, чтобы определить, равны ли они.
  1. После добавления этих тестовых методов сохраните файл teststock.py. Затем запустите тесты снова, используя следующую команду в терминале:
python3 teststock.py

Если все тесты пройдены успешно, вы должны увидеть вывод, похожий на следующий:

......
----------------------------------------------------------------------
Ran 7 tests in 0.001s

OK

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

Тестирование на возникновение исключений

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

  1. Откройте файл teststock.py. Мы добавим несколько тестовых методов, которые предназначены для проверки возникновения исключений. Эти тесты помогут нам убедиться, что наш код ведет себя правильно при встрече с некорректными входными данными.
def test_shares_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.shares = '50'

def test_shares_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.shares = -50

def test_price_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.price = '490.1'

def test_price_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.price = -490.1

def test_attribute_error(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(AttributeError):
        s.share = 100  ## 'share' is incorrect, should be 'shares'

Теперь разберемся, как работают эти тесты на возникновение исключений.

  • Оператор with self.assertRaises(ExceptionType): создает менеджер контекста. Этот менеджер контекста проверяет, вызывает ли код внутри блока with указанное исключение.
  • Если ожидаемое исключение возникает внутри блока with, тест проходит успешно. Это означает, что наш код правильно определяет некорректные входные данные и вызывает соответствующую ошибку.
  • Если исключение не возникает или возникает другое исключение, тест завершается неудачно. Это показывает, что наш код, возможно, не обрабатывает некорректные входные данные так, как ожидалось.

Эти тесты предназначены для проверки следующих сценариев:

  • Установка атрибута shares в строку должна вызвать исключение TypeError, так как shares должен быть числом.
  • Установка атрибута shares в отрицательное число должна вызвать исключение ValueError, так как количество акций не может быть отрицательным.
  • Установка атрибута price в строку должна вызвать исключение TypeError, так как price должен быть числом.
  • Установка атрибута price в отрицательное число должна вызвать исключение ValueError, так как цена не может быть отрицательной.
  • Попытка установить несуществующий атрибут share (обратите внимание на отсутствие буквы 's') должна вызвать исключение AttributeError, так как правильное имя атрибута - shares.
  1. После добавления этих тестовых методов сохраните файл teststock.py. Затем запустите все тесты, используя следующую команду в терминале:
python3 teststock.py

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

............
----------------------------------------------------------------------
Ran 12 tests in 0.002s

OK

Двенадцать точек представляют все тесты, которые вы написали до сих пор. Было 7 тестов из предыдущего шага, и мы только что добавили 5 новых. Этот вывод показывает, что ваш код обрабатывает исключения как ожидается, что является хорошим признаком хорошо протестированной программы.

Запуск выбранных тестов и использование обнаружения тестов

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

Запуск конкретных тестов

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

  1. Чтобы запустить только тесты, связанные с созданием объекта Stock:
python3 -m unittest teststock.TestStock.test_create

В этой команде python3 -m unittest сообщает Python запустить модуль unittest. teststock - это имя тестового файла, TestStock - имя тестового класса, а test_create - конкретный тестовый метод, который мы хотим запустить. Запустив эту команду, вы можете быстро проверить, работает ли код, связанный с созданием объекта Stock, как ожидается.

  1. Чтобы запустить все тесты в классе TestStock:
python3 -m unittest teststock.TestStock

Здесь мы опускаем имя конкретного тестового метода. Таким образом, эта команда выполнит все тестовые методы внутри класса TestStock в файле teststock. Это полезно, когда вы хотите проверить общую функциональность тестовых случаев объекта Stock.

Использование обнаружения тестов

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

  1. Переименуйте текущий файл, чтобы он соответствовал шаблону именования для обнаружения тестов:
mv teststock.py test_stock.py

Механизм обнаружения тестов в unittest ищет файлы, соответствующие шаблону именования test_*.py. Переименовав файл в test_stock.py, мы облегчаем модулю unittest поиск и запуск тестов в этом файле.

  1. Запустите обнаружение тестов:
python3 -m unittest discover

Эта команда сообщает модулю unittest автоматически обнаружить и запустить все тестовые файлы, соответствующие шаблону test_*.py в текущем каталоге. Он просмотрит каталог и выполнит все найденные в соответствующих файлах тестовые случаи.

  1. Вы также можете указать каталог для поиска тестов:
python3 -m unittest discover -s . -p "test_*.py"

Где:

  • -s . указывает каталог, с которого начнется поиск (в данном случае текущий каталог). Точка (.) представляет текущий каталог. Вы можете изменить это на другой путь к каталогу, если хотите искать тесты в другом месте.
  • -p "test_*.py" - это шаблон для сопоставления тестовых файлов. Это гарантирует, что в качестве тестовых файлов будут рассматриваться только файлы с именами, начинающимися с test_ и имеющими расширение .py.

Вы должны увидеть, что все 12 тестов запускаются и проходят успешно, как и раньше.

  1. Переименуйте файл обратно в исходное имя для согласованности с лабораторной работой:
mv test_stock.py teststock.py

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

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

Резюме

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

Юнит-тестирование (unit testing) является фундаментальным навыком в разработке программного обеспечения, обеспечивающим надежность и правильность кода. Написание комплексных тестов помогает выявлять ошибки на ранних этапах и дает уверенность в поведении вашего кода. При разработке приложений на Python рассмотрите возможность применения подхода тестирования - на - основе разработки (test - driven development, TDD), когда тесты пишутся до реализации функциональности, чтобы получить более надежный и поддерживаемый код.