sintesis

mockito

Códigos disponibles en git

¿Por qué?

  • Prueba lenta: porque el DOC realiza cálculos extensivos o accesos a memoria secundaria o a base de datos, …

  • Prueba errática: porque el DOC suministra al SUT valores de entrada “descontrolados” (aleatório, hora, señal de sensor, …)

  • Prueba interventora: porque el DOC es una interfaz de usuario, como en MVC, cuando el SUT es el controlador.

  • Prueba con Sermón de Preparación: porque la construcción del DOC es un esfuerzo superior a la configuración de un doble

  • Prueba con Dependencia Oculta: porque el DOC es parte de otro componente, por ejemplo, librería de acceso a otro servidor vía tcp/ip, o no debe cambiar su estado por ejemplo, un servicio de correo y no se desea desplegar un servicio de correo alternativo

  • DOC no está disponible: porque no está finalizada por parte de un compañero, o es posterior en desarrollo es top/down, TDD outside-in, …​ o porque están con una versión anterior

    • …​ la gran desventaja es que son frágiles al depender de la implantación

¿Qué?

  • Los dobles son los objetos que imitan el comportamiento de objetos reales de una forma controlada en el entorno de prueba.

    • Por ejemplo:

      • Al igual que los ingenieros de automóviles utilizan un muñeco, crash dummy, cuando simulan un accidente para estudiarlo y evitar pruebas con personas reales. Luego entregan un coche que se conducirá por una persona real

      • Directores de cine usan “dobles” para las tomas que entrañan riesgos para el actor principal … pero cuidado!?! a producción entregan la película con las escenas del “doble”?!? No sería una buena analogía

dummies

¿Para qué?

  • ¿Cómo pruebo un SUT con técnicas de Caja Negra

    • cuya entrada depende de un DOC o

    • cuya salida va hacia un DOC?

  • no está implementado, o

  • está en producción y no se puede alterar, o

  • es un componente externo a la prueba unitaria, o

  • es muy lento, o

  • …​?

  • ¿Cómo pruebo un SUT con técnicas de Caja Blanca?

¿Cómo?

Tipos de Dobles

  • Entradas:

    • argumentos en la preparación y ejercicio del SUT

    • atributos del SUT, precisa dobles

    • retornos del DOC, precisa dobles

  • Salidas:

    • retornos del SUT en la comprobación

    • atributos del SUT, a veces, precisa dobles

    • argumentos del DOC, precisa dobles

sinDobles
  • Con Spy, interceptor con capacidad de doblar parcialmente

    • crea el doble y el DOC, intercediendo y reenviando los mensajes

    • permite configuración del doble

    • permite su verificación del doble

  • Con Mock

    • crea el doble pero no el objeto

    • requiere configuración del doble

    • permite verificación del doble

conSpia
conMock

Inicialización del Doble

Con initMocks Con Rule Con Runwith, obsoleto
public class InitSutTest {

...
@Before
public void before(){
    initMocks(this);(1)
    ...
}
...
}
1 En org.mockito.MockitoAnnotations.initMocks
public class RuleSutTest {

...
@Rule
public MockitoRule initRule = MockitoJUnit.rule();(1)
...
}
1 En org.junit.Rule, org.mockito.junit.MockitoRule y org.mockito.junit.MockitoJUnit
@RunWith( MockitoJUnitRunner.class)(1)
public class RunWithSutTest {
...
}
1 En org.junit.runner.RunWith y org.mockito.junit.MockitoJUnitRunner
SUT.java DOC.java
public class SUT {

  private DOC doc;
  private int attribute;

  public SUT(DOC doc){
    this.doc = doc;
    this.attribute = 0;
  }

  public void voidMethod(){
    System.out.println("voidMethod of SUT");
    this.attribute++;
    this.doc.voidMethod();
  }

  public void voidMethodWithParemeters(int parameter, Other other){
    System.out.println("voidMethodWithParemeters of SUT");
    this.attribute++;
    this.doc.voidMethodWithParemeters(parameter, other);
  }

  public void voidMethodWithExcpetion() throws Exception {
    System.out.println("voidMethodWithExcpetion of SUT");
    this.attribute++;
    this.doc.voidMethodWithExcpetion();
  }

  public int returnMethod() {
    System.out.println("returnMethod of SUT");
    return this.attribute + this.doc.returnMethod();
  }

  public int returnMethodWithParemeters(int parameter){
    System.out.println("returnMethodWithParemeters of SUT");
    return parameter + this.attribute + this.doc.returnMethodWithParemeters(parameter);
  }

  public int returnMethodWithExcpetion() throws Exception {
    System.out.println("returnMethodWithExcpetion of SUT");
    return + this.attribute + this.doc.returnMethodWithExcpetion();
  }

  public void slowMethod(){
    this.doc.slowMethod();
  }

}
public class DOC {

  private int attribute;

  public DOC(){
    this.attribute = 0;
  }

  public void voidMethod() {
    System.out.println("voidMethod of DOC");
    this.attribute++;
  }

  public void voidMethodWithParemeters(int parameter, Other reDoc) {
    System.out.println("voidMethodWithParemeters of DOC");
    this.attribute++;
    this.attribute += parameter;
  }

  public void voidMethodWithExcpetion() throws Exception {
    System.out.println("voidMethodWithExcpetion of DOC");
    this.attribute++;
    throw new Exception("message");
  }

  public int returnMethod() {
    System.out.println("returnMethod of DOC");
    return this.attribute;
  }

  public int returnMethodWithParemeters(int parameter) {
    System.out.println("returnMethodWithParemeters of DOC");
    return parameter + this.attribute;
  }

  public int returnMethodWithExcpetion() throws Exception {
    System.out.println("returnMethodWithExcpetion of DOC");
    throw new Exception("message");
    //    return this.attribute++;
  }

  public void slowMethod(){
    for(int i=Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++){
    }
  }

}
sutDoc
Mocks
public class SutTest {

  private SUT sut;
  private DOC doc;

  @Before
  public void before() {
    initMocks(this);
    this.doc = mock(DOC.class); (1)
    this.sut = new SUT(this.doc);
  }
1 En org.mockito.Mockito.mock
public class SutTest {

    @InjectMocks (1)
    private SUT sut;
    @Mock (2)
    private DOC doc;

    @Before
    public void before(){
        initMocks(this);
    }
1 En org.mockito.InjectMocks
2 En org.mockito.Mock
Spies

Con métodos estáticos

Con anotaciones

public class SutTest {

  private SUT sut;
  private DOC doc;

  @Before
  public void before() {
    initMocks(this);
    this.doc = spy(new DOC()); (1)
    this.sut = new SUT(this.doc);
  }
1 En org.mockito.Mockito.spy
public class SutTest {

    @InjectMocks (1)
    private SUT sut;
    @Spy (2)
    private DOC doc;

    @Before
    public void before(){
        initMocks(this);
    }
1 En org.mockito.InjectMocks
2 En org.mockito.Spy

Verificación

  • verify(<doble>)

    • .<mensaje>(<argumentos);

this.sut.voidMethod();
verify(this.doc).voidMethod();
  • verifica que el doble recibió el mensaje

this.sut.voidMethodWithParemeters(1, new Other(0));
this.sut.voidMethod();
verify(this.doc).voidMethod();
  • independientemente del orden de ejecución y verificación

this.sut.voidMethodWithParemeters(1, new Other(0));
this.sut.voidMethod();
verify(this.doc).voidMethod();
verify(this.doc).voidMethodWithParemeters(1, new Other(0));
  • múltiples intercepciones requiere el argumento times(n)

this.sut.voidMethodWithParemeters(1, new Other(0));
this.sut.voidMethod();
this.sut.voidMethodWithParemeters(0, null);
this.sut.voidMethod();
verify(this.doc).voidMethodWithParemeters(0, null);
verify(this.doc, times(2)).voidMethod();
verify(this.doc).voidMethodWithParemeters(1, new Other(0));
  • variaciones de frecuencia: atLeastOnce(), atLeast(n), atMost(n), never()

this.sut.voidMethod();
verify(this.doc, times(1)).voidMethod(); // default
verify(this.doc, atLeastOnce()).voidMethod();
verify(this.doc, atLeast(1)).voidMethod();
verify(this.doc, atMost(1)).voidMethod();
verify(this.doc, never()).voidMethodWithParemeters(0, null);
  • verifyZeroInteractions(<doble>);, permite comprobar que no hubo otras interacciones

verifyZeroInteractions(this.doc);
this.sut.voidMethod();
verify(this.doc).voidMethod();
verifyNoMoreInteractions(this.doc);
  • Se permiten matchers de Hamfcrest: anyInt(n), anyBoolean(b), …​, any(o), any(), …​anyList(), …​

this.sut.voidMethodWithParemeters(0, null);
this.sut.voidMethodWithParemeters(1, new Other(0));
verify(this.doc).voidMethodWithParemeters(1, new Other(0));
verify(this.doc)
  .voidMethodWithParemeters(anyInt(), eq(new Other(0)));
verify(this.doc)
  .voidMethodWithParemeters(anyInt(), isA(Other.class));
verify(this.doc)
  .voidMethodWithParemeters(anyInt(), (Other) isNull());
verify(this.doc)
  .voidMethodWithParemeters(eq(0), (Other) isNull());
verify(this.doc)
  .voidMethodWithParemeters(intThat(greaterThan(-1)), (Other) isNull());
  • Se pueden especializar los matchers implementando ArgumentMatcher

this.sut.voidMethodWithParemeters(0, new Other(1));
verify(this.doc)
  .voidMethodWithParemeters(anyInt(), argThat(new isValidOther()));
class isValidOther implements ArgumentMatcher<Other> {
  public boolean matches(Other other) {
      return 0 <= other.attribute && other.attribute <= 2;
  }
  public String toString() {
      return "[isValidOther]";
  }

}
  • Permite captura del argumento del mensaje que el DOC recibe del SUT con ArgumentCaptor

ArgumentCaptor<Integer> value = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Other> other = ArgumentCaptor.forClass(Other.class);
this.sut.voidMethod();
this.sut.voidMethodWithParemeters(1, new Other(-1));
verify(this.doc).voidMethod();
verify(this.doc).voidMethodWithParemeters(value.capture(), other.capture());
assertThat(value.getValue(), is(1));
assertThat(other.getValue().attribute, is(-1));
  • Permite configurar el orden de verificación con InOrder

Other other = new Other(0);
this.sut.voidMethodWithParemeters(1, other);
this.sut.voidMethodWithParemeters(2, other);
this.sut.voidMethodWithParemeters(3, other);
InOrder inOrder = inOrder(this.doc);
inOrder.verify(this.doc).voidMethodWithParemeters(1, other);
inOrder.verify(this.doc).voidMethodWithParemeters(2, other);
inOrder.verify(this.doc).voidMethodWithParemeters(3, other);
  • Incluso entre varios dobles argumentos de InOrder

Other other = new Other(0);
DOC doc = spy(DOC.class);
SUT sut = new SUT(doc);
this.sut.voidMethodWithParemeters(1, other);
sut.voidMethodWithParemeters(1, other);
this.sut.voidMethodWithParemeters(2, other);
sut.voidMethodWithParemeters(2, other);
InOrder inOrder = inOrder(this.doc, doc);
inOrder.verify(this.doc).voidMethodWithParemeters(1, other);
inOrder.verify(doc).voidMethodWithParemeters(1, other);
inOrder.verify(this.doc).voidMethodWithParemeters(2, other);
inOrder.verify(doc).voidMethodWithParemeters(2, other);
  • Permite configurar el mensaje en el informe de pruebas con description

this.sut.voidMethod();
verify(this.doc, times(1)
  .description("Mensaje")).voidMethod();
verify(this.doc, atLeastOnce()
  .description("Mensaje")).voidMethod();
verify(this.doc, atLeast(1)
  .description("Mensaje")).voidMethod();
verify(this.doc, atMost(1)
  .description("Mensaje")).voidMethod();
verify(this.doc, never()
  .description("Mensaje")).voidMethodWithParemeters(0, null);
  • Permite establecer expiración con timeout

this.sut.slowMethod();
verify(this.doc, timeout(1)).slowMethod();

Configuración

  • when(<doble>.<mensaje>({<argumentos>,}))

    • .thenReturn({<expresión>,});

@Test
public void testSameThenReturn() {
  when(this.doc.returnMethod()).thenReturn(0);
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(2));
  assertThat(this.sut.returnMethod(), is(2));
}
  • permite configurar la salida del DOC, siempre igual

@Test
public void testSameDoReturn() {
  doReturn(0).when(this.doc).returnMethod();
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(2));
  assertThat(this.sut.returnMethod(), is(2));
}
  • Permite variaciones y fija a partir de último

@Test
public void testSeveralThenReturn() {
  when(this.doc.returnMethod()).thenReturn(0, 1);
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(3));
  assertThat(this.sut.returnMethod(), is(3));
}
  • doReturn({<expresiones>,})

    • .when(<doble>).<mensaje>({<argumentos>,});

@Test
public void testSeveralDoReturn() {
  doReturn(0, 1).when(this.doc).returnMethod();
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethod(), is(3));
  assertThat(this.sut.returnMethod(), is(3));
}
  • Permite configurar distintos valores en distintos momentos

@Test
public void testWithDisjointValuesThenReturn() {
  when(this.doc.returnMethodWithParemeters(0)).thenReturn(0);
  when(this.doc.returnMethodWithParemeters(1)).thenReturn(1);
  when(this.doc.returnMethodWithParemeters(2)).thenReturn(2);
  this.sut.voidMethod();
  assertThat(this.sut.returnMethodWithParemeters(0), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethodWithParemeters(1), is(4));
  assertThat(this.sut.returnMethodWithParemeters(2), is(6));
}
  • versión con doReturn

@Test
public void testWithDisjointValuesDoReturn() {
  doReturn(0).when(this.doc).returnMethodWithParemeters(0);
  doReturn(1).when(this.doc).returnMethodWithParemeters(1);
  doReturn(2).when(this.doc).returnMethodWithParemeters(2);
  this.sut.voidMethod();
  assertThat(this.sut.returnMethodWithParemeters(0), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethodWithParemeters(1), is(4));
  assertThat(this.sut.returnMethodWithParemeters(2), is(6));
}
  • Permite la configuración mediante matchers

@Test
public void testWithSametMatchers() {
  when(this.doc.returnMethodWithParemeters(anyInt()))
    .thenReturn(0);
  this.sut.voidMethod();
  assertThat(this.sut.returnMethodWithParemeters(0), is(1));
  this.sut.voidMethod();
  assertThat(this.sut.returnMethodWithParemeters(1), is(3));
  assertThat(this.sut.returnMethodWithParemeters(2), is(4));
}
  • El orden y restricción de los matchers determina la configuración final

@Test
public void testWithMixedValuestMatchers() {
  when(this.doc.returnMethodWithParemeters(2))
    .thenReturn(-100); // of no effect
  when(this.doc.returnMethodWithParemeters(anyInt()))
    .thenReturn(3);
  when(this.doc.returnMethodWithParemeters(0))
    .thenReturn(0);
  this.sut.voidMethod();
  assertThat(this.sut
    .returnMethodWithParemeters(0), is(1));
  this.sut.voidMethod();
  assertThat(this.sut
    .returnMethodWithParemeters(1), is(6));
  assertThat(this.sut
    .returnMethodWithParemeters(2), is(7));
}
  • Permite la especialización de matchers mediante ArgumentMatcher

@Test
public void testWithArgumentMatcher() {
  doNothing().when(this.doc)
    .voidMethodWithParemeters(
        anyInt(), argThat(new isValidOther()));
  this.sut.voidMethodWithParemeters(1, new Other(-1));
  this.sut.voidMethodWithParemeters(1, new Other(0));
  this.sut.voidMethodWithParemeters(1, new Other(3));
  assertThat(this.sut.returnMethod(), is(3));
}
  • doNothing().when(<doble>).<mensaje>({<argumentos>,});

@Test
public void testVoid() {
  doNothing().when(this.doc).voidMethod();
  doNothing().when(this.doc)
    .voidMethodWithParemeters(1, new Other(0));
  this.sut.voidMethod();
  this.sut.voidMethodWithParemeters(1, new Other(0));
  assertThat(this.sut.returnMethod(), is(2));
}
  • doAnswer(new Answer(){

    • public Object answer(InvocationOnMock invocation) {

    • Object[] args = invocation.getArguments();

    • return <expresión>;

    • }

    • }).when(<doble>).<mensaje>({<argumento>,});

@Test
public void testWithDoAnswer() {
  doAnswer(new Answer() {
    public Object answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      int value = (Integer) args[0];
      return value + 5;
    }
  }).when(this.doc).returnMethodWithParemeters(5);
  assertThat(this.sut
    .returnMethodWithParemeters(5), is(15));
}
  • Versión thenAnswer

@Test
public void testWithThenAnswer() {
  when(this.doc.returnMethodWithParemeters(5))
    .thenAnswer(new Answer() {
      public Object answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        int value = (Integer) args[0];
        return value + 5;
      }
  });
  assertThat(this.sut
    .returnMethodWithParemeters(5), is(15));
}
  • doThrow(<excepción>)

    • .when(<doble>).<mensaje>({<argumento>,});

@Test(expected = Error.class)
public void testWithExceptions() throws Exception {
  doThrow(new Error("test")).when(this.doc)
    .voidMethodWithExcpetion();
  this.sut.voidMethodWithExcpetion();
}
  • when(<doble>.<mensaje>({<argumento>,}))

    • .thenThrow(<excepción>)

@Test(expected = Error.class)
public void testWithReturnsAndExceptions() throws Exception {
  when(this.doc.returnMethodWithExcpetion())
    .thenReturn(0).thenThrow(new Error("test"));
  this.sut.returnMethodWithExcpetion();
  this.sut.returnMethodWithExcpetion();
}
  • Valores diferentes de salida

@Test
public void testWithExceptionsAndReturns() throws Exception {
  when(this.doc.returnMethodWithExcpetion())
    .thenThrow(new Error("test")).thenReturn(0);
  try {
    this.sut.returnMethodWithExcpetion();
  } catch (Error ex) {
  }
  assertThat(this.sut.returnMethodWithExcpetion(), is(0));
}

Múltiples Dobles

DOC DOC
public class Y {

  public int m() {
    System.out.println("m de Y " + this);
    return -1;
  }

}
public class Z {

  public int m() {
    System.out.println("m de Z "+ this);
    return -1;
  }

}

Dobles en Parámetros

Test SUT y DOC
public class XDoubleTest {

  @Mock
  private Y y;

  @Mock
  private Z z;

  @InjectMocks
  private X x;

  @Before
  public void before() {
    initMocks(this);
  }

  @Test
  public void testM()  {
    this.x.m(this.y, new Y(),
      new Z(), this.z);
  }

}
public class X {

  private int result;

  public X() {
    this.result = 0;
  }

  public void m(Y y1, Y y2,
                Z z1, Z z2) {
    this.result = y1.m() + y2.m()
      + z1.m() + z2.m();
    System.out.println("m de X con "
      + this.result);
  }

}
  • (Y igual que la anterior)

  • (Z igual que la anterior)

Dobles en Atributos

Test SUT y DOC
public class XDoubleTest {

  @Mock
  Z z;

  @Mock
  Y y;

  @InjectMocks
  X x;

  @Before
  public void before() {
      initMocks(this);
  }

  @Test
  public void testM() {
    this.x.m();
  }

}
public class X {

  private Y y1;
  private Y y2;
  private Z z1;
  private Z z2;
  private int result;

  public X() {
    this.y1 = new Y();
    this.y2 = new Y();
    this.z1 = new Z();
    this.z2 = new Z();
    this.result = 0;
  }

  public void m() {
    this.result = this.y1.m() + this.y2.m()
      + this.z1.m() + this.z2.m();
    System.out.println("m de X con " + this.result);
  }

}
  • (Y igual que la anterior)

  • (Z igual que la anterior)

Dobles en Locales

Test SUT y DOC
public class XDoubleTest {

  @Mock
  Y y;

  @Mock
  Z z;

  @InjectMocks
  private X x;

  @Before
  public void before() {
    initMocks(this);
    this.x = spy(this.x);
  }

  @Test
  public void testM() {
    doReturn(0).when(this.y).m();
    doReturn(0).when(this.z).m();
    doReturn(this.y).when(x).createY();
    doReturn(this.z).when(x).createZ();
    this.x.m();
  }

}
public class X {

  private int result;

  public X() {
    this.result = 0;
  }

  public void m() {
    this.result = this.createY().m() + new Y().m() + this.createZ().m() + new Z().m();
    System.out.println("m de X con " + this.result);
  }

  Y createY() {
    return new Y();
  }

  Z createZ() {
    return new Z();
  }

}
  • (Y igual que la anterior)

  • (Z igual que la anterior)

Aplicaciones

-

-

Sintesis

sintesis

Bibliografía

Obra, Autor y Edición Portada Obra, Autor y Edición Portada
  • xUnit Test Patterns: Refactoring Test Code

    • Gerard Meszaros

    • Addison-Wesley Educational Publishers Inc; Edición: 01 (21 de mayo de 2007)

xUnitTestPatternsRefactoringTestCode

  • Effective Unit Testing

    • Lasse Koskela

    • Manning Publications Company; Edición: 1 (16 de febrero de 2013)

EffectiveUnitTesting

  • The Art of Software Testing

    • Glenford J. Myers

    • John Wiley & Sons Inc; Edición: 3. Auflage (16 de diciembre de 2011)

TheArtofSoftwareTesting

  • Pragmatic Unit Testing in Java with JUnit

    • Andy Hunt, Dave Thomas

    • The Pragmatic Programmers (1674)

PragmaticUnitTestinginJavawithJUnit

  • Testing Computer Software

    • Cem Kaner,Jack Falk,Hung Q. Nguyen

    • Wiley John + Sons; Edición: 2nd (12 de abril de 1999)

TestingComputerSoftware

  • Diseno Agil con TDD

    • Ble Jurado, Carlos

    • Lulu.com (18 de enero de 2010)

DisenoAgilconTDD

  • Test Driven Development

    • Kent Beck

    • Addison Wesley; Edición: 01 (8 de noviembre de 2002)

TestDrivenDevelopment

  • Growing Object-Oriented Software, Guided by Tests

    • Steve Freeman

    • Addison-Wesley Professional (12 de octubre de 2009)

GrowingObjectOrientedSoftware,GuidedbyTests

  • How Google Tests Software

    • James A. Whittaker,Jason Arbon,Jeff Carollo

    • Addison-Wesley Educational Publishers Inc; Edición: 01 (2 de abril de 2012)

HowGoogleTestsSoftware

  • Pragmatic Project Automation: How to Build, Deploy, and Monitor Java Apps

    • Mike Clark

    • The Pragmatic Programmers (1900)

PragmaticProjectAutomation

Ponente

  • Luis Fernández Muñoz

setillo

  • Doctor en Inteligencia Artificial por la UPM

  • Ingeniero en Informática por la UMA

  • Diplomado en Informática por la UPM

  • Profesor Titular de ETSISI de la UPM