Conceito: Tipos de Teste de Desenvolvedor
Esta diretriz descreve vários tipos de teste de desenvolvedor.
Relacionamentos
Elementos Relacionados
Descrição Principal

Esta diretriz descreve alguns tipos de teste. Para processar estes tipos de teste é necessário definir, e então executar, uma série de testes no código fonte. Um teste de desenvolvedor é um teste único que necessita ser executado.

É importante ter muitos testes automatizados, com scripts que possam ser lidos pelas pessoas, para ser possível implementar os casos de teste de desenvolvedor e os scripts que incluam as informações abaixo. Um script de teste são passos reais, podendo ser procedimentos escritos a serem seguidos ou o código fonte de um teste. Os scripts de teste de desenvolvedor são executados nos alvos de teste, podendo ser: uma unidade de código fonte ou uma parte mais complexa do sistema (tal como um componente) ou o próprio sistema como um todo para testar alguma questão de desenvolvedor tal como a integração.

Teste de Regressão

O teste de regressão é o ato de assegurar que as mudanças no código não afetaram a funcionalidade existente de forma adversa. É importante reconhecer que o desenvolvimento incremental faz com que o teste de regressão seja crítico. Sempre que você liberar uma aplicação, você deve assegurar que as funcionalidades pré-existentes continuem funcionando. O fato de você liberar aplicações com maior freqüência quando usa a abordagem incremental, significa que os testes de regressão tornam-se muito mais importantes. A execução dos testes de regressão é a primeira coisa que você deve pensar pelas seguintes razões:

  1. Você quer ter a capacidade de modificar o código e saber que é possível reexecutar os testes para ver se alguma parte foi danificada.
  2. Os usuários ficam muito irritados quando descobrem que aquilo que estava funcionando previamente não funciona mais.

O Teste de regressão é razoavelmente direto - é necessário executar somente os casos de teste existentes na nova versão do código. As ferramentas de teste de regressão ajudam imensamente porque são projetadas com base na execução dos testes de regressão. Entretanto, existem desafios em potencial no teste de regressão:

  • Quando a produção de código for alterada para melhoria ou refatoração, você necessitará retrabalhar os casos de teste existentes associados a esse código.
  • Se as atualizações afetarem somente um componente do sistema, será necessário que você execute somente os casos de teste relacionados a este componente. Embora esta abordagem seja um pouco arriscada, porque as mudanças podem gerar um impacto maior do que você espera, ela ajuda a reduzir o tempo e o custo dos testes de regressão.
  • Quanto mais artefatos que não sejam de código você mantiver, maior será o esforço para executar testes de regressão no seu trabalho e conseqüentemente maior o risco para o projeto porque provavelmente haverá um aumento inadequado dos esforços de teste.

O teste de regressão é tão crítico ao sucesso do projeto quanto um desenvolvedor ágil. Muitos desenvolvedores de software usam a família xUnit de ferramentas de código aberto, tais como JUnit e VBUnit para testar seus códigos. A vantagem destas ferramentas é que elas implementam um framework de teste com o qual é possível executar o teste de regressão em todo o código fonte. As ferramentas comerciais de teste também são opções viáveis.

Técnicas Tradicionais de Teste de Código

Embora as tecnologias de objeto e processual sejam diferentes, diversos conceitos importantes de testes do mundo processual são válidos, independente da tecnologia utilizada. Estas técnicas tradicionais de teste são:

  • Teste de Caixa-Preta
  • Teste de Caixa-Clara
  • Teste de Valor Limítrofe
  • Teste de Cobertura/Caminho
  • Teste Exploratório

Teste de Caixa-Preta

O Teste de Caixa-Preta, também chamado de teste de interface, é uma técnica em que você cria casos de teste baseado somente na funcionalidade prevista de um método, de uma classe ou de uma aplicação sem nenhum conhecimento de sua implementação. Uma forma de definir o teste de caixa-preta é que dada a entrada A você deve obter o resultados previsto B.

O objetivo do teste de caixa-preta é assegurar que o sistema pode fazer o que deve ser feito, mas não como ele faz. Por exemplo, se você invocar a função differenceInDays(junho 30 2006, julho 3 2006) o resultado previsto deve ser três. A criação de testes de caixa-preta é normalmente orientada pelos requisitos do sistema. A idéia básica é olhar para o requisito e perguntar o que deve ser feito para mostrar que o requisito foi atendido.

A principal vantagem do teste de caixa-preta é que ele permite provar que a aplicação cumpre os requisitos definidos para ela. A principal desvantagem é que não mostra o que as partes internas do sistema fazem (implicando a necessidade do teste de caixa-branca).

Teste de Caixa-Branca

O Teste de Caixa-Branca, também chamado de teste de caixa-branca, é baseado na idéia de que o código do programa pode orientar o desenvolvimento dos casos de teste. O conceito básico é olhar para o código e criar os casos de teste que o exercitem. Por exemplo, suponha que você tem acesso ao código fonte da função differenceInDays(). Quando você olha para ele, você vê uma declaração IF testando se as duas datas são do mesmo ano. Se forem do mesmo ano então uma simples estratégia baseada em datas julianas será usada; se não, uma estratégia mais complexa deverá ser usada. Isto indica que é necessário pelo menos um teste que use datas do mesmo ano e um que use datas de anos diferentes. Olhando o código, é possível determinar novos casos de teste para exercitar os diferentes trajetos de sua lógica interna.

A principal vantagem deste conceito é que ele motiva a criação de testes que exercitem linhas específicas do código. As desvantagens são que ele não assegura que o código cumpre os requisitos reais (implicando a necessidade do teste de caixa-preta) e que o código do teste torna-se altamente dependente do código da aplicação.

Teste de Valor Limítrofe

Este teste é baseado no conhecimento necessário para testar o código assegurando que ele pode tratar situações incomuns e extremas. Por exemplo, os casos de teste de valor-limítrofe para a função differenceInDays() devem conter: a passagem da mesma data nos dois parâmetros, de duas datas extremamente diferentes, do último dia de um ano e o primeiro dia do ano seguinte e da data 29 de fevereiro de um ano bissexto. A idéia básica é procurar por limites definidos ou nas regras de negócio ou pelo senso comum, e criar os casos de teste para testar valores iguais ou próximos a eles.

A principal vantagem do teste de valor-limítrofe é que ele motiva a confirmação de que o código do programa pode tratar casos "incomuns" ou "extremos".

Teste de Cobertura e de Caminho

Dois conceitos "tradicionais" críticos são os testes de Cobertura e de Caminho O teste de cobertura é uma técnica em que se cria uma série de casos de teste projetados para testar todos os trajetos do código. Muitas vezes, o teste de cobertura é simplesmente uma coleção dos casos de teste de caixa-branca que, em conjunto, exercitam todas as linhas de código da aplicação pelo menos uma vez. O teste de caminho é um agrupamento de testes de cobertura que assegura que não somente todas as linhas de código foram testadas, como também foram testados todos os caminhos lógicos. A principal diferença ocorre quando existe um método com mais de um grupo de declarações CASE ou IFs aninhados: para determinar a quantidade de casos de teste com testes de cobertura, é necessário calcular a quantidade máxima de trajetos para cada variação das declarações CASE e IFs aninhados, e com testes de caminho, multiplicar pela quantidade de caminhos lógicos.

Testes de Unidade e de Integração

O teste de unidade é o teste de um item, tal como uma operação isoladamente. Por exemplo, os testes definidos acima para a função differenceInDays() são todos testes de unidade. O teste de integração, de outra forma, é o teste de uma coleção de itens para validar que eles trabalham juntos. No caso da biblioteca/classe de dados, será que todas as funções trabalham juntas? Talvez a função differenceInDays() tenha um efeito colateral que faça com que a função dayOfWeek() falhe se a differenceInDays() for chamada primeiramente. O teste de integração procura por problemas como este.

Testes Exploratórios

Os testes exploratórios são uma abordagem de testes que enfatiza as habilidades do testador em tomar decisões sobre o que será testado durante a execução do teste ao invés de seguir um roteiro previamente planejado. No entanto, o conceito não é novo. Glenford Myers em 1979, no seu livro chamado "The Art of Software Testing", já preconizava conceitos embrionários de testes de natureza exploratória, quando discutia técnicas de testes de suposição de erros (do inglês: "Error Guessing Testing").

Técnicas de Teste Orientadas a Objeto

Ao testar os sistemas construídos com a tecnologia de objeto é importante entender que o código fonte está composto de diversas construções tais como métodos (operações), classes e herança. Conseqüentemente é necessário ter técnicas de teste que reflitam o fato da existência dessas construções. Estas técnicas são:

  • Teste de Método
  • Teste de Classe
  • Teste de Integração de Classe
  • Teste de Regressão de Herança

Teste de Método

O teste de método é o ato de assegurar-se que os métodos(operações) trabalhem de acordo como o que foi definido. Nos testes processuais isto seria chamado de teste de função ou procedimento. Os testes de método devem:

  • Assegurar que os métodos get e set funcionam como desejado
  • Assegurar que cada método retorne valores apropriados, incluindo mensagens de erro e exceções
  • Validar os parâmetros que estão sendo passados para um método
  • Assegurar que um método faça o que ele deve fazer

A vantagem do teste de método é que ele assegura que os métodos funcionam perfeitamente de forma isolada, entretanto ele não ajuda a encontrar efeitos colaterais indesejados.

Teste de Classe

A principal finalidade do teste de classe é testar as classes isoladamente, sendo a combinação dos testes tradicionais de unidade e de integração. É um teste de unidade porque você está testando a classe e suas instancias como unidades isoladas, mas também é um teste de integração porque você precisa verifica se os métodos e os atributos da classe funcionam em conjunto. Uma condição que deve ser assumida quando os "testes de classe" forem escritos é que todas as outras classes do sistema funcionam perfeitamente. Os testes de classe devem:

  • Validar que os atributos de um objeto são corretamente inicializados
  • Validar as invariantes da classe

As principais vantagens do teste de classe são que ele valida se os métodos (operações) e os atributos (propriedades) de uma classe estão funcionando em conjunto e se a classe funciona isoladamente. Entretanto, ele não garante que uma classe funcione bem com o resto do sistema.

Teste de Integração de Classe

Também conhecido como teste de componente, esta técnica verifica se as classes do sistema, ou um componente do sistema, trabalham corretamente em conjunto. Os relacionamentos entre as classes podem ser usados para orientar o desenvolvimento de casos de teste de integração de classe. Os testes de integração de classe devem:

  • Validar que os objetos fazem o que os outros objetos esperam deles
  • Validar que os valores de retorno estão sendo gerados apropriadamente
  • Validar que as exceções e os erros estão sendo processados apropriadamente

A técnica ajuda a validar que todas as classes dentro de um componente, ou um sistema, trabalham em conjunto. Entretanto, pode ser difícil definir e desenvolver os casos de teste para executar completamente este nível de teste.

Teste de Regressão de Herança

Este teste é o ato de executar casos de teste definidos para uma superclasse na instancia da subclasse porque os erros não foram gerados pela subclasse. Os métodos novos são adicionados e os métodos existentes podem ser redefinidos pelas subclasses, métodos que acessam e alteram os valores dos atributos definidos na superclasse. É possível que uma subclasse mude o valor dos atributos de uma forma nunca desejada pela superclasse, ou pelo menos nunca esperada. O ponto principal é que se deve executar o conjunto de testes das superclasses ao testar uma subclasse.