Confusão de JUnit: use 'extends TestCase' ou '@Test'?

152

Eu achei o uso adequado (ou pelo menos a documentação) do JUnit muito confuso. Essa pergunta serve como referência futura e como uma pergunta real.

Se eu entendi corretamente, há duas abordagens principais para criar e executar um teste JUnit:

Abordagem A (JUnit 3-estilo): criar uma classe que se estende TestCase, e comece a métodos de ensaio com a palavra test. Ao executar a classe como um Teste JUnit (no Eclipse), todos os métodos iniciados com a palavra testsão executados automaticamente.

import junit.framework.TestCase;

public class DummyTestA extends TestCase {

    public void testSum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Abordagem B (estilo JUnit 4): crie uma classe 'normal' e adicione uma @Testanotação ao método. Note que você não tem que começar o método com a palavra test.

import org.junit.*;
import static org.junit.Assert.*;

public class DummyTestB {

    @Test
    public void Sum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Misturar os dois parece não ser uma boa ideia, veja, por exemplo, esta pergunta sobre o stackoverflow :

Agora, minhas perguntas:

  1. Qual é a abordagem preferida ou quando você usaria uma em vez da outra?
  2. A abordagem B permite testar exceções estendendo a anotação @Test como em @Test(expected = ArithmeticException.class). Mas como você testa exceções ao usar a abordagem A?
  3. Ao usar a abordagem A, você pode agrupar várias classes de teste em um conjunto de testes como este:

    TestSuite suite = new TestSuite("All tests");
    suite.addTestSuite(DummyTestA.class);
    suite.addTestSuite(DummyTestAbis.class);

    Mas isso não pode ser usado com a abordagem B (uma vez que cada classe de teste deve subclassificar TestCase). Qual é a maneira correta de agrupar testes para a abordagem B?

Editar: adicionei as versões do JUnit às duas abordagens

Rabarberski
fonte
Eu já vi extends TestCasee, em seguida, cada teste também anotou @Testapenas para confundir as coisas. :)
EM-Creations

Respostas:

119

A distinção é bastante fácil:

  • estender TestCaseé a maneira como os testes de unidade foram escritos no JUnit 3 (é claro que ainda é suportado no JUnit 4)
  • usar a @Testanotação é a maneira apresentada pelo JUnit 4

Geralmente, você deve escolher o caminho da anotação, a menos que seja necessária compatibilidade com o JUnit 3 (e / ou uma versão Java anterior ao Java 5). A nova maneira tem várias vantagens:

Para testar as exceções esperadas em uma JUnit 3, TestCaseé necessário tornar o texto explícito.

public void testMyException() {
  try {
    objectUnderTest.myMethod(EVIL_ARGUMENT);
    fail("myMethod did not throw an Exception!");
  } catch (MyException e) {
    // ok!
    // check for properties of exception here, if desired
  }
}

O JUnit 5 introduziu mais uma alteração na API, mas ainda usa anotações. A nova @Testanotação é org.junit.jupiter.api.Test(a "antiga" JUnit 4 era org.junit.Test), mas funciona praticamente da mesma forma que a JUnit 4.

Joachim Sauer
fonte
Resposta útil e completa, mas não entendo completamente "verificar a mensagem de exceção". Verificar uma cadeia codificada será um pesadelo de manutenção. Você deve ter significado "verificar as propriedades do seu tipo de exceção específico".
thSoft
3
@thSoft: nem sempre é usado, mas ocasionalmente quero garantir que o método de exceção mencione o campo incorreto, por exemplo. Então um simples assertTrue(e.getMessage().contains("foo"))pode ser útil.
Joachim Sauer
1
Mesmo no JUnit4, esse é um idioma importante quando você precisa verificar a mensagem ou alguma outra propriedade da exceção (como a causa). O expectedmétodo verifica apenas o tipo.
Yishai
@ Yishai: isso é verdade, mas na maioria das vezes já estou contente se o método lançar o tipo correto de exceção em entradas problemáticas.
Joachim Sauer
Por esse motivo, o JUnit 5 fez uma alteração inédita no teste de exceção. assertThrows () é fantástico :-)
Marcus K.
25

Eu tenho uma preferência pelo JUnit 4 (abordagem de anotação) porque acho mais flexível.

Se você deseja construir o conjunto de testes no JUnit 4, é necessário criar uma classe agrupando todos os testes como este:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;


@RunWith(Suite.class)
@SuiteClasses({
    Test1.class,
    Test2.class,
    Test3.class,
    Test4.class
})public class TestSuite
{
 /* empty class */
}
Grimmy
fonte
15

Há uma parte não respondida em sua pergunta, e é "Qual é a maneira correta de agrupar testes para a abordagem B?"

A resposta oficial é que você anota uma classe com um @RunWith (Suite.class) e, em seguida, usa a anotação @ Suite.SuiteClasses para listar as classes. É assim que os desenvolvedores do JUnit o fazem (listando todas as classes em um conjunto manualmente). De muitas maneiras, essa abordagem é uma melhoria, pois é trivial e intuitivo adicionar comportamentos antes e depois do conjunto (basta adicionar os métodos @BeforeClass e @AfterClass à classe anotada com o @RunWith - muito melhor que o antigo TestFixture )

No entanto, há um retrocesso, pois as anotações não permitem criar dinamicamente a lista de classes, e contornar esse problema fica um pouco feio. Você precisa subclassificar a classe Suite e criar dinamicamente a matriz de classes na subclasse e passá-la ao construtor Suite, mas essa é uma solução incompleta, pois outras subclasses da Suite (como Categorias) não funcionam com ela e essencialmente não suporta coleção de classes de teste dinâmica.

Yishai
fonte
1
+1 para isso. Depois de iniciar uma tarefa para escrever uma solução dinâmica para adicionar testes a um TestSuite, tive que estender o TestCase em cada um dos meus testes. Por sua vez, isso quebrou os testes de unidade que funcionavam anteriormente, que usavam anotações JUnit4 para definir exceções esperadas. Minha busca por uma maneira de preencher dinamicamente um Conjunto de Testes me levou a esta discussão e, especificamente, a sua resposta, que eu acredito que é um dos poucos remanescentes razões desejável prosseguir com JUnit 3.
8bitjunkie
4

Você deve usar o JUnit 4. É melhor.

Muitas estruturas começaram a descontinuar o suporte ao JUnit 3.8.

Isto é da documentação de referência do Spring 3.0:

[Aviso] A hierarquia de classes do JUnit 3.8 herdada foi descontinuada

Em geral, você deve sempre tentar usar a versão estável mais recente de uma estrutura ao iniciar algo novo.

Espen
fonte
1
  1. A abordagem "preferida" seria usar anotações que foram introduzidas desde o dia 4 de junho. Elas facilitam muitas coisas (veja sua segunda pergunta)

  2. Você pode usar um bloco try / catch simples para isso:


public void testForException() {
    try {
        Integer.parseInt("just a string");
        fail("Exception should have been thrown");
    } catch (final Exception e) {
        // expected
    }
}
black666
fonte