JUnit 5 - How to run a test method only if another test method passes?

4

It would look something like this:

@Test
void metodo1() { assertTrue(...); }

@Test
void metodo2() { //Deve ser executado após método1(), e SOMENTE SE metodo1() passou!
   assertTrue(...); 
}

I need to do this for cases where it does not make sense to run a "method2 ()" if the "method1 ()" test failed; are cases where "method2 ()" would receive exceptions for which I do not want to prepare it to receive. Example with calculator:

class TestCalculadora {
    private Calculadora calc = new Calculadora();
    @Test void nao_deve_lancar_exception_ao_tentar_dividir_por_zero() {
        try { calc.calcular("3/0"); } 
        catch(Exception e) { fail("lançou '"+e.getMessage()+"'"); }
    }
    @Test void deve_retornar_um_resultado_apos_tentar_dividir_por_zero() {
        assertNotNull(calc.calcular("3/0"));
    }
    @Test void deve_retornar_uma_msg_de_erro_apos_tentar_dividir_por_zero() {
        Resultado r = calc.calcular("3/0");
        String msgDeErro = r.getMensagem();
        assertEquals(msgDeErro, "Impossível Dividir por Zero!");
    }
    @Test void nao_deve_retornar_numero_apos_tentar_dividir_por_zero() {
        Resultado r = calc.calcular("3/0");
        BigDecimal num = r.getNumResultante();
        assertNull(num);
    }
}

See in this example that the deve_retornar_uma_msg_de_erro_apos_tentar_dividir_por_zero() and nao_deve_retornar_numero_apos_tentar_dividir_por_zero() methods should not be executed (should be ignored) if one of the nao_deve_lancar_exception_ao_tentar_dividir_por_zero() and / or deve_retornar_um_resultado_apos_tentar_dividir_por_zero() methods fail, otherwise they will throw a NullPointerException if calcular("3/0") return null or they will receive an Exception thrown by this calcular method that they do not expect to receive.

How to deal with this? Is there a way already provided by JUnit 5 to handle these situations?

    
asked by anonymous 24.02.2018 / 19:52

2 answers

4

You can do this using TestNG. TestNG is a test framework that is built on top of Junit. For more details see documentation: link

There are two ways to do this with TestNG:

1 - Using dependsOnMethods

@Test 
public void nao_deve_lancar_exception_ao_tentar_dividir_por_zero() {
    try { calc.calcular("3/0"); } 
    catch(Exception e) { fail("lançou '"+e.getMessage()+"'"); }
}
@Test
public void deve_retornar_um_resultado_apos_tentar_dividir_por_zero() {
    assertNotNull(calc.calcular("3/0"));
}
@Test(dependsOnMethods= {"nao_deve_lancar_exception_ao_tentar_dividir_por_zero"}) 
public void deve_retornar_uma_msg_de_erro_apos_tentar_dividir_por_zero() {
    Resultado r = calc.calcular("3/0");
    String msgDeErro = r.getMensagem();
    assertEquals(msgDeErro, "Impossível Dividir por Zero!");
}
@Test(dependsOnMethods= {"deve_retornar_um_resultado_apos_tentar_dividir_por_zero"}) 
public void nao_deve_retornar_numero_apos_tentar_dividir_por_zero() {
    Resultado r = calc.calcular("3/0");
    BigDecimal num = r.getNumResultante();
    assertNull(num);
}

This @Test was imported from TestNG: import org.testng.annotations.Test; . The asserts remain Junit's. dependsOnMethods receives a list of tests that need to pass before the underlying test runs.

If one of the tests that are in dependsOnMethods fails, a skip will be given in the underlying test.

TheotherwayistousedependsOnGroups.Imaginethatyouhaveasetofteststhatyouneedtoperformbeforeanothersetoftests.Inthiscase,TestNGallowsyoutoplacethefirstsetoftestsinagroupandthensaythatthesecondsetdependsonthefirst.

@Test(groups={"primeirosTestes"})
public void test1() {

}
@Test(groups= {"primeirosTestes"})
public void test2() {
    fail();
}
@Test(dependsOnGroups= {"primeirosTestes"})
public void test3() {

}
@Test(dependsOnGroups= {"primeirosTestes"})
public void test4() {

}

If you are using eclipse, you will need to add a plugin to run the tests in the same way you do in Junit:

Onceinstalled,thetestscanrunthesamewayasJunit,butinsteadofclicking"Run as Junit Test", you will click on "Run as TestNG Test". I do not know what the configuration is like in other IDEs.

    
25.02.2018 / 17:29
8

You should think that in your calculator example you described 4 tests. Reasonable to think so, right? Even JUnit will say that. But you did only 1 test.

Writing test cases

Every test case should consist of 3 basic parts:

  • Data being tested
  • operations to be performed on this data
  • expected results measurements
  • I usually summarize this way:

    • data
    • operations
    • gauges

    The same data set undergoing the same operations must undergo all possible measurements within the same @Test .

    It is also worth mentioning that each test case should basically be a unit independent of the others annotated with @Test . At most dependent on @Before or whose side effects are broken in @After . This means that each test case should be done in a way totally isolated from the others, so that they are not temporally coupled, where the execution of one precedes the other.

    Modeling Your Test Case

    In your case, we have as data:

  • the properly constructed calculator
  • the expression "3/0"
  • The operation is:

  • get Resultado of call from calculadora.calcular(expressao)
  • And the measurements:

  • Error message in result:

    String msgDeErro = r.getMensagem();
    assertEquals(msgDeErro, "Impossível Dividir por Zero!");
    
  • resulting null number:

    BigDecimal num = r.getNumResultante();
    assertNull(num);
    
  • So your 4 test methods are reduced to just one test:

    @Test
    public void dividindoPorZero() {
      // dados 
      Calculadora calc = new Calculadora();
      String expr = "3/0";
    
      // operações 
      Resultado r = calc.calcular(expr);
    
      // aferições 
      String msgDeErro = r.getMensagem(); // isso já verifica se 'r' é nulo, lançando NPE caso seja
      assertEquals(msgDeErro, "Impossível Dividir por Zero!");
      assertNull(r.getNumResultante)(
    }
    

    Much more clean , do not you think? Not to mention that more elegant too, given the modeling.

    Modeling Exceptions

    There are situations where code should raise an exception. And ready. Whoever modeled the program did so to throw an exception during an operation, for some reason (whether it was done right or wrong at concept level or performance is another five hundred, but if to operate the exception must be guaranteed, then the situation that triggers it needs be tested).

    For example, the Java language expects to launch NullPointerException in a number of cases. Among them, call method of a null object. How can this be ascertained? Creating Expectations!

    For example, I can have these two test cases, one for throwing an exception and the other for not throwing an exception:

  • No exception:
    • given: string with the value "abc"
    • operation: call method .toString()
    • verification: arrived at the end
  • Exception throw
    • given: string null
    • operation: call method .toString()
    • gauging: has thrown the specific exception NullPointerException
  • @Test
    public void naoLancaExcecao() {
      // dado
      String a = "abc";
      // operação 
      a.toString();
      // aferição implícita, precisa não lançar exceção 
    }
    
    @Test(expect = NullPointerException.class)
    public void lancaExcecao() {
      // dado
      String a = null;
      // operação 
      a.toString();
      // aferição implícita, precisa lançar exceção do tipo NullPointerException
    }
    

    If you create an expectation that is not met, JUnit marks it as an error. If you do not create expectations and explode an exception, JUnit also marks as an error.

    Conditional execution

    I believe you believe that you were in this problem. But no, you were not. In that case, you simply do not make the operations / measurements unnecessary.

    Recently where I work I had to implement 36 test cases: 36 different data sets (4 variables: 3 states, 3 states, Boolean, Boolean) and 1 possible operation for each of these data sets.

    The class in question had two methods: one that said whether the main method should be performed and the main method. Obviously, if the main method should not be run, I would not run it. And the main method execution had 3 possible behaviors: it throws exception A, throws exception B or executes clean.

    I created a helper method to create the dataset (it was pretty simple, simple enough so I did not need to create a test case to test my test utility). Here are three possible test cases (variables that do not show creation were created in @Before ):

    @Test
    public void naoExecuta() {
      Dados d1 = criaDados(Enum.Tipo1, Enum.tipo1, true, true);
      assertFalse(myObj.estahAtivo(d1));
    }
    
    @Test(expect = MyExceptionB.class)
    public void cabum() {
      Dados d1 = criaDados(Enum.Tipo1, Enum.tipo3, true, true);
    
      assertTrue(myObj.estahAtivo(d1));
      myObj.metodoExplosivo(d1);
    }
    
    @Test
    public void naoExolode() {
      Dados d1 = criaDados(Enum.Tipo1, Enum.tipo3, true, false);
    
      assertTrue(myObj.estahAtivo(d1));
      myObj.metodoExplosivo(d1);
    }
    
        
    25.02.2018 / 05:24