Pregunta

Quiero ser capaz de dividir una gran prueba para las pruebas más pequeñas de modo que cuando las pruebas más pequeños pasan dan a entender que la gran prueba también pasaría (lo que no hay razón para correr la gran prueba original). Quiero hacer esto porque las pruebas más pequeños suelen realizarse en menos tiempo, menos esfuerzo y son menos frágiles. Me gustaría saber si hay patrones de diseño de prueba o herramientas de verificación que pueden ayudar a mí para lograr esta división de prueba de una manera robusta.

Me temo que la conexión entre las pruebas más pequeños y la prueba original se pierde cuando alguien cambia algo en el conjunto de las pruebas más pequeños. Otro temor es que el conjunto de las pruebas más pequeños en realidad no cubre la gran prueba.

Un ejemplo de lo que estoy apuntando en:

//Class under test
class A {

  public void setB(B b){ this.b = b; }

  public Output process(Input i){
    return b.process(doMyProcessing(i));
  }

  private InputFromA doMyProcessing(Input i){ ..  }

  ..

}

//Another class under test
class B {

   public Output process(InputFromA i){ .. }

  ..

}

//The Big Test
@Test
public void theBigTest(){
 A systemUnderTest = createSystemUnderTest(); // <-- expect that this is expensive

 Input i = createInput();

 Output o = systemUnderTest.process(i); // <-- .. or expect that this is expensive

 assertEquals(o, expectedOutput());
}

//The splitted tests

@PartlyDefines("theBigTest") // <-- so something like this should come from the tool..
@Test
public void smallerTest1(){
  // this method is a bit too long but its just an example..
  Input i = createInput();
  InputFromA x = expectedInputFromA(); // this should be the same in both tests and it should be ensured somehow
  Output expected = expectedOutput();  // this should be the same in both tests and it should be ensured somehow

  B b = mock(B.class);
  when(b.process(x)).thenReturn(expected);

  A classUnderTest = createInstanceOfClassA();
  classUnderTest.setB(b);

  Output o = classUnderTest.process(i);

  assertEquals(o, expected);
  verify(b).process(x);
  verifyNoMoreInteractions(b);
}

@PartlyDefines("theBigTest") // <-- so something like this should come from the tool..
@Test
public void smallerTest2(){
  InputFromA x = expectedInputFromA(); // this should be the same in both tests and it should be ensured somehow
  Output expected = expectedOutput();  // this should be the same in both tests and it should be ensured somehow

  B classUnderTest = createInstanceOfClassB();

  Output o = classUnderTest.process(x);

  assertEquals(o, expected);
}
¿Fue útil?

Solución

La primera sugerencia de que voy a hacer es volver a factor de sus pruebas en rojo (no). Para ello, tendrá que romper su código de producción temporalmente. De esta manera, usted sabe que las pruebas siguen siendo válidas.

Un patrón común es utilizar un accesorio de prueba separada por colección de pruebas "grandes". Usted no tiene que atenerse a las "todas las pruebas para una clase en clase una prueba" patrón. Si un un conjunto de pruebas están relacionadas entre sí, pero no están relacionados con otro conjunto de pruebas, luego ponerlos en su propia clase.

La mayor ventaja de utilizar una clase separada para mantener las pequeñas pruebas individuales para la gran prueba es que usted puede tomar ventaja de la configuración y los métodos de lágrimas hacia abajo. En su caso, me gustaría mover las líneas que ha comentado con:

// this should be the same in both tests and it should be ensured somehow

para el método de configuración (en JUnit, un método anotado con @Before). Si tiene alguna configuración inusualmente caros que hay que hacer más, la mayoría de las infraestructuras de prueba xUnit tener una manera de definir un método de instalación que se ejecuta una vez antes de que todas las pruebas. En JUnit, este es un método public static void que tiene la anotación @BeforeClass.

Si los datos de prueba es inmutable, tiendo a definir las variables como constantes.

Juntando todo esto, es posible que tenga algo como:

public class TheBigTest {

    // If InputFromA is immutable, it could be declared as a constant
    private InputFromA x;
    // If Output is immutable, it could be declared as a constant
    private Output expected;

    // You could use 
    // @BeforeClass public static void setupExpectations()
    // instead if it is very expensive to setup the data
    @Before
    public void setUpExpectations() throws Exception {
      x = expectedInputFromA();
      expected = expectedOutput();
    }

    @Test
    public void smallerTest1(){
      // this method is a bit too long but its just an example..
      Input i = createInput();

      B b = mock(B.class);
      when(b.process(x)).thenReturn(expected);

      A classUnderTest = createInstanceOfClassA();
      classUnderTest.setB(b);

      Output o = classUnderTest.process(i);

      assertEquals(o, expected);
      verify(b).process(x);
      verifyNoMoreInteractions(b);
    }

    @Test
    public void smallerTest2(){
      B classUnderTest = createInstanceOfClassB();

      Output o = classUnderTest.process(x);

      assertEquals(o, expected);
    }

}

Otros consejos

Todo lo que puedo sugerir es el libro xUnit . Si hay una solución que debería estar ahí.

theBigTest falta la dependencia de B. También se burla smallerTest1 B dependencia. En smallerTest2 debe burlarse InputFromA.

¿Por qué se crea un gráfico de dependencias como lo hizo?

A toma una B luego cuando A::process Input, a continuación, InputFromA proceso posterior en B.

Mantener la gran prueba y perfeccionar por A y B para cambiar la asignación de dependencia.

[EDIT] en respuesta a las observaciones.

@mkorpela, mi punto es que al mirar el código y sus dependencias es la forma de empezar a tener una idea de cómo crear pruebas más pequeños. A tiene una dependencia en B. Con el fin de que se complete su process() debe utilizar B de process(). Debido a esto, B tiene una dependencia en A.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top