TDD: ¿Cómo escribir código testeable?

77
TDD: Cómo escribir código testeable.

description

 

Transcript of TDD: ¿Cómo escribir código testeable?

Page 1: TDD: ¿Cómo escribir código testeable?

TDD: Cómo escribir código testeable.

Page 2: TDD: ¿Cómo escribir código testeable?

¿Hacer tests es bueno?

Page 3: TDD: ¿Cómo escribir código testeable?

¿Por qué no los hacemos?

Page 4: TDD: ¿Cómo escribir código testeable?

Razones

Válidas

Inválidas

No hacer

pruebas

¡No se!

Código antiguo

El diseño es malo

No coge los bugs

Es len

to

UI

De eso se encarga QA

Demasiadas interfacesEs dificil de cambiar

Aburi

do…

.

Page 5: TDD: ¿Cómo escribir código testeable?

Hacer tests es una habilidad

Page 6: TDD: ¿Cómo escribir código testeable?

Entonces…

¿Cómo se escribe código difícil de probar?

Page 7: TDD: ¿Cómo escribir código testeable?

¿Por qué?

Construcción de objetosTrabajo en el constructorEstados globalesViolaciones de la Ley de Deméter

Page 8: TDD: ¿Cómo escribir código testeable?

Localización del “new”

API de Prueb

as

Clase bajo

prueba

Estímulo

Verificaciones

Page 9: TDD: ¿Cómo escribir código testeable?

Localización del “new”

API de Prueb

as

Clase bajo

prueba

Estímulo

Verificaciones

Otra clase

Otra clase

Otra clase

Instanciado dentro de la clasePasado por referenciasEstados globales

Page 10: TDD: ¿Cómo escribir código testeable?

Localización del “new”

API de Prueb

as

Clase bajo

prueba

Estímulo

Verificaciones

Otra clase

Otra clase

Otra clase

Otra clase

Otra clase

Otra clase

Otra clase

Page 11: TDD: ¿Cómo escribir código testeable?

Localización del “new”

API de Prueb

as

Clase bajo

prueba

Estímulo

Verificaciones

Otra clase

Otra clase

Otra clase

Otra clase

Otra clase

Otra clase

Otra clase

Costu

ra

Page 12: TDD: ¿Cómo escribir código testeable?

Localización del “new”

API de Prueb

as

Clase bajo

prueba

Estímulo

Verificaciones

Clase Falsa

Clase Falsa

Clase Falsa

Costu

ra

Page 13: TDD: ¿Cómo escribir código testeable?

Localización del “new”

Construcción del Grafo de Objetos y Búsqueda

Lógica de Negocio

Page 14: TDD: ¿Cómo escribir código testeable?

Localización del “new”

Construcción del Grafo de Objetos y Búsqueda

Lógica de Negocio

Page 15: TDD: ¿Cómo escribir código testeable?

Localización del “new”

API de Prueb

as

Clase bajo

prueba

Estímulo

Verificaciones

Clase Falsa

Clase Falsa

Clase Falsa

Instanciado dentro de la clasePasado por referenciasEstados globales

Page 16: TDD: ¿Cómo escribir código testeable?

Ejemplo (House)

Page 17: TDD: ¿Cómo escribir código testeable?

House

class House {Kitchen kitchen = new Kitchen();Bedroom bedroom;public House() {

this.bedroom = new Bedroom();

}}

Page 18: TDD: ¿Cómo escribir código testeable?

House Test

class HouseTests {@Testpublic void impossibleTestOnIsolation() {

// Can’t replace anything!!!}

}

Page 19: TDD: ¿Cómo escribir código testeable?

Análisis

Fácil de instanciar pero…No se puede remplazar nada.Puede ser costoso

computacionalmenteEl diseño está acoplado. Cerrado para extensión.

Page 20: TDD: ¿Cómo escribir código testeable?

House (Refactoizado)

class House {Kitchen kitchen;Bedroom bedroom;@Injectpublic House(Kitchen kitchen, Bedroom br) {

this.bedroom = br;this.kitchen = kitchen;

}}

Page 21: TDD: ¿Cómo escribir código testeable?

House Test (Refactorizado)

class HouseTests {@Testpublic void easierToTestOnIsolation() {DummyKitchen dk = new DummyKitchen();DummyBedroom db = new DummyBedroom();House h = new House(dk,db);// …}

}

Page 22: TDD: ¿Cómo escribir código testeable?

Ejemplo (Gardener)

Page 23: TDD: ¿Cómo escribir código testeable?

Gardener

class Garden {Gardener joe;public Garden(Gardener joe) {

joe.setWorkHours(new TwelveHours());

joe.setBoots(new ExpensiveBoots());

this.joe = joe;}

}

Page 24: TDD: ¿Cómo escribir código testeable?

Análisis

Mejor pero… No se pueden remplazar las botas

o el horario.La prueba puede tardar (horario

de 12h).Necesita el jardinero pero se

encarga de configurarlo.

Page 25: TDD: ¿Cómo escribir código testeable?

Gardener (Refactored)

class Garden {Gardener joe;@Injectpublic Garden(Gardener joe) {

this.joe = joe;}

}

class GardenerProvider {@ProvidesGardener getGardener(ExpensiveBoots boots, TwelveHours schedule) {

Gardener joe = new Gardener();joe.setWorkHours(schedule);joe.setBoots(boots);return joe;

}}

Page 26: TDD: ¿Cómo escribir código testeable?

¿Por qué?

Construcción de objetosTrabajo en el constructorEstados globalesViolaciones de la Ley de Deméter

Page 27: TDD: ¿Cómo escribir código testeable?

Costo de la construcción

Para testear, primero hay que instanciar pero…

El trabajo dentro del constructor no tiene “costuras”

No se puede sobre escribir.La prueba tiene que saber

navegar lo que haya dentro

Page 28: TDD: ¿Cómo escribir código testeable?

Ejemplo (Super Car)

Page 29: TDD: ¿Cómo escribir código testeable?

Super Car

class Car {Engine engine;public Car(File file) {

String m = readModelFromCfg(file);engine = new

EngineFactory().create(m);}

}

Page 30: TDD: ¿Cómo escribir código testeable?

Super Car Test

class CarTest {@Testpublic void hardToSetupTest() {

File file = new File(“custom.conf”);Car car = new Car(file);//Asserts...

}}

Page 31: TDD: ¿Cómo escribir código testeable?

Análisis

Pasamos un File cuando lo que se necesita es un Engine.Toda prueba que necesite Car requiere eta parafernaria.Accede al sistema de ficheros

Lento.No es una prueba unitaria

realmente.

Page 32: TDD: ¿Cómo escribir código testeable?

Super Car (Refactorizado)

class Car {Engine engine;@Injectpublic Car(Engine engine) {

this.engine = engine;}

}

Page 33: TDD: ¿Cómo escribir código testeable?

Super Car (Provider)

class CarEngineProvider {@ProvidesEngine getEngine(EngineFactory factory, @EngineModel String model) {

return factory.create(model);}

}

Page 34: TDD: ¿Cómo escribir código testeable?

Super Car Test (Refactorizado)

class CarTest {@Testpublic void easyToSetupTest() {

Engine engine = new FakeEngine();

Car car = new Car(engine);//Asserts...

}}

Page 35: TDD: ¿Cómo escribir código testeable?

¿Por qué?

Construcción de objetosTrabajo en el constructorEstados globalesViolaciones de la Ley de Deméter

Page 36: TDD: ¿Cómo escribir código testeable?

Locura

Page 37: TDD: ¿Cómo escribir código testeable?

Locura

Definición:Repetir una misma acción una y otra vez y esperar obtener un resultado diferente.

Albert Einstein

Page 38: TDD: ¿Cómo escribir código testeable?

O mejor dicho… Estados Globales

class X {public X() {}public int doSomething() {

//… Something…}

}

Page 39: TDD: ¿Cómo escribir código testeable?

Estados Globales

int a = new X().doSomething();

int b = new X().doSomething();

Page 40: TDD: ¿Cómo escribir código testeable?

Estados Globales

¿ a == b ó a != b ?

Page 41: TDD: ¿Cómo escribir código testeable?

Estados Globales

X x1 = new X(); X

Y

Z

Q

Page 42: TDD: ¿Cómo escribir código testeable?

Estados Globales

X x1 = new X();

X x2 = new X();

X

Y

Z

Q

X

Y

Z

Q

Page 43: TDD: ¿Cómo escribir código testeable?

Estados Globales

X x1 = new X();x1.doSomething();

A == B ó A != B

X x2 = new X();x2.doSomething();

X

Y

Z

Q

X

Y

Z

Q

GS

Page 44: TDD: ¿Cómo escribir código testeable?

Consecuencias

Múltiples ejecucionesResultados inesperadosOrden importaNo se pueden ejecutar en paralelo

Localización del estado desacotado.

Page 45: TDD: ¿Cómo escribir código testeable?

Estados Globales Ocultos

System.currentTime()new Date()Math.random()

Page 46: TDD: ¿Cómo escribir código testeable?

Consecuencias

La API miente:A cerca de lo que necesitaHace cosas que dan miedo detrás

de la fachada

Page 47: TDD: ¿Cómo escribir código testeable?

Ejemplo (Credit Card)

Page 48: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

Page 49: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

java.lang.NullPointerException at talk2.CreditCard.charge(CreditCard.java:48)

Page 50: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

CreditCardProcessor.init(…);CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

Page 51: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

CreditCardProcessor.init(…);CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

java.lang.NullPointerException at talk2.CreditCardProcessor.init(CreditCardProcessor.java:134)

Page 52: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

OfflineQueue.start();CreditCardProcessor.init(…);CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

Page 53: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

OfflineQueue.start();CreditCardProcessor.init(…);CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

java.lang.NullPointerException at talk2.OffileQueue.start (OfflineQueue.java:203)

Page 54: TDD: ¿Cómo escribir código testeable?

Credit Card

@Testpublic void creditCardChargeTest() {

Database.connect(…);OfflineQueue.start();CreditCardProcessor.init(…);CreditCard cc = new CreditCard(“1234567890”);cc.charge(200);

}

Page 55: TDD: ¿Cómo escribir código testeable?

Credit Card

¿Se ve un patrón?

Page 56: TDD: ¿Cómo escribir código testeable?

Credit Card

Miente sobre sus dependencias.El orden de las inicializaciones no es

explicito.

Page 57: TDD: ¿Cómo escribir código testeable?

Credit Card

¿Qué podemos hacer?

Page 58: TDD: ¿Cómo escribir código testeable?

Credit Card (Refactorizado)

@Testpublic void creditCardChargeTest() {

Database db = Database(“connectionStr”);OfflineQueue queue = new OfflineQueue(db);CreditCardProcessor ccProcessor = new CreditCardProcessor(queue);CreditCard cc = new CreditCard(“1234567890”, ccProcesor);cc.charge(200);

}

Page 59: TDD: ¿Cómo escribir código testeable?

Credit Card

¡Tenemos opciones!

Page 60: TDD: ¿Cómo escribir código testeable?

Credit Card

Pero…

¿Y si termino con 20 parámetros?

Es porque los tenias… solo que…

Page 61: TDD: ¿Cómo escribir código testeable?
Page 62: TDD: ¿Cómo escribir código testeable?

Accounting 101

class AccountView {User user;public AccountView() {

user = RPCClient.getInstance().getUser();}

}

Page 63: TDD: ¿Cómo escribir código testeable?

Análisis

Uso de Estados GlobalesIncorpora las dependencias del “singleton”Miente a cerca de las dependencias.

Page 64: TDD: ¿Cómo escribir código testeable?

Accounting 101

class AccountView {User user;public AccountView(User user) {

user = user;}

}

Page 65: TDD: ¿Cómo escribir código testeable?

¿Por qué?

Construcción de objetosTrabajo en el constructorEstados globalesViolaciones de la Ley de Deméter

Page 66: TDD: ¿Cómo escribir código testeable?
Page 67: TDD: ¿Cómo escribir código testeable?

La ley de Deméter

Cada unidad debería tener conocimiento limitado sobre otras unidades: solo unidades “relacionadas de cerca” a la unidad actual.

Cada unidad debe de hablar solamente con sus amigos; No hablar con extraños.

Habla solamente con tus amigos inmediatos.

Page 68: TDD: ¿Cómo escribir código testeable?

Service Locator

Aka. Context, aka. Registry… etc.

Mejor que un singletonAl menos el problema está en un solo lugar.Es testeable… pero no muy bonito.

Esconde las dependencias reales.Las dependencias hacen el código poco

reusable.

Page 69: TDD: ¿Cómo escribir código testeable?

Service Locator

Class House {//…

public House(Locator locator) {//… Qué tengo que mockear???

}}

Page 70: TDD: ¿Cómo escribir código testeable?

House

Class House {//…Window window;Door door;Roof roof;public House(Locator locator) {

this.window = locator.getWindow();this.door = locator.getDoor();this.roof = locator.getRoof();

}}

Page 71: TDD: ¿Cómo escribir código testeable?

House (Refactorizado)

class House {//…Window window;Door door;Roof roof;public House(Window w, Door d, Roof, r) {

this.window = w;this.door = d;this.roof = r;

}}

Page 72: TDD: ¿Cómo escribir código testeable?

Service Locator

¿Qué otro problema tiene?Mezcla responsabilidades.

Buscar y encontrar.Creación

Se necesita tener una interface para testearSi dependes de ServiceLocator, dependes de todo lo demás.

Page 73: TDD: ¿Cómo escribir código testeable?

Making a Mockery…

class LoginPage {RPCClient client;HttpRequest request;public LoginPage(RPCClient client, HttpRequest request) {

this.client = client;this.request = request;

}

public boolean login() {String cookie = request.getCookie();return client.getAuthenticator().authenticate(cookie);

}}

Page 74: TDD: ¿Cómo escribir código testeable?

Análisis

Para testear se hace complicadoLas dependencias no son las declaradas.Crear rpc client, entrenarlo para devolver un authenticator, crear un authenticator… Etc.

Page 75: TDD: ¿Cómo escribir código testeable?

Making a Mockery…

class LoginPage {Authenticator authenticator;String cookie;public LoginPage(Authenticator auth, @Cookie String cookie) {

this.cookie= cookie;this.authenticator = auth;

}

public boolean login() {return authenticator.authenticate(cookie);

}}

Page 76: TDD: ¿Cómo escribir código testeable?

Making a Mockery

Cosas a tener en cuenta.Tiempo de vida de los objetos que se pasan.

Ej. Cookie puede ser distinto para cada vez

Page 77: TDD: ¿Cómo escribir código testeable?