Введение в модульное тестирование Python с помощью unittest и pytest

Могу ли я стать полноценным разработчиком Python Программирование и разработка

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

Введение в тестирование программного обеспечения

Тестирование программного обеспечения — это процесс изучения поведения программного продукта для оценки и проверки его соответствия спецификациям. Программные продукты могут состоять из тысяч строк кода и сотен компонентов, которые работают вместе. Если одна строка не работает должным образом, ошибка может распространяться и вызывать другие ошибки. Таким образом, чтобы быть уверенным, что программа работает так, как предполагается, ее необходимо протестировать.

Поскольку современное программное обеспечение может быть довольно сложным, существует несколько уровней тестирования, которые оценивают различные аспекты корректности. Как указано в программе ISTQB Certified Test Foundation Level, существует четыре уровня тестирования программного обеспечения:

  1. Модульное тестирование, которое проверяет определенные строки кода.
  2. Интеграционное тестирование, которое проверяет интеграцию между многими модулями.
  3. Системное тестирование, при котором тестируется вся система
  4. Приемочное тестирование, которое проверяет соответствие бизнес-целям

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

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

Что такое модульное тестирование?

Это первый уровень тестирования, также называемый компонентным тестированием. В этой части тестируются отдельные программные компоненты. В зависимости от языка программирования программная единица может быть классом, функцией или методом. Например, если у вас есть класс Java с именем ArithmeticOperations, который имеет методы multiplyи divide, модульные тесты для ArithmeticOperationsкласса должны будут проверить правильное поведение методов multiplyи divide.

Читайте также:  Как освоить валидаторы Pydantic?

Модульные тесты обычно выполняются тестировщиками программного обеспечения. Для запуска модульных тестов тестерам программного обеспечения (или разработчикам) необходим доступ к исходному коду, поскольку сам исходный код является тестируемым объектом. По этой причине такой подход к тестированию программного обеспечения, который напрямую проверяет исходный код, называется тестированием белого ящика.

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

Почему вы должны проводить модульное тестирование

Основное преимущество тестирования программного обеспечения заключается в том, что оно повышает качество программного обеспечения. Качество программного обеспечения имеет решающее значение, особенно в мире, где программное обеспечение выполняет широкий спектр наших повседневных действий. Улучшение качества ПО — пока слишком туманная цель. Попробуем уточнить, что мы понимаем под качеством программного обеспечения. Согласно стандарту ISO/IEC 9126-1 ISO 9126, качество программного обеспечения включает в себя следующие факторы:

  • надежность
  • функциональность
  • эффективность
  • удобство использования
  • ремонтопригодность
  • портативность

Если вы владеете компанией, тестирование программного обеспечения — это деятельность, которую вы должны тщательно обдумать, поскольку она может оказать влияние на ваш бизнес. Например, в мае 2022 года Tesla отозвала 130 000 автомобилей из-за проблем с информационно-развлекательными системами транспортных средств. Затем эта проблема была исправлена ​​с помощью обновления программного обеспечения, распространяемого «по воздуху». Эти сбои стоили компании времени и денег, а также создавали проблемы для клиентов, поскольку они какое-то время не могли пользоваться своими автомобилями. Тестирование программного обеспечения действительно стоит денег, но верно и то, что компании могут сэкономить миллионы на технической поддержке.

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

К другим преимуществам модульного тестирования относятся:

  • Упрощение интеграции : гарантируя, что все компоненты работают по отдельности, легче решать проблемы интеграции.
  • Минимизация регрессии кода : при большом количестве тестовых случаев, если некоторые модификации исходного кода в будущем вызовут проблемы, найти проблему будет проще.
  • Предоставление документации : проверяя правильное сопоставление между вводом и выводом, модульные тесты предоставляют документацию о том, как работает тестируемый метод или класс.

Разработка стратегии тестирования

Давайте теперь посмотрим, как разработать стратегию тестирования.

Определение области тестирования

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

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

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

  • Риск : какие последствия для бизнеса могут быть, если ошибка повлияет на этот компонент?
  • Время : как скоро вы хотите, чтобы ваш программный продукт был готов? У вас есть крайний срок?
  • Бюджет : сколько денег вы готовы инвестировать в тестирование?

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

Качества модульного теста

  • Быстро. Модульные тесты в основном выполняются автоматически, а значит, они должны быть быстрыми. Разработчики чаще пропускают медленные модульные тесты, потому что они не обеспечивают мгновенной обратной связи.
  • Изолированный. Модульные тесты автономны по определению. Они тестируют отдельные единицы кода и не зависят ни от чего внешнего (например, от файла или сетевого ресурса).
  • Повторяемый. Модульные тесты выполняются многократно, и результат должен быть постоянным во времени.
  • Надежный. Модульные тесты не пройдут, только если в тестируемой системе есть ошибка. Окружающая среда или порядок выполнения тестов не должны иметь значения.
  • Правильно назван. Название теста должно содержать соответствующую информацию о самом тесте.

Остался последний шаг перед тем, как углубиться в модульное тестирование в Python. Как мы организуем наши тесты, чтобы сделать их чистыми и легко читаемыми? Мы используем шаблон под названием Arrange, Act and Assert (AAA).

Шаблон ААА

Шаблон Arrange, Act and Assert — это распространенная стратегия, используемая для написания и организации модульных тестов. Это работает следующим образом:

  • На этапе аранжировки устанавливаются все объекты и переменные, необходимые для теста.
  • Затем, на этапе действия, вызывается тестируемая функция/метод/класс.
  • В конце, на этапе утверждения, мы проверяем результат теста.

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

Модульное тестирование в Python: unittest или pytest?

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

Введение в модульный тест

Стандартная библиотека Python включает среду модульного тестирования unittest. Эта структура вдохновлена ​​JUnit, которая является средой модульного тестирования в Java.

Как указано в официальной документации, unittestподдерживает несколько важных концепций, о которых мы упомянем в этой статье:

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

unittestимеет свой способ написания тестов. В частности, нам необходимо:

  1. напишите наши тесты как методы класса, который является подклассомunittest.TestCase
  2. использовать специальные методы утверждения

Поскольку unittestон уже установлен, мы готовы написать наш первый модульный тест!

Написание модульных тестов с использованием unittest

Допустим, у нас есть BankAccountкласс:

import unittest

class BankAccount:
  def __init__(self, id):
    self.id = id
    self.balance = 0

  def withdraw(self, amount):
    if self.balance >= amount:
      self.balance -= amount
      return True
    return False

  def deposit(self, amount):
    self.balance += amount
    return True

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

В тот же файл Python мы можем добавить следующий код:

class TestBankOperations(unittest.TestCase):
    def test_insufficient_deposit(self):
      # Arrange
      a = BankAccount(1)
      a.deposit(100)
      # Act
      outcome = a.withdraw(200)
      # Assert
      self.assertFalse(outcome)

Мы создаем класс TestBankOperations, который является подклассом unittest.TestCase. Таким образом, мы создаем новый тестовый пример.

Внутри этого класса мы определяем одну тестовую функцию с методом, который начинается с test. Это важно, потому что каждый метод тестирования должен начинаться со слова test.

Мы ожидаем, что этот тестовый метод вернет значение False, что означает, что операция не удалась. Чтобы подтвердить результат, мы используем специальный метод утверждения, называемый assertFalse().

Мы готовы выполнить тест. Давайте запустим эту команду в командной строке:

python -m unittest example.py

Здесь example.pyимя файла, содержащего весь исходный код. Вывод должен выглядеть примерно так:

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

OK

Хороший! Это означает, что наш тест прошел успешно. Давайте теперь посмотрим, как выглядит вывод при сбое. Добавляем новый тест в предыдущий класс. Давайте попробуем внести отрицательную сумму денег, что, конечно же, невозможно. Справится ли наш код с этим сценарием?

Это наш новый метод тестирования:

  def test_negative_deposit(self):
    # Arrange
    a = BankAccount(1)
    # Act
    outcome = a.deposit(-100)
    # Assert
    self.assertFalse(outcome)

Мы можем использовать подробный режим unittestдля выполнения этого теста, установив -vфлаг:

python -m unittest -v example.py

И вывод теперь другой:

test_insufficient_deposit (example.TestBankOperations) ... ok
test_negative_deposit (example.TestBankOperations) ... FAIL

======================================================================
FAIL: test_negative_deposit (example.TestBankOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "example.py", line 35, in test_negative_deposit
    self.assertFalse(outcome)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

В этом случае подробный флаг дает нам больше информации. Мы знаем, что test_negative_depositне удалось. В частности, AssertionErrorговорит нам, что ожидаемый результат должен был быть falseно True is not false, что означает, что метод вернул True.

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

  • assertEqual(x,y), который проверяет,x == y
  • assertRaises(exception_type), который проверяет, возникло ли конкретное исключение
  • assertIsNone(x), который проверяет, еслиx is None
  • assertIn(x,y), который проверяет, еслиx in y

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

Введение в pytest

Фреймворк pytestпредставляет собой фреймворк модульного тестирования Python, который имеет несколько важных функций:

  • это позволяет выполнять комплексное тестирование с использованием меньшего количества кода
  • он поддерживает unittestнаборы тестов
  • он предлагает более 800 внешних плагинов

Поскольку pytestон не установлен по умолчанию, мы должны сначала установить его. Обратите внимание, что pytestтребуется Python 3.7+.

Установка pytest

Установка pytestдовольно проста. Вам просто нужно запустить эту команду:

pip install -U pytest

Затем убедитесь, что все было установлено правильно, набрав это:

pytest --version

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

pytest 7.1.2

Хорошо! Давайте напишем первый тест, используя pytest.

Написание модульных тестов с использованием pytest

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

Для тестирования pytestнам нужно:

  • Создайте каталог и поместите в него наши тестовые файлы.
  • Запишите наши тесты в файлы, имена которых начинаются test_или заканчиваются на _test.py. pytestбудет искать эти файлы в текущем каталоге и его подкаталогах.

Итак, мы создаем файл с именем test_bank.pyи помещаем его в папку. Вот как выглядит наша первая тестовая функция:

def test_insufficient_deposit():
  # Arrange
  a = BankAccount(1)
  a.deposit(100)
  # Act
  outcome = a.withdraw(200)
  # Assert
  assert outcome == False

Как вы заметили, единственное, что изменилось по отношению к unittestверсии, — это секция assert. Здесь мы используем простые методы утверждения Python.

И теперь мы можем взглянуть на test_bank.pyфайл:

class BankAccount:
  def __init__(self, id):
    self.id = id
    self.balance = 0

  def withdraw(self, amount):
    if self.balance >= amount:
      self.balance -= amount
      return True
    return False

  def deposit(self, amount):
    self.balance += amount
    return True

def test_insufficient_deposit():
  # Arrange
  a = BankAccount(1)
  a.deposit(100)
  # Act
  outcome = a.withdraw(200)
  # Assert
  assert outcome == False

Чтобы запустить этот тест, давайте откроем командную строку в папке, где находится test_bank.pyфайл. Затем запустите это:

pytest

Вывод будет примерно таким:

======== test session starts ======== 
platform win32 -- Python 3.7.11, pytest-7.1.2, pluggy-0.13.1
rootdir: \folder
plugins: anyio-2.2.0
collected 1 item

test_bank.py .                                                                                                   [100%]

======== 1 passed in 0.02s ========

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

Давайте перейдем к проваленному тесту!

Мы используем второй метод, который мы написали ранее, который называется test_negative_deposit. Мы рефакторим раздел assert, и вот результат:

def test_negative_deposit():
  # Arrange
  a = BankAccount(1)
  # Act
  outcome = a.deposit(-100)
  # Assert
  assert outcome == False

Мы запускаем тест так же, как и раньше, и это должно быть результатом:

======= test session starts =======
platform win32 -- Python 3.7.11, pytest-7.1.2, pluggy-0.13.1
rootdir: \folder
plugins: anyio-2.2.0
collected 2 items

test_bank.py .F                                                                                                  [100%]

======= FAILURES =======
_____________ test_negative_deposit _____________
    def test_negative_deposit():
      # Arrange
      a = BankAccount(1)
      # Act
      outcome = a.deposit(-100)
      # Assert
>     assert outcome == False
E     assert True == False

test_bank.py:32: AssertionError
======= short test summary info =======
FAILED test_bank.py::test_negative_deposit - assert True == False
======= 1 failed, 1 passed in 0.15s =======

Анализируя вывод, мы можем прочитать collected 2 items, что означает, что были выполнены два теста. test_negative_depositПрокрутив вниз, мы можем прочитать, что при тестировании метода произошел сбой. В частности, ошибка произошла при оценке утверждения. Кроме того, в отчете также говорится, что значение outcomeпеременной равно True, значит, depositметод содержит ошибку.

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

Заключение

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

Мы использовали два фреймворка Python под названием unittestи pytest. У обоих есть полезные функции, и они являются двумя наиболее часто используемыми фреймворками для модульного тестирования Python.

В конце концов, мы рассмотрели два основных тестовых случая, чтобы дать вам представление о том, как пишутся тесты по шаблону Arrange, Act и Assert.

Надеюсь, я убедил вас в важности тестирования программного обеспечения. Выберите фреймворк, такой как unittestили pytest, и начните тестирование — потому что это стоит дополнительных усилий!

Оцените статью
bestprogrammer.ru
Добавить комментарий