Python · Тестування з pytest · Початковий
Організація тестів
Структуруйте тестові файли і директорії для зручного пошуку і підтримки.
Короткий вступ до теми та пояснення перед вправами (вправи нижче):
Виявлення на практиці: функції, класи та структура
#Довідник: організація тестів
#Вправи:
Реорганізація плоских тестів у класи
#Наведений нижче файл містить 6 плоских тест-функцій для двох тем (реверс рядка і фільтрація списку). Реорганізуйте їх у два класи `Test*` -- `TestReverse` і `TestFilter` -- не змінюючи жодного твердження.
def reverse(s):
return s[::-1]
def filter_positive(nums):
return [n for n in nums if n > 0]
def test_reverse_hello():
assert reverse('hello') == 'olleh'
def test_reverse_empty():
assert reverse('') == ''
def test_reverse_single():
assert reverse('a') == 'a'
def test_filter_mixed():
assert filter_positive([1, -2, 3, -4]) == [1, 3]
def test_filter_all_negative():
assert filter_positive([-1, -2]) == []
def test_filter_empty():
assert filter_positive([]) == []
Рішення
def reverse(s):
return s[::-1]
def filter_positive(nums):
return [n for n in nums if n > 0]
class TestReverse:
def test_hello(self):
assert reverse('hello') == 'olleh'
def test_empty(self):
assert reverse('') == ''
def test_single_char(self):
assert reverse('a') == 'a'
class TestFilter:
def test_mixed_numbers(self):
assert filter_positive([1, -2, 3, -4]) == [1, 3]
def test_all_negative(self):
assert filter_positive([-1, -2]) == []
def test_empty_list(self):
assert filter_positive([]) == []
Клас TestStringUtils
#Створіть клас `TestStringUtils` з трьома тест-методами: один для `capitalize_words(s)` (робить першу літеру кожного слова великою), один для `count_vowels(s)` (рахує a, e, i, o, u без урахування регістру) і один для `is_palindrome(s)`.
def capitalize_words(s):
return ' '.join(word.capitalize() for word in s.split())
def count_vowels(s):
return sum(1 for c in s.lower() if c in 'aeiou')
def is_palindrome(s):
return s == s[::-1]
class TestStringUtils:
pass # додайте три тест-методи тут
Рішення
def capitalize_words(s):
return ' '.join(word.capitalize() for word in s.split())
def count_vowels(s):
return sum(1 for c in s.lower() if c in 'aeiou')
def is_palindrome(s):
return s == s[::-1]
class TestStringUtils:
def test_capitalize_words(self):
assert capitalize_words('hello world') == 'Hello World'
assert capitalize_words('python') == 'Python'
def test_count_vowels(self):
assert count_vowels('hello') == 2
assert count_vowels('rhythm') == 0
assert count_vowels('AEIOU') == 5
def test_is_palindrome(self):
assert is_palindrome('racecar') is True
assert is_palindrome('hello') is False
assert is_palindrome('') is True
setup_method для спільного стану
#Додайте `setup_method` до класу `TestShoppingCart`, щоб кожен тест отримував свіжий кошик. Кошик має бути словником `{'items': [], 'total': 0}`. Перевірте, що `test_cart_still_empty_after_other_test` проходить навіть після виконання `test_can_add_item`.
class TestShoppingCart:
# додайте setup_method тут
def test_cart_starts_empty(self):
assert self.cart['items'] == []
def test_total_starts_at_zero(self):
assert self.cart['total'] == 0
def test_can_add_item(self):
self.cart['items'].append('apple')
assert len(self.cart['items']) == 1
def test_cart_still_empty_after_other_test(self):
# має проходити навіть якщо test_can_add_item виконався раніше
assert self.cart['items'] == []
Рішення
class TestShoppingCart:
def setup_method(self):
self.cart = {'items': [], 'total': 0}
def test_cart_starts_empty(self):
assert self.cart['items'] == []
def test_total_starts_at_zero(self):
assert self.cart['total'] == 0
def test_can_add_item(self):
self.cart['items'].append('apple')
assert len(self.cart['items']) == 1
def test_cart_still_empty_after_other_test(self):
assert self.cart['items'] == []
Розподіл тестів по файлах і фільтрування з -k
#Створіть два тестових файли: `test_math.py` з тестами для `add` і `multiply`, та `test_strings.py` з тестами для `str.upper()` і `str.lower()`. Запустіть `pytest -k 'math'` і переконайтеся, що виконуються лише math-тести. Потім запустіть `pytest -k 'multiply'`.
# test_math.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# напишіть test_add і test_multiply тут
# test_strings.py
# напишіть test_upper і test_lower
Рішення
# test_math.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_multiply():
assert multiply(3, 4) == 12
assert multiply(0, 5) == 0
# test_strings.py
def test_upper():
assert 'hello'.upper() == 'HELLO'
def test_lower():
assert 'WORLD'.lower() == 'world'
Структура src/ + tests/
#Створіть мінімальну структуру проекту: помістіть `string_utils.py` з функцією `slugify(s)` (перетворює на нижній регістр і замінює пробіли на дефіси) у `src/`, два тести -- у `tests/test_string_utils.py`, і додайте `pytest.ini` з `testpaths = tests`. Запустіть pytest з кореня проекту.
# src/string_utils.py
def slugify(s):
pass # нижній регістр + замінити пробіли на дефіси
# tests/test_string_utils.py
# імпортуйте та протестуйте slugify тут
# pytest.ini
# [pytest]
# testpaths = tests
Рішення
# src/string_utils.py
def slugify(s):
return s.lower().replace(' ', '-')
# tests/test_string_utils.py
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from string_utils import slugify
def test_slugify_spaces():
assert slugify('hello world') == 'hello-world'
def test_slugify_uppercase():
assert slugify('Hello World') == 'hello-world'
# pytest.ini
# [pytest]
# testpaths = tests