1. Simple Test
- Test one feature at a time
- Small test
- A single assertion
Example code
def some_calculation(a, b):
return a + b
def make_a_dict(a, b):
if a < 0 or b < 0:
raise ValueError("a and b must be positive")
operation = some_calculation(a, b)
return {"a": a, "b": b, "result": operation}Bad
def test_dict():
assert make_a_dict(2, 3) == {"a": 2, "b": 3, "result": 5}
with pytest.raises(ValueError):
make_a_dict(-1, -1)Good
def test_make_a_dict():
my_dict = make_a_dict(2, 3)
expected_dict = {"a": 2, "b": 3, "result": 5}
assert my_dict == expected_dict
def test_make_a_dict_with_negative_values():
with pytest.raises(ValueError):
make_a_dict(-1, -1)2. Mock everything we don’t want to test
Isolate functions from other functions.
Bad
def test_make_a_dict():
my_dict = make_a_dict(2, 3)
expected_dict = {"a": 2, "b": 3, "result": 5}
assert my_dict == expected_dictGood
def test_make_a_dict(mocker):
mocker.patch(
"__main__.some_calculation",
return_value=5,
autospec=True
)
my_dict = make_a_dict(2, 3)
expected_dict = {"a": 2, "b": 3, "result": 5}
assert my_dict == expected_dict
def test_some_calculation():
value = some_calculation(2, 3)
assert value == 53. DRY (Don’t repeat yourself)
@pytest.mark.parametrize
def add_numbers(a, b):
return a + b
@pytest.mark.parametrize("a, b, expected_result", [
(2, 3, 5),
(-10, 5, -5),
(0, 0, 0),
(100, -50, 50)
])
def test_add_numbers(a, b, expected_result):
result = add_numbers(a, b)
assert result == expected_result@pytest.fixture
class Person:
def __init__(self, name):
self.name = name
self.age = 0
def is_adult(self):
return self.age >= 18Bad
def test_person_is_adult():
person = Person("Emi")
person.age = 19
assert person.is_adult()
def test_person_is_not_adult():
person = Person("Emi")
person.age = 10
assert not person.is_adult()Good
@pytest.fixture
def person():
return Person("Emi")
def test_person_is_adult(person):
person.age = 19
assert person.is_adult()
def test_person_is_not_adult(person):
person.age = 10
assert not person.is_adult()4. Test behavior, not implementation
from dataclasses import dataclass
@dataclass
class User:
username: str
class InMemoryUserRepository:
def __init__(self):
self._users = []
def add(self, user):
self._users.append(user)
def get_by_username(self, username):
return next(user for user in self._users if user.username == username)Bad — accede a internals del objeto
def test_add():
user = User(username="johndoe")
repository = InMemoryUserRepository()
repository.add(user)
assert user in repository._users # accediendo a _users directamenteGood
def test_added_user_can_be_retrieved_by_username():
user = User(username="johndoe")
repository = InMemoryUserRepository()
repository.add(user)
assert user == repository.get_by_username(user.username)5. Do not test the framework (at least in unit tests)
Bad — estás testeando Django/Flask, no tu código
@pytest.mark.django_db
def test_django_authentication():
user = User.objects.create_user(username='testuser', password='testpassword')
authenticated = user.check_password('testpassword')
assert authenticated6. autospec=True
Útil para verificar que una función es llamada con los argumentos correctos. Autospec genera un mock que respeta la firma original de la función.
def test_make_a_dict(mocker):
mocker.patch(
"__main__.some_calculation",
return_value=5,
autospec=True
)
my_dict = make_a_dict(2, 3)
expected_dict = {"a": 2, "b": 3, "result": 5}
assert my_dict == expected_dict7. Mark tests: slow, integration, exhaustive
Usar markers para categorizar los tests y poder correr subconjuntos según el contexto.