Test Driven Development
¿panacea del desarrollo o pérdida de tiempo?
Israel Saeta Pérez
@dukebody en todas partes
+10.000 backend + 5.000 frontend tests solamente en sistemas principales
DiversIT
PyConES 2021
¿quieres participar? escríbeme
coste
beneficio
-
Intro. Tests, coste/beneficio, niveles, TDD.
-
100% 💩 unit test coverage
-
integration test fails
-
Mi estrategia actual
intro
¿Qué es un test automático?
def foo_bar_baz(number):
if number % 2 == 0 and number % 3 == 0:
return "baz"
if number % 2 == 0:
return "foo"
elif number % 3 == 0:
return "bar"
else:
return None
def test_foo():
number = 2 # preparación
result = foo_bar_baz(number) # ejecución
assert result == "foo" # validación
def test_foo():
number = 6
result = foo_bar_baz(number)
assert result == "baz"
utilidad
- Saber si un sistema funciona como se espera
- Saber qué parte de un sistema es la que no funciona bien
coste
- Crear/escribir la prueba
- Mantenerla cuando haya cambios
- Tiempo de ejecución
Niveles de prueba
componente
función/clase
servicio entero
frontend + backend
selenium
cypress
unittest+framework
pact
unittest
tdd
mantra
- No debes escribir código sin escribir tests antes
- Todo tu código debe estar cubierto por tests (100% coverage)
- Escribe tu código para que pueda testearse unitariamente
TDD >>> que tu código tenga tests
TDD = que los tests sean los que guíen el código
100% 💩 unit test coverage
tests 💩 para 100% coverage
def add_1(number):
return number + 2
@pytest.mark.parametrize("number", [1, 3, -5])
def test_add_1(number):
assert add_1(number) = number + 2
tests 💩 para 100% coverage
import get_builder
def build_something():
builder = get_builder()
builder.build()
@mock.patch("myfile.get_builder")
def test_build_something(mock_get_builder):
build_something()
assert mock_get_builder().build.called
tests 💩 para 100% coverage
def do_whatever():
logger.info("Doing whatever!")
return True
@mock.patch("myfile.logger_info")
def test_do_whatever_logging(mock_logger_info):
do_whatever()
mock_logger_info.assert_called_with("Doing whatever!")
tests 💩 para 100% coverage
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def test_person_str():
person = Person("Alberto")
assert str(person) == "Alberto"
def get_order():
return ["A", "B", "C"]
def test_order():
assert get_order() == ["A", "B", "C"]
valor TEST ~ probabilidad fallo
100% ✅ unit test coverage
!= siempre
funciona bien
2 unit tests, 0 integration tests
import os
from unittest import mock
def is_active():
if os.getenv("SERVICE_ACTIVE", False):
return True
return False
@mock.patch("os.getenv")
def test_activated(mock_getenv):
mock_getenv.return_value = True
assert is_active() is True
@mock.patch("os.getenv")
def test_deactivated(mock_getenv):
mock_getenv.return_value = False
assert is_active() is False
from datetime import datetime
from unittest import mock
def view(request):
start = request.GET["start"]
end = request.GET["end"]
return get_data(start=start, end=end)
def get_data(since, until):
return {
"since": since.isoformat(),
"until": until.isoformat()
}
@mock.patch("__main__.get_data")
def test_view(get_data):
start = datetime(2020, 2, 10)
end = datetime(2020, 2, 11)
request = mock.Mock(
GET={"start": start, "end": end}
)
view(request)
get_data.assert_called_with(start=start, end=end)
mi estrategia actual
escribe los tests con la perspectiva de alguien que no ha escrito el código o se enfrenta a revisarlo
empieza por el nivel más alto razonable y crea tests más unitarios sólo si tiene sentido en el diseño real del programa
evita escribir tests unitarios que sean triviales o sólo prueben la implementación - no te ayudarán a refactorizar!
más valor cuanto más cerca de requisito real
enlaces
-
Why most unit testing is waste
James O Coplien -
A fresh look at testing
Daniel Hall -
Test-induced design damage
David Heinemeier Hansson - Seque
James O Coplien - The art of misdirection
Dan North
¿Y tú qué opinas sobre...?
- ¿TDD hace que el diseño de tu sistema sea mejor?
- ¿Hay que hacer más tests de integración? ¿Más unitarios?
- ¿Cuántas veces has tenido bugs a pesar de tener tests? ¿Era por un caso que no habías contemplado? ¿Era por un problema de integración?
- ¿Qué estrategia tiene tu equipo para mejorar el ratio coste/beneficio de los tests?
Test Driven Development Filosófico
By Israel Saeta Pérez
Test Driven Development Filosófico
- 1,398