Dividing a test into a set of smaller tests

I want to be able to split a large test into smaller tests, so when smaller tests pass, they imply that a large test will also pass (so there is no reason to run the original large test). I want to do this because smaller tests usually take less time, less effort and less fragile. I would like to know if there are test design patterns or validation tools that can help me achieve this test splitting in a reliable way.

I am afraid that the connection between smaller tests and the original test will be lost if someone changes something in the set of smaller tests. Another concern is that a set of small tests does not actually cover a large test.

An example of what I'm aiming for:

//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);
}
+5
3

, , - (). . , , .

- "" . " ". , , .

, . , :

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

( JUnit, , @Before). , , xUnit , . JUnit public static void, @BeforeClass.

, .

, - :

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);
    }

}
+2
0

theBigTest B. smallerTest1 B. smallerTest2 InputFromA.

, ?

A a B, , A::process Input, InputFromA B.

A B, .

[EDIT] .

@mkorpela, , , , , . A B. process(), B process(). - B A.

0

All Articles