Python · Тестування з pytest · Початковий

Твердження в деталях

5 завдань

Опануйте перезапис тверджень pytest для чітких та інформативних повідомлень про помилки.

Як pytest перезаписує твердження

#
Оператор `assert` у чистому Python є мінімальним: якщо вираз хибний, він підіймає `AssertionError` без жодної корисної інформації: ```python # чистий assert Python assert result == expected # → AssertionError (лише виняток, більше нічого) ``` pytest вирішує це за допомогою **перезапису тверджень**. Коли pytest імпортує ваш тестовий файл, він трансформує байт-код так, що кожен `assert` захоплює обидві сторони порівняння до обчислення. При провалі ви бачите, що саме відрізнялось: ``` # порівняння списків: AssertionError: assert [1, 2, 3] == [1, 2, 4] At index 2 diff: 3 != 4 # порівняння словників: AssertionError: assert {'name': 'Alice', 'age': 31} == {'name': 'Alice', 'age': 30} Differing items: {'age': 31} != {'age': 30} # порівняння рядків: AssertionError: assert 'hello earth' == 'hello world' At position 6 diff: 'e' != 'w' ``` Це робить провали само-діагностувальними. Ви бачите *що* відрізнялось, а не просто *те, що* щось відрізнялось. **`==` vs `is`** `==` перевіряє *рівність* (однакове значення). `is` перевіряє *ідентичність* (той самий об'єкт у пам'яті). Використовуйте `is` для булевих значень і `None`: ```python assert result is True # result є булевим True assert result == True # теж працює, але 1 == True теж True (менш суворо) assert result is not None # чітко і ідіоматично ``` Для чисел, рядків і колекцій завжди використовуйте `==`. Перевірка `is` для рядків і цілих чисел може давати несподівані результати через інтернування об'єктів у Python. **Заперечення:** ```python assert x != y # не рівне assert x not in lst # не в колекції assert not condition # загальне заперечення ``` **Власне повідомлення при провалі:** Додайте повідомлення після твердження -- pytest показує його разом з самим твердженням: ```python assert score >= 50, f"Expected score >= 50, got {score}" # → AssertionError: Expected score >= 50, got 42 # assert 42 >= 50 ``` **Порівняння чисел з плаваючою точкою:** `0.1 + 0.2` у Python не є рівно `0.3` через двійкове представлення дійсних чисел. Використовуйте `pytest.approx`: ```python import pytest assert 0.1 + 0.2 == pytest.approx(0.3) # за замовчуванням: відносна похибка 1e-6 assert result == pytest.approx(1.5, rel=1e-3) # у межах 0.1% assert result == pytest.approx(0.0, abs=1e-9) # абсолютна похибка (краще поблизу нуля) ``` Використовуйте `rel` (відносна), коли величина велика; використовуйте `abs` (абсолютна), коли очікуване значення дорівнює нулю або близьке до нього (ділення на нуль у відносній перевірці було б проблемою).

Патерни тверджень на практиці

#
**Рівність, нерівність і впорядкування:** ```python def test_equality(): assert 2 + 2 == 4 assert 'hello' != 'world' assert 10 > 5 assert 3 <= 3 def test_negation(): assert not False assert not [] # порожній список є хибним assert not '' # порожній рядок є хибним assert not 0 # нуль є хибним ``` **Перевірка належності і типу:** ```python def test_membership(): items = ['apple', 'banana', 'cherry'] assert 'banana' in items assert 'grape' not in items def test_types(): items = ['apple', 'banana', 'cherry'] assert isinstance(items, list) assert isinstance(42, int) assert isinstance(3.14, float) # isinstance також приймає кортежі типів: assert isinstance(42, (int, float)) # збігається з будь-яким ``` **Перевірки словників і рядків:** ```python def test_dict_assertions(): user = {'name': 'Alice', 'age': 30, 'active': True} assert 'name' in user # ключ існує assert user['name'] == 'Alice' # значення правильне assert 'email' not in user # ключ відсутній assert len(user) == 3 # точний розмір def test_string_assertions(): msg = 'Hello, World!' assert msg.startswith('Hello') assert msg.endswith('!') assert 'World' in msg assert len(msg) == 13 assert msg.upper() == 'HELLO, WORLD!' ``` **Числа з плаваючою точкою через pytest.approx:** ```python import math, pytest def test_sqrt(): assert math.sqrt(2) == pytest.approx(1.41421, rel=1e-4) def test_float_arithmetic(): # 0.1 + 0.2 = 0.30000000000000004 у Python assert 0.1 + 0.2 == pytest.approx(0.3) def test_near_zero(): result = 1e-10 - 1e-10 assert result == pytest.approx(0.0, abs=1e-15) ``` **Власні повідомлення для контексту:** ```python # process_batch -- це заглушка для ілюстрації патерну, не готовий до запуску сніпет def test_with_context(): results = process_batch([1, 2, 3, 4, 5]) assert len(results) == 5, f"Expected 5 results, got {len(results)}: {results}" ```

Шпаргалка: твердження

#
```python # Рівність / нерівність assert x == y assert x != y # Впорядкування assert x > y assert x >= y assert x < y assert x <= y # Належність assert item in collection assert item not in collection # Тип assert isinstance(obj, SomeClass) # також збігається з підкласами assert type(obj) is int # лише точний тип # Істинність assert value # істинне (ненульове, непорожнє, не None) assert not value # хибне # Числа з плаваючою точкою assert x == pytest.approx(y) # відносна похибка 1e-6 за замовчуванням assert x == pytest.approx(y, rel=1e-3) # похибка 0.1% assert x == pytest.approx(y, abs=0.01) # абсолютна похибка +-0.01 # Рядки assert s.startswith('prefix') assert s.endswith('suffix') assert 'substring' in s # Власне повідомлення при провалі assert condition, f"Описове повідомлення: отримано {value}" ```
01

Тестування конвертера температур

#

Напишіть тести для `to_celsius(f)`, яка конвертує градуси Фаренгейта в Цельсій за формулою `(f - 32) * 5 / 9`. Перевірте результат принаймні для трьох значень: 32F (0C), 212F (100C) і 98.6F (37C). Використовуйте `pytest.approx` для порівняння float.

import pytest


def to_celsius(f):
    return (f - 32) * 5 / 9


def test_to_celsius():
    pass
Рішення
import pytest


def to_celsius(f):
    return (f - 32) * 5 / 9


def test_freezing_point():
    assert to_celsius(32) == pytest.approx(0.0)

def test_boiling_point():
    assert to_celsius(212) == pytest.approx(100.0)

def test_body_temperature():
    assert to_celsius(98.6) == pytest.approx(37.0, rel=1e-3)
02

pytest.approx для площі кола

#

Реалізуйте `circle_area(r)`, яка повертає `π * r²` з використанням `math.pi`. Напишіть тести, що перевіряють: площа при `r=3` приблизно дорівнює `28.274` (з допуском 0.1%), при `r=1` -- приблизно `3.14159`. Також перевірте `r=0`.

import math
import pytest


def circle_area(r):
    pass  # реалізуйте тут


def test_circle_area():
    pass
Рішення
import math
import pytest


def circle_area(r):
    return math.pi * r ** 2


def test_circle_area_r3():
    assert circle_area(3) == pytest.approx(28.274, rel=1e-3)

def test_circle_area_r1():
    assert circle_area(1) == pytest.approx(3.14159, rel=1e-4)

def test_circle_area_r0():
    assert circle_area(0) == pytest.approx(0.0)
03

Твердження для вмісту списку

#

Напишіть `top_scores(scores, n)`, яка повертає `n` найвищих балів зі списку, відсортованих у спадному порядку. Напишіть тести, що перевіряють: тип повернутого значення є списком, його довжина дорівнює `n`, конкретні значення присутні або відсутні.

def top_scores(scores, n):
    pass  # реалізуйте тут


def test_top_scores():
    pass
Рішення
def top_scores(scores, n):
    return sorted(scores, reverse=True)[:n]


def test_top_scores_returns_list():
    result = top_scores([5, 1, 8, 3, 9], 3)
    assert isinstance(result, list)

def test_top_scores_length():
    result = top_scores([5, 1, 8, 3, 9], 3)
    assert len(result) == 3

def test_top_scores_correct_values():
    result = top_scores([5, 1, 8, 3, 9], 3)
    assert 9 in result
    assert 8 in result
    assert 5 in result
    assert 1 not in result
04

Твердження для ключів і значень словника

#

Напишіть `parse_user(s)`, яка парсить рядок виду `'name:Alice,age:30'` у словник. Напишіть тести, що перевіряють наявність конкретних ключів, правильність значень та відсутність неочікуваних ключів.

def parse_user(s):
    pass  # "name:Alice,age:30" -> {'name': 'Alice', 'age': '30'}


def test_parse_user():
    pass
Рішення
def parse_user(s):
    result = {}
    for pair in s.split(','):
        key, value = pair.split(':')
        result[key.strip()] = value.strip()
    return result


def test_parse_user_name():
    user = parse_user('name:Alice,age:30')
    assert 'name' in user
    assert user['name'] == 'Alice'

def test_parse_user_age():
    user = parse_user('name:Alice,age:30')
    assert 'age' in user
    assert user['age'] == '30'

def test_parse_user_no_extra_keys():
    user = parse_user('name:Alice,age:30')
    assert 'email' not in user
    assert len(user) == 2
05

Твердження для властивостей рядка

#

Напишіть `format_greeting(name, title)`, яка повертає `'Dear {title}. {name},'`. Напишіть тести, що перевіряють: результат починається з 'Dear', закінчується на ',', містить ім'я і не має подвійних пробілів.

def format_greeting(name, title):
    pass  # реалізуйте тут


def test_format_greeting():
    pass
Рішення
def format_greeting(name, title):
    return f'Dear {title}. {name},'


def test_greeting_starts_with_dear():
    result = format_greeting('Smith', 'Dr')
    assert result.startswith('Dear')

def test_greeting_ends_with_comma():
    result = format_greeting('Smith', 'Dr')
    assert result.endswith(',')

def test_greeting_contains_name():
    result = format_greeting('Smith', 'Dr')
    assert 'Smith' in result

def test_greeting_no_double_spaces():
    result = format_greeting('Smith', 'Dr')
    assert '  ' not in result