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

  1. Intro. Tests, coste/beneficio, niveles, TDD.

  2. 100% 💩 unit test coverage

  3. integration test fails

  4. 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

¿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