img

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

21 ноября
20:00
Бесплатный вебинар
Введение в Docker
Ведущий — Филипп Игнатенко.
Руководитель центра разработки
Записаться
img
img

С помощью этого руководства вы научитесь писать модульные тесты для функций Python. Но для чего вообще нужно уделять такое внимание модульным тестам?

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

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

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

Давайте начнем!

Тестирование в Python – первый этап

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

import math

def is_prime(num):
    '''Check if num is prime or not.'''
    for i in range(2,int(math.sqrt(num))+1):
        if num%i==0:
            return False
    return True

Давайте запустим Python REPL, вызовем функцию is_prime(), передав ей аргументы, и проверим результаты. 

>>> from prime_number import is_prime
>>> is_prime(3)
True
>>> is_prime(5)
True
>>> is_prime(12)
False
>>> is_prime(8)
False
>>> assert is_prime(7) == True

Для того, чтобы убедиться, что is_prime(), возвращает ожидаемое логическое значение, вы также можете использовать конструкцию assert (см. выше). Если значение, которое вернула функция, отличается от ожидаемого логического значения, то возникнет ошибка AssertionError.

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

Как работать с модулем Python unittest

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

# <module-name>.py

import unittest
from <module> import <function_to_test>
# all entries within <> are placeholders

class TestClass(unittest.TestCase):
def test_<name_1>(self):
# check function_to_test

def test_<name_2>(self):
# check function_to_test
:
:
:

def test_<name_n>(self):
# check function_to_test

Фрагмент кода <module-name>.py, который приведен выше, выполняет следующее:

  • Импортирует встроенный в Python модуль unittest.
  • Импортирует функцию Python <function_to_test>, которую необходимо протестировать, из модуля <module>, в котором она определена. 
  • Создает тестовый класс (TestClass), который наследуется от класса unittest.TestCase.
  • Все тесты, которые должны быть запущены, должны быть определены в качестве методов внутри тестового класса.
  • ? Примечание: для того, чтобы unittest определял эти методы как тесты и запускал их, названия этих методов должны начинаться с test_.
  • Класс TestCase из модуля unittest предоставляет полезные методы с утверждениями для проверки того факта, что тестируемая функция возвращает ожидаемые значения. 

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

Метод

Описание

assertEqual(expected_value,actual_value)

Утверждает, что expected_value == actual_value

assertTrue(result)

Утверждает, что bool(result) это True

assertFalse(result)

Утверждает, что bool(result) это False

assertRaises(exception, function, *args, **kwargs)

Утверждает, что function(*args, **kwargs) вызывает exception

Для того, чтобы запустить эти тесты, мы должны запустить unittest в качестве основного модуля с помощью следующей команды:

$ python -m unittest <module-name>.py

Для запуска unittest в качестве основного модуля мы можем добавить условие if __name__=='__main__'.

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

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

$ python <module-name>.py

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

unittesting-101

В этом разделе мы напишем модульные тесты для функции is_prime() с помощью уже изученного синтаксиса. 

Для того, чтобы протестировать функцию is_prime(), которая возвращает логическое значение, мы можем воспользоваться методами assertTrue() и assertFalse(). Мы определяем четыре метода тестирования в классе TestPrime, который наследуется от unittest.TestCase

import unittest
# import the is_prime function
from prime_number import is_prime
class TestPrime(unittest.TestCase):
    def test_two(self):
        self.assertTrue(is_prime(2))
    def test_five(self):
     self.assertTrue(is_prime(5))
    def test_nine(self):
     self.assertFalse(is_prime(9))
    def test_eleven(self):
     self.assertTrue(is_prime(11))
if __name__=='__main__':
unittest.main()
$ python test_prime.py

Ниже вывод в виде точки ( . ) говорит нам об успешно выполненном тестировании.

Output
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK

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

import unittest
from prime_number import is_prime
class TestPrime(unittest.TestCase):
def test_prime_not_prime(self):
        self.assertTrue(is_prime(2))
        self.assertTrue(is_prime(5))
        self.assertFalse(is_prime(9))
        self.assertTrue(is_prime(11))

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

$ python test_prime.py
Output
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

Как писать модульные тесты для проверки на исключения

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

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

При тестировании функции is_prime()мы не учли следующее:

  • Если аргумент будет числом с плавающей точкой, то функция is_prime() также будет работать и возвращать значение True или False, что является ошибочным. 
  • Если аргумент будет совершенно другого типа (например, строкой «five» вместо числа 5), то функция выдаст ошибку TypeError
  • Если аргумент будет отрицательным числом, то функция math.sqrt() выдаст ошибку ValueError, поскольку квадраты всех действительных чисел (положительных, отрицательных или нуля) всегда неотрицательны.

Давайте проверим то, что мы рассмотрели выше, запустив несколько циклов Python REPL. 

>>> from prime_number import is_prime

>>> is_prime('five')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/bala/unit-test-1/prime_number.py", line 5, in is_prime
for i in range(2,int(math.sqrt(num))+1):
TypeError: must be real number, not str

>>> is_prime(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/bala/unit-test-1/prime_number.py", line 5, in is_prime
for i in range(2,int(math.sqrt(num))+1):
ValueError: math domain error

>>> is_prime(2.5)
True

Как вызвать исключения для недопустимых входных данных

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

  • Проверяем, является ли num целым числом. Если является, то переходим к следующей проверке. В противном случае выбрасываем исключение TypeError.
  • Проверяем, является ли num отрицательным целым числом. Если является, то выбрасываем исключение ValueError.

Переопределяем функцию, чтобы она также проверяла значение и выбрасывала по мере необходимости исключения. И получаем:

import math
def is_prime(num):
    '''Check if num is prime or not.'''
    # raise TypeError for invalid input type
    if type(num) != int:
        raise TypeError('num is of invalid type')
    # raise ValueError for invalid input value
    if num < 0:
        raise ValueError('Check the value of num; is num a non-negative integer?')
    # for valid input, proceed to check if num is prime
    for i in range(2,int(math.sqrt(num))+1):
        if num%i==0:
        return False
    return True

Теперь, когда мы изменили функцию так, чтобы мы могли выбрасывать исключения ValueError и TypeError для недопустимых входных данных, следующий шаг - проверка того, выпадает ли исключение. 

Как пользовать методом assertRaises() для проверки наличия исключений

Давайте добавим в определение TestPrime методы для проверки того, выпадают ли исключения. 

Мы определим методы test_typeerror_1() и test_typeerror_2() для проверки исключения TypeError, и метод test_valueerror() для проверки исключения ValueError.

? Для вызова метода assertRaises() мы можем использовать следующий общий синтаксис:

def test_exception(self):
    self.assertRaises(exception-name,function-name,args)

Мы также можем применить и другой синтаксис, используя менеджер контекста (в этом примере мы будем использовать именно его):

def test_exception(self):
    with self.assertRaises(exception-name):
        function-name(args)

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

import unittest
from prime_number import is_prime
class TestPrime(unittest.TestCase):
    def test_prime_not_prime(self):
        self.assertTrue(is_prime(2))
        self.assertTrue(is_prime(5))
        self.assertFalse(is_prime(9))
        self.assertTrue(is_prime(11))
    def test_typeerror_1(self):
        with self.assertRaises(TypeError):
         is_prime(6.5)
    def test_typeerror_2(self):
        with self.assertRaises(TypeError):
         is_prime('five')
    def test_valueerror(self):
        with self.assertRaises(ValueError):
         is_prime(-4)
            
if __name__=='__main__':
unittest.main()

Давайте запустим модуль test_prime и посмотрим на результат:

$ python test_prime.py
Output
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s
OK

Во всех примерах, которые мы успели написать до этого момента, все тесты выполнялись успешно. Давайте изменим один из методов, допустим, test_typeerror_2():

def test_typeerror_2(self):
    with self.assertRaises(TypeError):
     is_prime(5)

Мы вызываем функцию is_prime() и передаем ей в качестве аргумента число 5. В данном случае 5 является допустимым вводом, для которого функция возвращает True. Поэтому функция не вызывает исключение TypeError. А когда мы снова запустим тесты, то увидим, что один тест завершился неудачно. 

$ python test_prime.py
Output

..F.
======================================================================
FAIL: test_typeerror_2 (__main__.TestPrime)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_prime.py", line 17, in test_typeerror_2
is_prime(5)
AssertionError: TypeError not raised
----------------------------------------------------------------------
Ran 4 tests in 0.003s
FAILED (failures=1)

Заключение

Благодарю, что дочитали до конца! ? Надеюсь, что это руководство помогло вам понять азы модульного тестирования в Python.

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

Ссылка
скопирована
Получите бесплатные уроки на наших курсах
Все курсы
Программирование
Скидка 25%
Python-программист с нуля
Стань разработчиком на одном из самых популярных языков программирования.
Получи бесплатный
вводный урок!
Пожалуйста, укажите корректный e-mail
отправили вводный урок на твой e-mail!
Получи все материалы в telegram и ускорь обучение!
img
Еще по теме:
img
Гипервизор - это программное обеспечение для виртуализации, используемое для создания и запуска виртуальных машин (ВМ). Гипервиз
img
Виртуализация серверов позволяет запускать несколько виртуальных машин на одном физическом сервере. Запуск виртуальных машин (ВМ
img
Сегодня мы рассмотрим, как настроить и использовать PHP в проекте. Но прежде чем начать, нужно понять, что такое PHP. Что такое
img
Как разработчик, вы знаете, что HTML расшифровывается как HyperText Markup Language (язык разметки гипертекста). HTML — это язык
img
Бесконечные споры вокруг искусственного интеллекта приводят к путанице. Существует много терминов, которые кажутся похожими, но
img
SVG расшифровывается как масштабируемая векторная графика. Это веб-дружелюбный векторный формат файлов, используемый для отображ
21 ноября
20:00
Бесплатный вебинар
Введение в Docker