Баннер мобильный (3) Пройти тест

Как написать простые автотесты на Python

Тестируем ПО с помощью Unittest и Pytest

Инструкция

24 февраля 2025

Поделиться

Скопировано
Как написать простые автотесты на Python

Содержание

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

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

    Что такое автотесты и зачем они нужны

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

    Для автоматического тестирования какого-либо программного обеспечения (ПО) сначала необходимо научиться писать автотесты. Автотест — это тоже программа, а точнее сценарий, который имитирует взаимодействие пользователя с тестируемым приложением. Автоматические тесты, как следует из названия, работают в автоматическом режиме и выполняют свою работу значительно быстрее, чем ручные тесты. Их основная цель — выявить ошибки в работе ПО, обнаружить недоработки и оценить качество программного продукта.

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

    Решатель квадратных уравнений

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

    from math import sqrt
    
    def quadratic_equation_solver(a, b, c):
      result = []
      d = b * b - 4 * a * c
    
      if d == 0:
          result.append(-b / (2 * a))
      elif d > 0:
          result.append((-b + sqrt(d)) / (2 * a))
          result.append((-b - sqrt(d)) / (2 * a))
    
      return result
    
    def show_result(data):
      if len(data) == 1:
          print(f'Корень равен {data[0]:.02f}')
      elif len(data) == 2:
          for index, value in enumerate(data):
            print(f'Корень номер {index+1} равен {value:.02f}')
      else:
          print('Уравнение не имеет корней')
    
    def main():
      a = int(input('Введите первый коэффициент: '))
      b = int(input('Введите второй коэффициент: '))
      c = int(input('Введите третий коэффициент: '))
      result = quadratic_equation_solver(a, b, c)
      show_result(result)
    
    if __name__ == '__main__':
      main()

    Рассмотрим его более подробно. В функции main, которая является точкой входа в приложение, происходит ввод коэффициентов уравнения. Затем вызывается функция quadratic_equation_solver, на вход которой передаются введенные коэффициенты. Результат работы этой функции присваивается переменной result, которая, в свою очередь, передается в функцию show_result, отвечающую за вывод результатов решения. Теперь подробнее о работе этих функций.

    В функции quadratic_equation_solver сначала создается пустой список result, в котором будут храниться корни уравнения. Этот список функция возвращает как результат своей работы. Далее вычисляется дискриминант уравнения по формуле, известной еще со школьной скамьи. Затем происходит сравнение дискриминанта с нулем:

    • Если дискриминант равен нулю, в список result добавляется один корень.
    • Если дискриминант больше нуля, в список добавляются два корня.
    • Если дискриминант меньше нуля, ни одно из условий не срабатывает, и функция возвращает пустой список. Это означает, что уравнение не имеет корней.

    На вход функции show_result передается список с корнями уравнения. С помощью условного оператора проверяется содержимое этого списка:

    • Если в списке содержится один корень, он выводится с помощью функции print как элемент списка с нулевым индексом.
    • Если в списке два корня, используется цикл for, который вызывает функцию print дважды. В выводе указываются номер корня и его значение.
    • Если ни одно из условий не выполняется, это означает, что список пуст, и программа выводит сообщение о том, что уравнение не имеет корней.

    Пример работы программы:

    (env) alex@alex-pc:~/Projects/autotest$ /home/alex/env/bin/python3 /home/alex/Projects/autotest/test.py
    Введите первый коэффициент: -1
    Введите второй коэффициент: 2
    Введите третий коэффициент: 8
    Корень номер 1 равен -2.00
    Корень номер 2 равен 4.00

    Теперь займемся тестированием этой программы.

    Тестируем решатель при помощи Unittest

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

    import unittest

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

    class QuadraticEquationSolverTestCase(unittest.TestCase):

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

    def test_no_roots(self):
          res = quadratic_equation_solver(7, -25, 23)
          self.assertEqual(len(res), 0)

    Здесь мы вызываем функцию quadratic_equation_solver с уже определенным набором коэффициентов на входе, а результат ее работы записываем в переменную res. Дальше при помощи метода assertEqual сравниваем длину списка res с нулем (ожидаемое количество корней). Если в переменной res не будет никаких значений (пустой список, так как корней нет), то функция len(res) вернет нулевое значение и тест будет считаться пройденным, так как результат совпал с ожидаемым значением (0=0). 

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

    def test_one_root(self):
          res = quadratic_equation_solver(1, -12, 36)
          self.assertEqual(len(res), 1)
          self.assertEqual(res, [6])

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

    Последняя функция проверяет случай, когда уравнение имеет два корня:

    def test_two_roots(self):
          res = quadratic_equation_solver(1, -6, -7)
          self.assertEqual(len(res), 2)
          self.assertEqual(res, [7, -1])

    В этом случае тест будет считаться пройденным, если уравнение с заданными коэффициентами (1, -6 и -7) будет иметь два корня: 7 и -1. Весь исходник класса с этими тремя функциями и импортом должен выглядеть примерно так:

    import unittest
    
    class QuadraticEquationSolverTestCase(unittest.TestCase):
      def test_no_roots(self):
          res = quadratic_equation_solver(7, -25, 23)
          self.assertEqual(len(res), 0)
    
      def test_one_root(self):
          res = quadratic_equation_solver(1, -12, 36)
          self.assertEqual(len(res), 1)
          self.assertEqual(res, [6])
    
      def test_two_roots(self):
          res = quadratic_equation_solver(1, -6, -7)
          self.assertEqual(len(res), 2)
          self.assertEqual(res, [7, -1])

    Как теперь это все запустить? Для запуска тестов нужно открыть консоль из папки с исходником и скомандовать:

    python3 -m unittest test.py

    Получим следующий вывод:

    (env) alex@alex-pc:~/Projects/autotest$ python3 -m unittest test.py
    ...
    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s
    
    OK

    Это значит, что все тесты успешно пройдены. А теперь давайте заменим, например, значение ожидаемого результата во втором вызове метода assertEqual в функции test_one_root. Там ожидается, что корень будет равен числу 6. Заменим это значение на 7 и запустим тестирование. Нас будет ожидать такой результат:

    (env) alex@alex-pc:~/Projects/autotest$ python3 -m unittest test.py
    .F.
    ======================================================================
    FAIL: test_one_root (test.QuadraticEquationSolverTestCase.test_one_root)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/home/alex/Projects/autotest/test.py", line 47, in test_one_root
        self.assertEqual(res, [7])
    AssertionError: Lists differ: [6.0] != [7]
    
    First differing element 0:
    6.0
    7
    
    - [6.0]
    + [7]
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.001s
    
    FAILED (failures=1)

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

    Тестируем решатель при помощи Pytest

    Pytest — это еще одна популярная библиотека для проведения автоматических тестов программного обеспечения. В отличие от Unittest, она не входит в состав стандартной библиотеки Python, и для ее использования эту библиотеку сначала придется установить с помощью менеджера пакетов PIP. Открываем терминал и вводим следующую команду:

    pip install pytest

    После завершения установки можно приступать к созданию тестов. В том же файле с исходным кодом нашего решателя квадратных уравнений прописываем эти три функции:

    def test_no_roots():
      res = quadratic_equation_solver(7, -25, 23)
      assert len(res) == 0
    
    def test_one_root():
      res = quadratic_equation_solver(1, -12, 36)
      assert len(res) == 1
      assert res == [6]
    
    def test_two_roots():
      res = quadratic_equation_solver(1, -6, -7)
      assert len(res) == 2
      assert res == [7, -1]

    Как видим, при использовании Pytest не нужно создавать отдельный класс для тестов. Также нет необходимости импортировать дополнительный модуль. И, если говорить о коде внутри функций, то можно сказать, что логика здесь та же самая, что и у Unittest, но код выглядит компактнее. Давайте же запустим тестирование! Для этого в терминале вводим следующую команду:

    pytest test.py

    Результат выполнения будет таким:

    (env) alex@alex-pc:~/Projects/autotest$ pytest test.py
    ====================== test session starts =======================
    platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0
    rootdir: /home/alex/Projects/autotest
    plugins: anyio-4.6.2.post1
    collected 6 items                                               
    
    test.py ......                                             [100%]
    
    ======================= 6 passed in 0.01s ========================

    Все тесты прошли успешно. Теперь сделаем такую же замену, как в предыдущем разделе (меняем 6 на 7 в функции test_one_root) и снова запускаем тестирование. Получим такой вывод:

    (env) alex@alex-pc:~/Projects/autotest$ pytest test.py
    ====================== test session starts =======================
    platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0
    rootdir: /home/alex/Projects/autotest
    plugins: anyio-4.6.2.post1
    collected 6 items                                               
    
    test.py ....F.                                             [100%]
    
    ============================ FAILURES ============================
    _________________________ test_one_root __________________________
    
        def test_one_root():
          res = quadratic_equation_solver(1, -12, 36)
          assert len(res) == 1
    >      assert res == [7]
    E      assert [6.0] == [7]
    E       
    E        At index 0 diff: 6.0 != 7
    E        Use -v to get more diff
    
    test.py:64: AssertionError
    ==================== short test summary info =====================
    FAILED test.py::test_one_root - assert [6.0] == [7]
    ================== 1 failed, 5 passed in 0.08s ===================

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

    Задание для самостоятельного выполнения

    Даны две функции, которые могут быть частью какого-нибудь калькулятора:

    def addition(a, b):
      return a + b
    
    def subtraction(a, b):
      return a - b

    Напишите для них автотесты с использованием библиотек Unittest и Pytest.

    Решение

    Если писать с использованием библиотеки Unittest, то понадобится всего один вызов метода assertEqual на функцию, так как надо проверить лишь соответствие вычисленных суммы и разности ожидаемым результатам:

    import unittest
    
    class CalculatorTestCase(unittest.TestCase):
      def test_addition(self):
          res = addition(7, 5)
          self.assertEqual(res, 12)
     
      def test_subtraction(self):
          res = subtraction(10, 5)
          self.assertEqual(res, 5)

    Подобным образом пишем функции и с использованием Pytest:

    def test_addition():
        res = addition(7, 5)
        assert res == 12
    
    def test_subtraction():
        res = subtraction(10, 5)
        assert res == 5

    Запускаем автотесты на Unittest. Получаем:

    (env) alex@alex-pc:~/Projects/calc-test$ python3 -m unittest test.py
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK

    На Pytest:

    (env) alex@alex-pc:~/Projects/calc-test$ pytest test.py
    ===================== test session starts ======================
    platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0
    rootdir: /home/alex/Projects/calc-test
    plugins: anyio-4.6.2.post1
    collected 4 items                                             
    
    test.py ....                                             [100%]
    
    ====================== 4 passed in 0.01s =======================

    Подведем итоги

    • Автотест — это сценарий, который имитирует работу пользователя с тестируемой программой.
    • Автоматическое тестирование работает быстрее ручного и необходимо для выявления ошибок, различных недоработок в программном обеспечении и для оценки его качества в автоматическом режиме.
    • Для создания автотестов на языке Python отлично подходят библиотеки Unittest и Pytest.
    • Код на Pytest получается более лаконичным, чем на Unittest. Также вывод результатов у Pytest более информативен.

    Инструкция

    Поделиться

    Скопировано
    0 комментариев
    Комментарии