Um componente é testado enviando entradas à interface, aguardando que o componente as processe e, em seguida,
verificando os resultados. Durante o processamento, um componente provavelmente usa outros componentes enviando-lhes
entradas e usando seus resultados:
Fig1: Testando um Componente implementado
Os outros componentes podem causar problemas nos testes:
-
Eles ainda não podem ser implementados.
-
Os componentes podem ter defeitos que impedem o funcionamento dos testes ou que fazem o usuário perder muito tempo
descobrindo que uma falha de teste não foi causada pelo componente.
-
Eles podem dificultar a execução dos testes quando você precisar. Se o componente é um banco de dados comercial, a
sua empresa talvez não tenha licenças suficientes para todos. Ou um dos componentes pode ser o hardware, que só
está disponível em horários programados em um laboratório separado.
-
Eles podem fazer os testes tão devagar, de maneira que os testes não sejam executados com freqüência suficiente.
Por exemplo, a inicialização do banco de dados pode levar cinco minutos por teste.
-
Isso pode dificultar a provocação dos componentes para produzir certos resultados. Por exemplo, você pode querer
que cada um dos métodos que grava em disco manipule erros de "disco cheio". Como ter certeza de que o disco está
cheio no momento exato em que o método é chamado?
Para evitar esses problemas, você pode optar por utilizar os componentes stub (também chamados
objetos-modelo). Os componentes stub comportam-se como os componentes reais, pelo menos nos valores que o
componente envia ao responder aos testes. Eles podem ir além disso: eles podem ser emuladores de finalidade
geral que buscam imitar fielmente a maioria ou todos os comportamentos do componente. Por exemplo, normalmente é uma
boa estratégia criar emuladores de software para hardware. Eles se comportam exatamente como o hardware, apenas são
mais lentos. Eles são úteis porque suportam melhor a depuração, há mais cópias disponíveis e podem ser usados antes de
o hardware estar pronto.
Fig2: Testando um Componente implementado por meio de stub de um componente do qual depende
Os stubs têm duas desvantagens.
-
Eles podem ser de construção cara. (Esse é o caso específico dos emuladores.) Como também são software, eles
precisam de manutenção.
-
Eles podem mascarar os erros. Por exemplo, suponha que o componente usa funções trigonométricas, mas nenhuma
biblioteca ainda está disponível. Os três casos de teste solicitam o seno de três ângulos: 10 graus, 45 graus e 90
graus. Você usa a calculadora para encontrar os valores corretos, em seguida, constrói um stub para o seno que
retorna, respectivamente, 0,173648178, 0,707106781 e 1,0. Tudo vai bem até você integrar o componente com a
biblioteca trigonométrica real, cuja função seno utiliza os argumentos em radianos e, portanto, retorna
-0,544021111, 0,850903525 e 0,893996664. Esse é um defeito no seu código que é descoberto depois e com mais esforço
do que você gostaria.
A menos que os stubs sejam construídos porque o componente real ainda não está disponível, você deve esperar mantê-los
na implementação passada. Os testes que eles suportam provavelmente serão importantes durante a manutenção do produto.
Os stubs, portanto, precisam ser escritos com padrões mais altos que o código descartado. Enquanto eles não precisarem
atender aos padrões do código do produto - por exemplo, a maioria não precisa de um conjunto de testes próprio - os
próximos desenvolvedores deverão mantê-los como componentes da mudança de produto. Se essa manutenção for muito
difícil, os stubs serão descartados, e o investimento neles será perdido.
Especialmente quando eles precisarem ser mantidos, os stubs alteram o design do componente. Por exemplo, suponha que o
seu componente usará um banco de dados para armazenar pares de chaves/valores persistentemente. Considere dois cenários
de design:
Cenário 1: O banco de dados é utilizado para teste e uso normal. A existência do banco de dados não precisa ser
escondida do componente. Você pode inicializá-lo com o nome do banco de dados:
public Component(
String databaseURL) { try { databaseConnection = DriverManager.getConnection(databaseURL); ... } catch (SQLException e) {...} }
Enquanto você não quiser que cada localização leia ou escreva um valor para construir uma instrução SQL, certamente você
terá alguns métodos que contenham SQL. Por exemplo, o código do componente que precisa de um valor pode chamar este método
do componente:
public String get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery(
"SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} }
Cenário 2: Para os testes, o banco de dados é substituído por um stub. O código do componente deve parecer o mesmo,
quer esteja sendo executado junto ao banco de dados real ou stub. Portanto, ele precisa ser codificado para usar os métodos
de uma interface abstrata:
interface KeyValuePairs { String
get(String key); void
put(String key, String value); }
Os testes implementariam KeyValuePairs com algo simples como uma tabela hash:
class FakeDatabase implements KeyValuePairs { Hashtable table = new Hashtable(); public String
get(String key) { return (String) table.get(key); } public void
put(String key, String value) { table.put(key, value); } }
Quando não estiver sendo utilizado em um teste, o componente deverá utilizar um
objeto adaptador que converte as chamadas para o KeyValuePairs em instruções SQL:
class DatabaseAdapter implements KeyValuePairs { private Connection databaseConnection; public DatabaseAdapter(String databaseURL) { try { databaseConnection = DriverManager.getConnection(databaseURL); ... } catch (SQLException e) {...} } public String
get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} } public void
put(String key, String value) { ... } }
O componente pode ter um único construtor para os testes e para outros clientes. Esse construtor utilizaria um objeto que
implementa KeyValuePairs. Ou pode fornecer essa interface apenas para testes, exigindo que clientes
simples do componente insiram o nome do banco de dados:
class Component {
public Component(String databaseURL) { this.valueStash = new DatabaseAdapter(databaseURL); } // For testing.
protected Component(KeyValuePairs valueStash) { this.valueStash = valueStash; } }
Portanto, do ponto de vista dos programadores do cliente, os dois cenários de design produzem a mesma API, mas um é mais
facilmente testável. (Observe que alguns testes podem usar o banco de dados real, e outros, o banco de dados stub.)
Para obter mais informações relacionadas aos Stubs, consulte o seguinte:
|