Como testar a classe abstrata em Java com JUnit?

87

Eu sou novo em testes de Java com JUnit. Tenho que trabalhar com Java e gostaria de usar testes de unidade.

Meu problema é: eu tenho uma classe abstrata com alguns métodos abstratos. Mas existem alguns métodos que não são abstratos. Como posso testar esta classe com JUnit? Código de exemplo (muito simples):

abstract class Car {

    public Car(int speed, int fuel) {
        this.speed = speed;
        this.fuel = fuel;
    }

    private int speed;
    private int fuel;

    abstract void drive();

    public int getSpeed() {
        return this.speed;
    }

    public int getFuel() {
        return this.fuel;
    }
}

Eu quero testar getSpeed() e getFuel()funções.

Uma questão semelhante a este problema é aqui , mas não está usando JUnit.

Na seção JUnit FAQ, encontrei este link , mas não entendo o que o autor quer dizer com este exemplo. O que essa linha de código significa?

public abstract Source getSource() ;
vasco
fonte
4
Consulte stackoverflow.com/questions/1087339/… para duas soluções usando Mockito.
ddso
Existe alguma vantagem em aprender outra estrutura para teste? O Mockito é apenas uma extensão do jUnit ou um projeto completamente diferente?
vasco
O Mockito não substitui o JUnit. Como outras estruturas de simulação, é usado além de uma estrutura de teste de unidade e ajuda a criar objetos de simulação para usar em seus casos de teste.
ddso
1
Linguagem agnóstica: stackoverflow.com/questions/243274/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

104

Se você não tem implementações concretas da classe e os métodos não são static que testá-los? Se você tiver uma classe concreta, então testará esses métodos como parte da API pública da classe concreta.

Eu sei o que você está pensando "Não quero testar esses métodos repetidamente, por isso criei a classe abstrata", mas meu contra-argumento é que o objetivo dos testes de unidade é permitir que os desenvolvedores façam alterações, execute os testes e analise os resultados. Parte dessas mudanças pode incluir a substituição dos métodos de sua classe abstrata, ambos protectede public, o que pode resultar em mudanças comportamentais fundamentais. Dependendo da natureza dessas alterações, isso pode afetar o modo como seu aplicativo é executado de maneiras inesperadas e possivelmente negativas. Se você tiver um bom conjunto de testes de unidade, os problemas decorrentes desses tipos de alterações devem ser aparentes no momento do desenvolvimento.

nsfyn55
fonte
17
100% de cobertura de código é um mito. Você deve ter testes exatamente suficientes para cobrir todas as suas hipóteses conhecidas sobre como seu aplicativo deve se comportar (de preferência escrito antes de escrever o código em Test Driven Development). Atualmente, estou trabalhando em uma equipe de TDD altamente funcional e temos apenas 63% de cobertura em nossa última construção, tudo escrito conforme desenvolvemos. Isso é bom? quem sabe ?, mas consideraria uma perda de tempo voltar e tentar aumentar ainda mais.
nsfyn55
3
certo. Alguns argumentariam que isso é uma violação do bom TDD. Imagine que você está em uma equipe. Você presume que o método é final e não coloca testes em nenhuma implementação concreta. Alguém remove o modificador e faz alterações que afetam todo um ramo da hierarquia de herança. Você não gostaria que sua suíte de testes captasse isso?
nsfyn55
31
Discordo. Quer você trabalhe em TDD ou não, o método concreto de sua classe abstrata contém código, portanto, eles devem ter testes (independentemente de haver subclasses ou não). Além disso, o teste de unidade em Java testa (normalmente) classes. Portanto, realmente não há lógica em testar métodos que não façam parte da classe, mas sim de sua superclasse. Seguindo essa lógica, não devemos testar nenhuma classe em Java, exceto para classes sem nenhuma subclasse. Com relação aos métodos sendo sobrescritos, é exatamente quando você adiciona um teste para verificar as alterações / adições ao teste da subclasse.
Ethanfar,
3
@ nsfyn55 E se os métodos concretos fossem final? Não vejo razão para testar o mesmo método várias vezes se a implementação não puder ser alterada
Dioxina,
3
Não deveríamos ter os testes como alvo a interface abstrata para que possamos executá-los para todas as implementações? Se não for possível, estaremos violando o de Liskov, que gostaríamos de conhecer e consertar. Somente se a implementação adicionar alguma funcionalidade estendida (compatível), devemos ter um teste de unidade específico para ela (e apenas para aquela funcionalidade extra).
tne
36

Crie uma classe concreta que herde a classe abstrata e teste as funções que a classe concreta herda da classe abstrata.

Kevin Bowersox
fonte
O que você faria no caso de ter 10 classes concretas estendendo a classe abstrata e cada uma dessas classes concretas implementaria apenas 1 método e digamos que outros 2 métodos são os mesmos para cada uma dessas classes, porque são implementados de forma abstrata classe? Meu caso é que não quero copiar e colar os testes da classe abstrata para cada subclasse.
scarface
12

Com a aula de exemplo que você postou, não parece fazer muito sentido testar getFuel()egetSpeed() uma vez que eles podem retornar apenas 0 (não há setters).

No entanto, presumindo que este foi apenas um exemplo simplificado para fins ilustrativos, e que você tem motivos legítimos para testar métodos na classe base abstrata (outros já apontaram as implicações), você pode configurar seu código de teste para que crie um código anônimo subclasse da classe base que apenas fornece implementações fictícias (não operacionais) para os métodos abstratos.

Por exemplo, no seu, TestCasevocê pode fazer o seguinte:

c = new Car() {
       void drive() { };
   };

Em seguida, teste o restante dos métodos, por exemplo:

public class CarTest extends TestCase
{
    private Car c;

    public void setUp()
    {
        c = new Car() {
            void drive() { };
        };
    }

    public void testGetFuel() 
    {
        assertEquals(c.getFuel(), 0);
    }

    [...]
}

(Este exemplo é baseado na sintaxe JUnit3. Para JUnit4, o código seria um pouco diferente, mas a ideia é a mesma.)

Grodriguez
fonte
Obrigado pela resposta. Sim, meu exemplo foi simplificado (e não tão bom). Depois de ler todas as respostas aqui, escrevi aula fictícia. Mas, conforme escreveu @ nsfyn55 em sua resposta, eu escrevo teste para cada descendente dessa classe abstrata.
vasco
9

Se você precisa de uma solução de qualquer maneira (por exemplo, porque você tem muitas implementações da classe abstrata e os testes sempre repetem os mesmos procedimentos), então você pode criar uma classe de teste abstrata com um método de fábrica abstrato que será executado pela implementação daquele classe de teste. Este exemplo funciona para mim com TestNG:

A classe de teste abstrata de Car:

abstract class CarTest {

// the factory method
abstract Car createCar(int speed, int fuel);

// all test methods need to make use of the factory method to create the instance of a car
@Test
public void testGetSpeed() {
    Car car = createCar(33, 44);
    assertEquals(car.getSpeed(), 33);
    ...

Implementação de Car

class ElectricCar extends Car {

    private final int batteryCapacity;

    public ElectricCar(int speed, int fuel, int batteryCapacity) {
        super(speed, fuel);
        this.batteryCapacity = batteryCapacity;
    }

    ...

Aula ElectricCarTestde teste de unidade da classe ElectricCar:

class ElectricCarTest extends CarTest {

    // implementation of the abstract factory method
    Car createCar(int speed, int fuel) {
        return new ElectricCar(speed, fuel, 0);
    }

    // here you cann add specific test methods
    ...
thomas.mc.work
fonte
5

Você poderia fazer algo assim

public abstract MyAbstractClass {

    @Autowire
    private MyMock myMock;        

    protected String sayHello() {
            return myMock.getHello() + ", " + getName();
    }

    public abstract String getName();
}

// this is your JUnit test
public class MyAbstractClassTest extends MyAbstractClass {

    @Mock
    private MyMock myMock;

    @InjectMocks
    private MyAbstractClass thiz = this;

    private String myName = null;

    @Override
    public String getName() {
        return myName;
    }

    @Test
    public void testSayHello() {
        myName = "Johnny"
        when(myMock.getHello()).thenReturn("Hello");
        String result = sayHello();
        assertEquals("Hello, Johnny", result);
    }
}
iil
fonte
4

Eu criaria uma classe interna jUnit que herda da classe abstrata. Isso pode ser instanciado e ter acesso a todos os métodos definidos na classe abstrata.

public class AbstractClassTest {
   public void testMethod() {
   ...
   }
}


class ConcreteClass extends AbstractClass {

}
marting
fonte
3
Este é um conselho excelente. No entanto, pode ser melhorado fornecendo um exemplo. Talvez um exemplo da aula que você está descrevendo.
SDJMcHattie
2

Você pode instanciar uma classe anônima e, em seguida, testar essa classe.

public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    private MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.myDependencyService = new MyDependencyService();
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {    
            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Lembre-se de que a visibilidade deve ser protectedpara a propriedade myDependencyServiceda classe abstrata ClassUnderTest.

Você também pode combinar essa abordagem perfeitamente com o Mockito. Veja aqui .

Samuel
fonte
2

Minha maneira de testar isso é bastante simples, dentro de cada um abstractUnitTest.java. Eu simplesmente crio uma classe no abstractUnitTest.java que estende a classe abstrata. E teste dessa forma.

Haomin
fonte
0

Você não pode testar uma classe abstrata inteira. Nesse caso, você tem métodos abstratos, isso significa que eles devem ser implementados por classes que estendem a classe abstrata dada.

Nessa classe o programador tem que escrever o código-fonte que é dedicado à lógica dele.

Em outras palavras, não há sentido em testar uma classe abstrata porque você não é capaz de verificar o comportamento final dela.

Se você tiver uma funcionalidade principal não relacionada a métodos abstratos em alguma classe abstrata, apenas crie outra classe onde o método abstrato irá lançar alguma exceção.

Damian Leszczyński - Vash
fonte
0

Como opção, você pode criar uma classe de teste abstrata cobrindo a lógica dentro da classe abstrata e estendê-la para cada teste de subclasse. Para que desta forma você possa garantir que essa lógica será testada para cada criança separadamente.

Andrew Taran
fonte