**Зачем тесты и почему pytest**
Тесты — это исполняемая документация. Они доказывают что ваш код делает то что вы думаете, и ловят регрессии при изменениях. pytest — де-факто стандарт: меньше шаблонного кода чем `unittest`, мощный parametrize, огромная экосистема плагинов.
**Первый тест на pytest**
Любая функция начинающаяся с `test_` в файле начинающемся с `test_` — автоматически обнаруживается:
```python
# test_math.py
def add(a, b):
return a + b
def test_add_integers():
assert add(2, 3) == 5
def test_add_floats():
assert add(0.1, 0.2) == pytest.approx(0.3)
```
**pytest assertions vs unittest**
pytest переписывает `assert` чтобы показывать детальные diff при неудаче:
```python
# стиль unittest:
self.assertEqual(result, [1, 2, 3])
self.assertRaises(ValueError, func, bad_arg)
# стиль pytest — чистый Python:
assert result == [1, 2, 3]
with pytest.raises(ValueError):
func(bad_arg)
```
**`@pytest.mark.parametrize` — тестирование нескольких входов**
```python
import pytest
@pytest.mark.parametrize('a, b, expected', [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
```
**Тестирование исключений**
```python
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError, match='cannot divide'):
divide(10, 0)
def test_divide_by_zero_type():
with pytest.raises(ZeroDivisionError) as exc_info:
divide(10, 0)
assert 'zero' in str(exc_info.value)
```
**Фикстуры — повторно используемая подготовка**
Фикстура — функция с декоратором `@pytest.fixture` устанавливающая общее состояние для тестов. pytest внедряет фикстуры по имени параметра:
```python
import pytest
@pytest.fixture
def user():
return {'name': 'Alice', 'role': 'admin'}
def test_admin_access(user): # pytest внедряет 'user' автоматически
assert user['role'] == 'admin'
```
**Очистка фикстуры с `yield`**
```python
@pytest.fixture
def db_connection():
conn = connect_to_test_db()
yield conn # настройка возвращается здесь
conn.close() # очистка после теста
```
**Область видимости фикстуры**
```python
@pytest.fixture(scope='module') # один экземпляр на модуль
def expensive_resource():
return setup_slow_external_service()
@pytest.fixture(scope='session') # один экземпляр на весь запуск
def db_schema():
return create_schema()
```
**`conftest.py` — общие фикстуры между файлами**
Фикстуры в `conftest.py` автоматически доступны всем тестам в той же директории и поддиректориях — без import:
```
tests/
conftest.py <- фикстуры здесь
test_users.py <- использует фикстуры из conftest
test_orders.py <- тоже
```
**`unittest.mock` — замена зависимостей**
```python
from unittest.mock import patch
@patch('mymodule.requests.get')
def test_fetch_data(mock_get):
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {'key': 'value'}
result = fetch_data('https://api.example.com')
assert result == {'key': 'value'}
mock_get.assert_called_once_with('https://api.example.com')
```
**`monkeypatch` — встроенный патчер pytest**
```python
def test_home_dir(monkeypatch, tmp_path):
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.setattr(os.path, 'exists', lambda p: True)
# изменения автоматически откатываются после теста
```
Напишите три тестовые функции pytest для функции `add(a, b)`: `test_add_positive`, `test_add_negative` и `test_add_zero`. Каждая должна вызывать `add()` и проверять результат через `assert`.
Напишите тесты для функции `divide(a, b)`. Проверьте, что `divide(10, 2)` возвращает `5.0`, и что `divide(10, 0)` выбрасывает `ZeroDivisionError` с помощью `pytest.raises`.
import pytest
def divide(a: float, b: float) -> float:
return a / b
def test_divide_normal():
pass
def test_divide_by_zero():
pass
Решение
import pytest
def divide(a: float, b: float) -> float:
return a / b
def test_divide_normal():
assert divide(10, 2) == 5.0
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
Используйте `@pytest.mark.parametrize` для тестирования `is_palindrome(s)` с пятью случаями: `'racecar'` → True, `'hello'` → False, `'madam'` → True, `'a'` → True, `'ab'` → False. Запишите все пять как один параметризованный тест.
Напишите функцию `get_debug_mode() -> bool`, читающую переменную окружения `DEBUG` и возвращающую `True`, если она равна `'1'`, иначе `False`. Напишите два теста с `monkeypatch.setenv`: `DEBUG=1` → True, `DEBUG=0` → False.
Функция `get_username()` вызывает `os.getlogin()`, что может не работать в CI. Используйте `monkeypatch.setattr`, чтобы заменить `os.getlogin` лямбдой, возвращающей `'testuser'`. Убедитесь, что `get_username()` возвращает `'testuser'`.
Напишите фикстуру `temp_list`, создающую список `[1, 2, 3]`, возвращающую его через `yield`, затем очищающую после теста (teardown через `yield`). Напишите тест `test_append`, добавляющий `4` и проверяющий `len == 4`.
import pytest
@pytest.fixture
def temp_list():
data = [1, 2, 3]
yield data
# teardown: очистка после теста
pass
def test_append(temp_list):
temp_list.append(4)
assert len(temp_list) == 4
Решение
import pytest
@pytest.fixture
def temp_list():
data = [1, 2, 3]
yield data
data.clear()
def test_append(temp_list):
temp_list.append(4)
assert len(temp_list) == 4
Напишите функцию `write_and_read(path, text)`, записывающую `text` в `path`, читающую обратно и возвращающую содержимое. Используйте встроенную фикстуру `tmp_path` для тестирования без реальной файловой системы.
from pathlib import Path
def write_and_read(path: Path, text: str) -> str:
path.write_text(text)
return path.read_text()
def test_write_and_read(tmp_path):
file = tmp_path / 'test.txt'
result = write_and_read(file, 'hello')
assert result == 'hello'
Решение
from pathlib import Path
def write_and_read(path: Path, text: str) -> str:
path.write_text(text)
return path.read_text()
def test_write_and_read(tmp_path):
file = tmp_path / 'test.txt'
result = write_and_read(file, 'hello')
assert result == 'hello'
Напишите тесты для функции `circle_area(r)` (возвращает `math.pi * r ** 2`). Используйте `pytest.approx` для сравнения — обычное `==` для float не сработает. Тестируйте с `r=1` (~3.14159), `r=2` (~12.566) и `r=0` (0).
Мы используем необходимые 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 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.