Como TDD uma implementação de lista em Java
1. Visão geral
Neste tutorial, vamos percorrer uma implementação deList personalizada usando o processo Test-Driven Development (TDD)
Esta não é uma introdução ao TDD, então presumimos que você já tenha alguma ideia básica do que isso significa e o interesse sustentado em melhorar.
Simplificando,TDD is a design tool, enabling us to drive our implementation with the help of tests.
Um aviso rápido - não estamos nos concentrando em criar uma implementação eficiente aqui - apenas usando isso como uma desculpa para mostrar as práticas de TDD.
2. Começando
Primeiro, vamos definir o esqueleto de nossa classe:
public class CustomList implements List {
private Object[] internal = {};
// empty implementation methods
}
A classeCustomList implementa a interfaceList, portanto, deve conter implementações para todos os métodos declarados nessa interface.
Para começar, podemos apenas fornecer corpos vazios para esses métodos. Se um método tiver um tipo de retorno, podemos retornar um valor arbitrário desse tipo, comonull paraObject oufalse paraboolean.
Por uma questão de brevidade, omitiremos métodos opcionais, juntamente com alguns métodos obrigatórios que não são usados com frequência.
3. Ciclos TDD
Desenvolver nossa implementação com TDD significa que precisamoscreate test cases first, definindo assim os requisitos para nossa implementação. Apenasthen we’ll create or fix the implementation code para fazer esses testes passarem.
De uma maneira muito simplificada, as três principais etapas de cada ciclo são:
-
Writing tests – define requisitos na forma de testes
-
Implementing features – faz os testes passarem sem focar muito na elegância do código
-
Refactoring – melhora o código para torná-lo mais fácil de ler e manter enquanto passa nos testes
Percorreremos esses ciclos TDD para alguns métodos da interfaceList, começando com os mais simples.
4. O MétodoisEmpty
O métodoisEmpty é provavelmente o método mais direto definido na interfaceList. Aqui está nossa implementação inicial:
@Override
public boolean isEmpty() {
return false;
}
Essa definição de método inicial é suficiente para compilar. O corpo desse método será "forçado" a melhorar quando mais e mais testes forem adicionados.
4.1. O primeiro ciclo
Vamos escrever o primeiro caso de teste que garante que o métodoisEmpty retornetrue quando a lista não contiver nenhum elemento:
@Test
public void givenEmptyList_whenIsEmpty_thenTrueIsReturned() {
List
O teste fornecido falha, pois o métodoisEmpty sempre retornafalse. Podemos fazê-lo passar apenas lançando o valor de retorno:
@Override
public boolean isEmpty() {
return true;
}
4.2. O Segundo Ciclo
Para confirmar que o métodoisEmpty retornafalse quando a lista não está vazia, precisamos adicionar pelo menos um elemento:
@Test
public void givenNonEmptyList_whenIsEmpty_thenFalseIsReturned() {
List
Uma implementação do métodoadd agora é necessária. Aqui está o métodoadd com o qual começamos:
@Override
public boolean add(E element) {
return false;
}
A implementação deste método não funciona, pois nenhuma alteração é feita na estrutura de dados interna da lista. Vamos atualizá-lo para armazenar o elemento adicionado:
@Override
public boolean add(E element) {
internal = new Object[] { element };
return false;
}
Nosso teste ainda falha, pois o métodoisEmpty não foi aprimorado. Vamos fazer isso:
@Override
public boolean isEmpty() {
if (internal.length != 0) {
return false;
} else {
return true;
}
}
O teste não vazio passa neste momento.
4.3. Reestruturação
Ambos os casos de teste que vimos passarem até agora, mas o código do métodoisEmpty poderia ser mais elegante.
Vamos refatorá-lo:
@Override
public boolean isEmpty() {
return internal.length == 0;
}
Podemos ver que os testes passam, então a implementação do métodoisEmpty está concluída agora.
5. O Métodosize
Esta é a nossa implementação inicial do métodosize permitindo que a classeCustomList compile:
@Override
public int size() {
return 0;
}
5.1. O primeiro ciclo
Usando o métodoadd existente, podemos criar o primeiro teste para o métodosize, verificando se o tamanho de uma lista com um único elemento é1:
@Test
public void givenListWithAnElement_whenSize_thenOneIsReturned() {
List
O teste falha porque o métodosize está retornando0. Vamos fazer isso passar com uma nova implementação:
@Override
public int size() {
if (isEmpty()) {
return 0;
} else {
return internal.length;
}
}
5.2. Reestruturação
Podemos refatorar o métodosize para torná-lo mais elegante:
@Override
public int size() {
return internal.length;
}
A implementação deste método está concluída.
6. O Métodoget
Aqui está a implementação inicial deget:
@Override
public E get(int index) {
return null;
}
6.1. O primeiro ciclo
Vamos dar uma olhada no primeiro teste para esse método, que verifica o valor do único elemento na lista:
@Test
public void givenListWithAnElement_whenGet_thenThatElementIsReturned() {
List
O teste será aprovado com esta implementação do métodoget:
@Override
public E get(int index) {
return (E) internal[0];
}
6.2. Melhoria
Normalmente, adicionamos mais testes antes de fazer melhorias adicionais no métodoget. Esses testes precisariam de outros métodos da interfaceList para implementar asserções adequadas.
No entanto, esses outros métodos não estão maduros o suficiente, ainda, então quebramos o ciclo TDD e criamos uma implementação completa do métodoget, que é, na verdade, não muito difícil.
É fácil imaginar queget deve extrair um elemento da matrizinternal no local especificado usando o parâmetroindex:
@Override
public E get(int index) {
return (E) internal[index];
}
7. O Métodoadd
Este é o métodoadd que criamos na seção 4:
@Override
public boolean add(E element) {
internal = new Object[] { element };
return false;
}
7.1. O primeiro ciclo
A seguir está um teste simples que verifica o valor de retorno deadd:
@Test
public void givenEmptyList_whenElementIsAdded_thenGetReturnsThatElement() {
List
Devemos modificar o métodoadd para retornartrue para o teste passar:
@Override
public boolean add(E element) {
internal = new Object[] { element };
return true;
}
Embora o teste seja aprovado, o métodoadd ainda não cobre todos os casos. Se adicionarmos um segundo elemento à lista, o elemento existente será perdido.
7.2. O Segundo Ciclo
Aqui está outro teste adicionando o requisito de que a lista pode conter mais de um elemento:
@Test
public void givenListWithAnElement_whenAnotherIsAdded_thenGetReturnsBoth() {
List
O teste irá falhar porque o métodoadd em sua forma atual não permite que mais de um elemento seja adicionado.
Vamos mudar o código de implementação:
@Override
public boolean add(E element) {
Object[] temp = Arrays.copyOf(internal, internal.length + 1);
temp[internal.length] = element;
internal = temp;
return true;
}
A implementação é elegante o suficiente, portanto, não precisamos refatorá-la.
8. Conclusão
Este tutorial passou por um processo de desenvolvimento orientado a teste para criar parte de uma implementaçãoList customizada. Usando o TDD, podemos implementar os requisitos passo a passo, mantendo a cobertura do teste em um nível muito alto. Além disso, a implementação é garantida para ser testada, pois foi criada para fazer os testes passarem.
Observe que a classe personalizada criada neste artigo é usada apenas para fins de demonstração e não deve ser adotada em um projeto do mundo real.
O código-fonte completo para este tutorial, incluindo os métodos de teste e implementação deixados de fora por uma questão de brevidade, pode ser encontradoover on GitHub.