Arrange-Act-Assert структура, понятные assert-сообщения, один тест — одна концепция
Вы уже умеете писать тесты. Но работающий тест и хороший тест — это разные вещи.
Цель: научиться писать тесты, которые легко читать, поддерживать и которые находят баги раньше, чем их увидят пользователи.
Реальный пример плохого теста:
def test_stuff():
u = User("Alice", 25)
assert u.greet() == "Hello, I'm Alice!"
u.celebrate_birthday()
assert u.age == 26
u2 = User("Bob", 16)
assert not u2.is_adult()
# ... ещё 10 assert'овЧто не так:
test_stuff)Хороший тест должен:
AAA — это структура теста из 3 частей:
def test_bad():
user = User("Alice", 25)
greeting = user.greet()
assert greeting == "Hello, I'm Alice!"
user.celebrate_birthday()
assert user.age == 26Проблема: непонятно где setup, где действие, где проверка.
def test_user_greeting():
# Arrange: подготовка данных
user = User("Alice", 25)
# Act: выполнение действия
greeting = user.greet()
# Assert: проверка результата
assert greeting == "Hello, I'm Alice!"Почему лучше:
greet())def test_bank_account_deposit():
# Arrange
account = BankAccount(initial_balance=100)
# Act
new_balance = account.deposit(50)
# Assert
assert new_balance == 150
assert account.balance == 150
def test_bank_account_insufficient_funds():
# Arrange
account = BankAccount(initial_balance=50)
# Act & Assert (для исключений можно объединить)
with pytest.raises(ValueError) as exc_info:
account.withdraw(100)
assert "Insufficient funds" in str(exc_info.value)Плохой assert:
def test_discount():
price = calculate_discount(100, 0.1)
assert price == 90Когда тест падает:
AssertionError: assert 91 == 90
Непонятно: что за 91? Что за 90? Откуда взялась скидка 0.1?
def test_discount_with_message():
# Arrange
original_price = 100
discount_rate = 0.1
expected_price = 90
# Act
actual_price = calculate_discount(original_price, discount_rate)
# Assert
assert actual_price == expected_price, (
f"Discount calculation failed: "
f"expected {expected_price} but got {actual_price} "
f"for price {original_price} with {discount_rate * 100}% discount"
)Когда тест падает:
AssertionError: Discount calculation failed: expected 90 but got 91
for price 100 with 10.0% discount
✅ Теперь понятно ЧТО пошло не так!
# ❌ ПЛОХО — тестирует И greeting, И birthday, И adult status
def test_user_all_methods():
user = User("Alice", 17)
assert user.greet() == "Hello, I'm Alice!"
user.celebrate_birthday()
assert user.age == 18
assert user.is_adult() == TrueКогда тест падает на greet():
greet() или что-то ещё# ✅ ХОРОШО — каждый тест проверяет ОДНУ вещь
def test_user_greeting():
"""Тест приветствия"""
user = User("Alice", 17)
greeting = user.greet()
assert greeting == "Hello, I'm Alice!"
def test_user_celebrates_birthday():
"""Тест увеличения возраста"""
user = User("Alice", 17)
new_age = user.celebrate_birthday()
assert new_age == 18
assert user.age == 18
def test_user_becomes_adult_after_birthday():
"""Тест: после 18-го дня рождения становится взрослым"""
user = User("Alice", 17)
user.celebrate_birthday() # Теперь 18
assert user.is_adult() == True, (
f"User should be adult after turning {user.age}"
)Преимущества:
greet() сломан — другие тесты всё равно запустятся# ❌ ПЛОХО
def test_1():
pass
def test_user():
pass
def test_function():
passПроблема: не понятно ЧТО тестируется.
# ✅ ХОРОШО — описывает ПОВЕДЕНИЕ
def test_user_greets_with_their_name():
"""Приветствие содержит имя пользователя"""
pass
def test_deposit_increases_balance():
"""Пополнение увеличивает баланс"""
pass
def test_withdraw_fails_when_insufficient_funds():
"""Снятие выбрасывает ошибку при недостатке средств"""
passPattern: test_[что]_[делает]_[при_каких_условиях]
Примеры:
test_discount_applies_correctly_for_vip_userstest_login_fails_with_wrong_passwordtest_email_sends_when_order_confirmeddef test_shopping_cart():
cart = ShoppingCart()
cart.add_item("apple", 1.50)
cart.add_item("bread", 2.00)
assert cart.total() == 3.50
cart.apply_discount(0.1)
assert cart.total() == 3.15
cart.remove_item("apple")
assert cart.total() == 1.80def test_empty_cart_has_zero_total():
"""Пустая корзина имеет итог 0"""
# Arrange
cart = ShoppingCart()
# Act
total = cart.total()
# Assert
assert total == 0, "Empty cart should have total of 0"
def test_adding_items_increases_total():
"""Добавление товаров увеличивает итог"""
# Arrange
cart = ShoppingCart()
# Act
cart.add_item("apple", price=1.50)
cart.add_item("bread", price=2.00)
total = cart.total()
# Assert
assert total == 3.50, (
f"Expected total 3.50 for apple(1.50) + bread(2.00), got {total}"
)
def test_discount_reduces_total_by_percentage():
"""Скидка уменьшает итог на процент"""
# Arrange
cart = ShoppingCart()
cart.add_item("apple", price=1.50)
cart.add_item("bread", price=2.00)
discount = 0.1 # 10%
expected_total = 3.15
# Act
cart.apply_discount(discount)
actual_total = cart.total()
# Assert
assert actual_total == expected_total, (
f"10% discount on 3.50 should give {expected_total}, "
f"but got {actual_total}"
)Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.
Далее: Parametrize: один тест — десятки сценариев