**Навіщо тримати тести окремо від продуктивного коду?**
Тести не є частиною вашого готового продукту. Вони існують поруч з кодом для його перевірки, але користувачі ніколи їх не запускають. Зберігання тестів у виділеній директорії `tests/` (або у файлах `test_*.py`) робить це розмежування чітким, уникає захаращення імпортів і дає вам контроль над тим, що потрапляє до пакету розгортання.
**Як pytest виявляє тести:**
pytest обходить дерево директорій проекту і застосовує такі правила на кожному рівні:
1. **Рівень файлу** -- збирати файли, що відповідають `test_*.py` або `*_test.py`
2. **Рівень модуля** -- збирати функції, ім'я яких починається з `test_`
3. **Рівень класу** -- збирати класи, ім'я яких починається з `Test` (без `__init__`)
4. **Рівень методу** -- збирати методи всередині класів `Test*`, що починаються з `test_`
Кілька прикладів того, що збирається і що пропускається:
```
test_math.py → збирається
math_test.py → збирається
math.py → не збирається
helpers.py → не збирається
def test_add(): → збирається
def add_test(): → не збирається (немає префіксу test_)
def check_add(): → не збирається
class TestMath: → збирається
class MathTest: → не збирається (неправильний префікс)
class TestMath:
def test_add(): → збирається
def add_check(): → не збирається
```
Ви можете запустити `pytest --collect-only`, щоб побачити, що буде зібрано, до фактичного запуску.
**Звичайні функції vs тест-класи:**
Звичайні функції простіші і достатні для більшості випадків:
```python
def test_add():
assert add(2, 3) == 5
def test_subtract():
assert subtract(5, 3) == 2
```
Використовуйте класи, коли у вас є група пов'язаних тестів, що поділяють концепцію -- класи слугують простором імен і роблять зв'язок явним:
```python
class TestUserAccount:
def test_creation(self):
...
def test_deactivation(self):
...
def test_password_change(self):
...
```
Класи також підтримують `setup_method` / `teardown_method` для налаштування перед кожним тестом. Проте для спільних *даних* фікстури (розглядаються далі) є чистішим рішенням, ніж стан на рівні класу.
**Рекомендована структура проекту:**
```
my_project/
├── src/
│ ├── users.py
│ └── products.py
├── tests/
│ ├── conftest.py <- спільні фікстури (розглядаються пізніше)
│ ├── test_users.py
│ └── test_products.py
└── pytest.ini
```
Дзеркальне відображення структури `src/` у `tests/` полегшує пошук тестів для будь-якого модуля.
Виявлення на практиці: функції, класи та структура
Наведений нижче файл містить 6 плоских тест-функцій для двох тем (реверс рядка і фільтрація списку). Реорганізуйте їх у два класи `Test*` -- `TestReverse` і `TestFilter` -- не змінюючи жодного твердження.
Створіть клас `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` до класу `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'] == []
Створіть два тестових файли: `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
Створіть мінімальну структуру проекту: помістіть `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
Ми використовуємо необхідні cookies для роботи сайту. З вашого дозволу ми також можемо зберігати налаштування сайту та використовувати аналітичні й рекламні cookies, щоб розуміти використання сайту й підтримувати розвиток проєкту.
* Ви завжди можете змінити свій вибір у налаштуваннях сайту.
Оберіть категорії cookies
Налаштування аналітики
Можна вимкнути аналітику використання платформи. Також можна надіслати в Google Analytics запит на видалення даних про використання цього сайту, пов'язаних із цим браузером.
Навчальний workspace
Навчайтеся, читаючи, запускаючи код і розв'язуючи задачі.
Практикуйте програмування з поясненнями тем, вправами, інструментами browser IDE, перевіркою regex і тренуванням друку коду в одному workspace.
Відкривайте інструменти у вкладках.Вправи, IDE-інструменти й тренажери залишаються доступними як вкладки сайту.
Перемикайтеся без втрати контексту.Переходьте між поясненнями, кодом та утилітами, зберігаючи своє місце.
Використовуйте sidebar як карту.Ліві панелі містять навігацію, налаштування, файли, libraries та керування інструментами.
PythonJavaScriptSQLite
Одна IDE, три практичні режими
Python у браузері.Запускайте невеликі скрипти, пробуйте бібліотеки й тренуйте API-запити без встановлення.
JavaScript для швидких експериментів.Перевіряйте код для браузера й порівнюйте ідеї поруч із навчальними матеріалами.
SQLite для практики з даними.Відкривайте оглядач бази даних, переглядайте таблиці, пишіть запити й вивчайте SQL локально.
ТемаIDE
Працюйте поруч у split tabs
Тримайте інструкції перед очима.Відкрийте вправу або довідкову сторінку поруч з IDE, замість постійних перемикань.
Порівнюйте інструменти під час навчання.Розміщуйте перевірки regex, пояснення та експерименти з кодом поруч, коли це потрібно для задачі.
Закрийте split, коли завершите.Workspace повернеться до однієї сфокусованої вкладки, а відкриті вкладки сайту залишаться доступними.
Тренажер друку коду
Або звичайного тексту
Тренажер розрахований на фізичну клавіатуру.Відкрийте цей розділ на ноутбуці або комп'ютері з широким екраном. На телефоні тренування друку не працюватиме коректно.
Швидкість: 0 зн/хв
0 слів/хв
Найкраща швидкість (60с): 0 зн/хв
0 слів/хв
Помилки: 0
Загальний час: 0.0 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.