Testing basics
Ключові моменти: @pytest.mark.parametrize(...) pytest.param(...) (з аргументом "id"!) with pytest.raises(error_which_must_be_risen):
Дві основні бібліотеки тестування для Python: unittest (тестування на основі класів, без вбудованого "assert", потрібно використовувати спеціальну функцію з бібліотеки unittest. Потрібно створювати клас з наслідуванням) та pytest (тестування на основі функцій з вбудованою функціональністю "assert").
Створи клас для кожної функції, яку будеш тестувати в класу створи функцію, задекоровану:
import pytest
class TestingFirstFunction:
@pytest.mark.parametrize(
"тут,через,кому,НАЗВИ,змінних",
#список значень для кожної змінної в такому вигляді:
[
#це буде конкретні значення для тесту1
pytest.param(
#тут через кому значення для кожної змінної оголошеної в parametrize, а також в кінці додати id="назва тесту"
),
#це буде конкретні значення для тесту2
pytest.param(
#тут через кому значення для кожної змінної оголошеної в parametrize, а також в кінці додати id="назва тесту"
),
#це буде конкретні значення для тесту3
pytest.param(
#тут через кому значення для кожної змінної оголошеної в parametrize, а також в кінці додати id="назва тесту"
),
#це буде конкретні значення для тесту4
pytest.param(
#тут через кому значення для кожної змінної оголошеної в parametrize, а також в кінці додати id="назва тесту"
),
]
)
def zahalna_funkcia(self,
тут,
через,
кому,
назву,
змінних):
#опис тесту з використанням змінних



Приклад Unittest:
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# перевірка, що s.split видає помилку, коли роздільник не є рядком
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()
Теорія
Фреймворк unittest
unittest — це вбудований фреймворк тестування Python. Він має деякі важливі вимоги для написання та запуску тестів: unittest базується на класах, вам потрібно розміщувати тести в класах (які наслідують клас unittest.TestCase) як методи; вам потрібно використовувати спеціальний метод assertion класу unittest.TestCase замість вбудованого оператора assert.
Щоб перетворити попередній приклад на тестовий випадок unittest, потрібно: імпортувати unittest зі стандартної бібліотеки. Створити клас TestSum, який наслідує клас TestCase. Перетворити тестові функції на методи, додавши self як перший аргумент. Змінити assertions на використання методу self.assertEqual() класу TestCase. Змінити точку входу командного рядка на виклик unittest.main().
Протестуємо ту саму функцію з main.py:
def sum_instances(a: int | str, b: int | str) -> int | str:
if isinstance(a, int) and isinstance(b, int):
return a - b
if isinstance(a, str) and isinstance(b, str):
return a + b
return a + b
Дотримуючись наведених вище кроків, створимо файл test_sum_instances.py:
import unittest
from main import sum_instances
class TestSum(unittest.TestCase):
def test_can_sum_2_strings(self) -> None:
self.assertEqual(
sum_instances("Can", "Add"),
"CanAdd",
"Sum of 'Can' and 'Add' should be equal to 'CanAdd'"
)
def test_can_sum_2_numbers(self) -> None:
self.assertEqual(
sum_instances(2, 3),
5,
"Sum of 2 and 3 should be equal to 5"
)
if __name__ == "__main__":
unittest.main()
Фреймворк pytest
pytest — це фреймворк тестування на основі Python, який використовується для написання та виконання тестового коду.
Перш за все, потрібно встановити pytest, виконавши команду pip install pytest.
Запуск pytest без вказання імені файлу запустить усі файли формату test_*.py або *_test.py (цей крок називається збір тестів) у поточній директорії та піддиректоріях. Pytest автоматично визначає ці файли як тестові. Ми можемо змусити pytest запустити інші файли, вказавши їх.
pytest вимагає, щоб назви тестових функцій починалися з test. Інші назви функцій не вважаються тестовими функціями. Розглянемо приклад:
from main import sum_instances
def test_can_sum_2_strings() -> None:
assert (
sum_instances("Can", "Add") == "CanAdd"
), "Sum of 'Can' and 'Add' should be equal to 'CanAdd'"
def test_can_sum_2_numbers() -> None:
assert (
sum_instances(2, 3) == 5
), "Sum of 2 and 3 should be equal to 5"
Метод pytest.raises
Коли ви хочете перевірити, чи ваш код викликає правильне виключення, ви можете використовувати pytest.raises як контекстний менеджер, який перехопить виключення заданого типу:
def sum_instances(a: int | str, b: int | str) -> int | str:
if isinstance(a, int) and isinstance(b, int):
return a - b
if isinstance(a, str) and isinstance(b, str):
return a + b
return a + b
Перевіримо, чи виникає TypeError, коли надано невалідні дані:
import pytest
from main import sum_instances
def test_cannot_add_int_and_str() -> None:
with pytest.raises(TypeError):
sum_instances(2, "3")
def test_cannot_add_2_lists() -> None:
with pytest.raises(TypeError):
# але TypeError не виникне, бо насправді ми можемо використовувати '+' зі списками
sum_instances([2], ["3"])
Декоратор @pytest.mark.parametrize
Параметризація в контексті тестування — це процес запуску одного й того ж тесту з різними значеннями з підготовленого набору. Кожна комбінація тесту і даних вважається новим тестовим випадком. У pytest для параметризації є декоратор @pytest.mark.parametrize.
Протестуємо ту саму функцію sum_instances з main.py, використовуючи параметризацію:
import pytest
from main import sum_instances
@pytest.mark.parametrize(
"a,b,result",
[
("Can", "Add", "CanAdd"),
(2, 3, 5)
]
)
def test_can_sum(a: int | str, b: int | str, result: int | str) -> None:
assert (
sum_instances(a, b) == result
), f"Sum of {a} and {b} should be equal to {result}"
Тепер ми бачимо стандартну назву тестів — test_can_sum[2-3-5], але це не завжди зрозуміло та читабельно. Ми можемо задати власні назви тестів двома способами.
Перший — використовуючи pytest.param перед кожною групою параметрів та додаючи id як останній параметр:
@pytest.mark.parametrize(
"a,b,result",
[
pytest.param("Can", "Add", "CanAdd", id="2 strings"),
pytest.param(2, 3, 5, id="2 numbers")
]
)
def test_can_sum(a: int | str, b: int | str, result: int | str) -> None:
assert (
sum_instances(a, b) == result
), f"Sum of {a} and {b} should be equal to {result}"
Другий — надаючи додатковий параметр ids для @pytest.mark.parametrize:
import pytest
from main import sum_instances
@pytest.mark.parametrize(
"a,b,result",
[
("Can", "Add", "CanAdd"),
(2, 3, 5)
],
ids=[
"2 strings",
"2 numbers"
]
)
def test_can_sum(a: int | str, b: int | str, result) -> None:
assert (
sum_instances(a, b) == result
), f"Sum of {a} and {b} should be equal to {result}"