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

Asked

Viewed 1,864 times

3

It would be something like that:

@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 doesn’t even make sense to run a "methodo2()" if the "methodo1()" test failed; these are cases where "methodo2()" would receive exceptions for which I don’t 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);
    }
}

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

How to deal with it? There is a way already foreseen by Junit 5 to deal with these situations?

  • 1

    It seems to me that you’re not using Junit the way it was meant to work

2 answers

7

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

Writing test cases

Every test case should be composed of 3 basic parts:

  1. data being tested
  2. operations to be carried out on such data
  3. measurements of expected results

I usually sum it up like this:

  • dice
  • operations
  • measurements

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

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

Modeling Your Test Case

In your case, we have as data:

  1. the properly constructed calculator
  2. the expression "3/0"

The operation is:

  1. get the Resultado of the call for calculadora.calcular(expressao)

And the measurements:

  1. error message in result:

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

    BigDecimal num = r.getNumResultante();
    assertNull(num);
    

Thus, 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, don’t you think? Not to mention more elegant too, taking into account the modeling.

Modeling exceptions

There are situations in which the code must make an exception. And that’s it. Whoever modeled the program did it to make an exception during an operation, for some reason (whether done right or wrong at concept or performance level are another five hundred, but if to work must be guaranteed the exception, then the situation that fires it needs to be tested).

For example, the Java language provides for the release of NullPointerException in several cases. Among them, call a null object method. How can this be ascertained? Creating expectations!

For example, I can have these two test cases, one to cast exception and the other to not cast exception:

  1. Makes no exception:
    • given: string with value "abc"
    • operation: call the method .toString()
    • measurement: arrived at the end
  2. Exception
    • given: null string
    • operation: call the method .toString()
    • measurement: launched 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 don’t create expectations and explode an exception, Junit also marks as an error.

Conditional execution

I believe you believed you were in this problem. But no, you were not. In this case, you simply don’t do unnecessary operations/measurements.

Recently where I work I needed to implement 36 test cases: 36 distinct 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 stated whether the main method should be executed and the main method. Obviously, if the main method should not be executed, I did not execute it. And the execution of the main method had 3 possible behaviors: launches exception A, launches exception B or runs clean.

I created an auxiliary method to create the dataset (it was very simple, simple enough that I didn’t need to create a test case to test my test utility). I will illustrate here 3 possible test cases (variables that are not displayed 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);
}
  • Very obliged by the answer, I am new with the tests and you clarified things a lot! I think you need to review the test methods in the toString() example: the bottom would not be called "lancaExcecao" and its measurement would not be explicit since the method declares to expect an exception?

  • But I was doubtful about the names of the tests, I enjoyed making long names that describe the operation and the result expected, as ao_fazer_x_deve_retornar_y(), these names seem to make much clearer what the code tested does without having to analyze the code within the test methods. I saw here that it is good that " The names should explain not only the scenario being tested but should also explain the expected result." If I have several asserts, there is much expected to put in the name.

  • @Douglas thanks for the corrections. About to be explicit or implicit: I didn’t use any assertNull or assertEquals or anything like that. So in the method code there is nothing explicit to verify the exception. This is in charge of the annotation, which is meta-code. That’s why I still classified it as "implicit". But, yes, this can be debated whether the concept of "explicit" and "implicit" are correct, but the use of them within this context of "if explicit, has called assert..." is befitting

  • 1

    In Junit 5 there is the assertThrows that replaces this annotation, so there must surely no longer be this doubt :D

  • @Douglas assertThrows is new to me. In fact, if there are two points that can cast the same exception, the check becomes more accurate

3


You can do this using Testng. Testng is a test framework that is done on top of Junit. For more details see documentation: http://testng.org/doc/documentation-main.html

There are two ways to do this with Testng:

1 - Using the 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);
}

That one @Test was imported from Testng: import org.testng.annotations.Test;. The asserts remain Junit’s. O dependsOnMethods receives a list of tests that need to pass for the underlying test to run.

If one of the tests that are on dependsOnMethods fail, will be given a Skip in the underlying test. inserir a descrição da imagem aqui

The other way is by using the dependsOnGroups. Imagine that you have a set of tests that you need to run before another set of tests. In this case, Testng allows you to place the first set of tests in a group and then say that the second group depends on the first.

@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 in order to run the tests in the same way as in Junit:

inserir a descrição da imagem aqui

Once installed, the tests can be run in the same way as Junit, but instead of clicking "Run as Junit Test", you will click "Run as Testng Test". I don’t know what the configuration is like in other Ides.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.