Модуль тестування за допомогою модуля unittest Python

Жоден кваліфікований розробник не випускає програмний код без попереднього, ретельного тестування. Одним із ключових етапів цього процесу є модульне тестування, яке передбачає перевірку окремих компонентів великої програми.

Ця стаття присвячена розгляду можливостей модульного тестування коду з використанням вбудованого модуля Python – `unittest`. Для початку, давайте розберемося з основними видами тестування програмного забезпечення.

Існує два основних підходи до тестування: ручне та автоматизоване. Ручне тестування – це процес, коли тестувальник особисто виконує тестові сценарії після завершення розробки. Автоматизоване тестування, навпаки, передбачає використання спеціальних програм, які виконують тести автоматично, надаючи результати.

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

Розглянемо типовий процес тестування програмного забезпечення:

  • Написання або оновлення коду.
  • Створення або модифікація тестів для різних сценаріїв, пов’язаних з кодом.
  • Запуск тестів (ручний або автоматизований).
  • Аналіз результатів тестування. У разі виявлення помилок, їх виправлення та повторення процесу.

У цій статті ми детально розглянемо модульне тестування – один з найважливіших і базових видів тестування. Перейдемо безпосередньо до практичного керівництва.

Що таке модульне тестування?

Модульне тестування – це метод тестування окремого, невеликого блоку коду. Найчастіше, таким блоком є функція. Ключовим є поняття “незалежність”, що означає, що цей блок не залежить від інших частин коду в проекті.

Наприклад, якщо нам потрібно перевірити, чи відповідає рядок тексту значенню “techukraine.net”, ми можемо написати функцію, яка приймає один аргумент і повертає `True` або `False`, залежно від результату порівняння.

def is_equal_to_techukraine(рядок):
    return рядок == "techukraine.net"

Ця функція не залежить від іншого коду. Ми можемо перевірити її незалежно, подаючи різні вхідні дані. Такий незалежний фрагмент коду можна вільно використовувати в проекті.

Важливість модульного тестування

Код незалежних блоків є основою для створення великих програм. Саме тому так важливо, щоб він був якісно написаний і ретельно протестований. Модульні тести – це інструмент для перевірки саме таких незалежних блоків коду. Що ж відбудеться, якщо ми не будемо використовувати модульне тестування?

Уявіть, що ми не тестуємо невеликі блоки коду, що використовуються в проекті. В результаті, інші види тестування, такі як інтеграційне чи наскрізне, можуть зазнати невдачі через проблеми з базовими компонентами, що призведе до некоректної роботи програми. Звідси випливає необхідність ретельного тестування базових будівельних блоків коду.

Отже, ми усвідомили важливість модульного тестування та необхідність написання модульних тестів для кожного незалежного блоку коду. Завдяки цьому, інші тести не будуть “спотикатися” об проблеми в базових компонентах.

У наступних розділах ми розглянемо, що таке модуль `unittest` Python і як його використовувати для написання модульних тестів.

Примітка: Ми припускаємо, що ви знайомі з поняттями класів та модулів у Python. Якщо у вас немає базових знань про ці поняття, то розуміння подальшого матеріалу може бути ускладнене.

Що таке `unittest` Python?

Модуль `unittest` – це вбудований фреймворк для тестування коду Python. Він містить засоби для зручного запуску тестів. Це означає, що ми можемо використовувати `unittest` для тестування, не звертаючись до сторонніх бібліотек. Хоча вибір інструментів залежить від конкретних вимог, `unittest` є чудовим варіантом для початку тестування в Python.

Для тестування коду Python за допомогою `unittest` необхідно виконати наступні кроки:

#1. Написати код, що підлягає тестуванню.

#2. Імпортувати модуль `unittest`.

#3. Створити файл, назва якого починається з ключового слова `test`. Наприклад, `test_my_code.py`. Ключове слово `test` використовується для ідентифікації тестових файлів.

#4. Створити клас, що успадковує клас `unittest.TestCase`.

#5. Написати методи (тести) всередині класу. Кожен метод містить різні тестові сценарії, що відповідають вимогам. Назви методів повинні починатися з ключового слова `test`.

#6. Запустити тести. Існує декілька способів запуску тестів:

  • Використання команди `python -m unittest test_filename.py`.
  • Запуск тестових файлів як звичайних файлів Python за допомогою команди `python test_filename.py`. Для цього необхідно викликати основний метод `unittest` в тестовому файлі.
  • Використання методу `discover`, який дозволяє автоматично запускати всі тести командою `python -m unittest discover` без вказівки конкретного файлу. Для цього тестові файли повинні відповідати правилам іменування (починатися зі слова `test`).

Під час тестування ми порівнюємо результат роботи коду з очікуваним результатом. Модуль `unittest` надає різноманітні методи для порівняння результатів. Перелік цих методів доступний тут. Ці методи є досить простими у використанні.

Це був огляд теорії. Тепер перейдемо до практичної частини.

Примітка: Якщо у вас є запитання щодо `unittest`, зверніться до офіційної документації. Без зволікань, почнемо використовувати `unittest` на практиці.

Модульні тести в Python з `unittest`

Спершу напишемо кілька функцій, а потім зосередимося на написанні тестів. Відкрийте потрібну папку у вашому редакторі коду та створіть файл `utils.py`. Вставте в нього наступний код:

import math

def is_prime(n):
    if n < 0:
        return 'Від’ємні числа не дозволені'

    if n <= 1:
        return False

    if n == 2:
        return True

    if n % 2 == 0:
        return False

    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True


def cubic(a):
    return a * a * a


def say_hello(name):
    return "Привіт, " + name

Файл `utils.py` містить три різні функції. Тепер ми повинні перевірити кожну з них, використовуючи різні тестові сценарії. Розпочнемо з першої функції – `is_prime`.

#1. Створіть файл `test_utils.py` у тій же папці, що і `utils.py`.

#2. Імпортуйте `utils` та модуль `unittest`.

#3. Створіть клас `TestUtils`, який успадковує `unittest.TestCase`. Назва класу може бути будь-якою, але слід обирати значущу назву.

#4. У класі створіть метод `test_is_prime`, який приймає `self` як аргумент.

#5. Напишіть різноманітні тестові випадки, використовуючи різні аргументи для функції `is_prime`, та порівняйте отримані результати з очікуваними.

#6. Приклад тесту: `self.assertFalse(utils.is_prime(1))`.

#7. У цьому випадку ми очікуємо, що результат `is_prime(1)` буде `False`.

#8. Аналогічно, розглянемо різні тестові випадки, залежно від функціональності, яку ми тестуємо.

Поглянемо на тестовий код:

import unittest

import utils

class TestUtils(unittest.TestCase):
    def test_is_prime(self):
        self.assertFalse(utils.is_prime(4))
        self.assertTrue(utils.is_prime(2))
        self.assertTrue(utils.is_prime(3))
        self.assertFalse(utils.is_prime(8))
        self.assertFalse(utils.is_prime(10))
        self.assertTrue(utils.is_prime(7))
        self.assertEqual(utils.is_prime(-3),
                         "Від’ємні числа не дозволені")

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

Ми викликаємо основний метод модуля `unittest` для запуску тестів командою `python filename.py`. Запустіть тести.

Ви побачите результат, подібний до цього:

$ python test_utils.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Тепер спробуйте написати тестові випадки для інших функцій. Продумайте різні сценарії для кожної функції та напишіть для них тести. Погляньте на наступний код, доданий до попереднього класу:

...

class TestUtils(unittest.TestCase):
    def test_is_prime(self):
        ...

    def test_cubic(self):
        self.assertEqual(utils.cubic(2), 8)
        self.assertEqual(utils.cubic(-2), -8)
        self.assertNotEqual(utils.cubic(2), 4)
        self.assertNotEqual(utils.cubic(-3), 27)

    def test_say_hello(self):
        self.assertEqual(utils.say_hello("techukraine.net"), "Привіт, techukraine.net")
        self.assertEqual(utils.say_hello("Chandan"), "Привіт, Chandan")
        self.assertNotEqual(utils.say_hello("Chandan"), "Hi, Chandan")
        self.assertNotEqual(utils.say_hello("Hafeez"), "Hi, Hafeez")

...

Ми використали лише деякі методи порівняння з модуля `unittest`. Повний перелік доступний тут.

Ми навчилися писати модульні тести за допомогою `unittest`. Тепер давайте розглянемо різні способи запуску тестів.

Як запускати тести з `unittest`

Вище ми вже розглянули один із способів запуску тестів. Давайте подивимося на інші два методи.

#1. Використання імені файлу та модуля `unittest`.

У цьому випадку ми використовуємо модуль `unittest` та ім’я файлу для запуску тестів. Команда для запуску: `python -m unittest filename.py`. У нашому випадку це буде `python -m unittest test_utils.py`.

#2. Використання методу `discover`.

Метод `discover` модуля `unittest` автоматично знаходить і запускає всі тестові файли. Для цього тестові файли повинні мати ім’я, що починається з ключового слова `test`.

Команда для запуску тестів методом `discover` є `python -m unittest discover`. Ця команда знаходить усі файли, що починаються з `test`, і запускає їх.

Висновок 👩‍💻

Модульні тести є основою якісного програмного забезпечення. Існує багато інших видів тестування. Намагайтеся вивчати їх поступово. Сподіваємося, що цей посібник допоможе вам писати базові тести в Python з використанням модуля `unittest`. Також існують сторонні бібліотеки, такі як `pytest`, `Robot Framework`, `nose`, `nose2`, `slash`, та інші. Ви можете вивчати їх відповідно до вимог вашого проекту.

Успіхів у тестуванні 😎

Вас також можуть зацікавити питання та відповіді щодо співбесід з Python.