Como escrever testes que fazem sentido para o software de visualização?

8

Eu tenho um software bastante grande que pega certos tipos de arquivos e os visualiza / cria uma série de botões para manipulação da imagem plotada. Sinto que estou encontrando bugs / partes de código que na verdade não funcionam uma vez por semana, mas estou tendo dificuldades para entender como posso escrever testes para este software?

Entendo como os testes são importantes para projetos como bibliotecas e APIs, você simplesmente escreve testes que usam essas funções.

Mas e o software de visualização? Parece exigir uma abordagem diferente devido aos elementos visuais envolvidos.

Preciso escrever um programa ou equipamento de teste que execute e chame manualmente todas as operações, desde que eu possa usar os dados?

Qual abordagem devo usar para começar a escrever testes para validar que corrigi os bugs e para me alertar se o código quebrar novamente?


Há uma pergunta relacionada, mas não duplicada, a respeito de quando você deve fazer o teste de unidade. Como estou descobrindo bugs, quero escrever testes para ajudar a impedir que o software volte novamente.

Need4Sleep
fonte

Respostas:

8

Existem algumas coisas que você pode fazer para facilitar o teste de software como esse. Primeiro, tente abstrair o máximo possível em camadas que não são visuais. Isso permitirá que você escreva testes de unidade padrão nessas camadas inferiores. Por exemplo, se você possui um botão que executa um determinado cálculo, verifique se há uma maneira de realizar esse cálculo em um teste de unidade com uma chamada de função regular.

A outra sugestão para testar programas com gráficos pesados ​​é criar alguma saída que um testador possa verificar manualmente com facilidade. Um exemplo de minecraft é:

minecraft test output

Também trabalhei em projetos que tiveram vários testes que renderizaram algo na tela e pedi ao testador que verifique manualmente se corresponde à descrição. Isso garante que você não esqueça os casos de teste posteriormente.

O teste geralmente é extremamente difícil se você não projetou originalmente com o teste em mente. Apenas trabalhe para testar seu código mais frágil primeiro. Quando você encontrar um bug, faça um teste que irá capturá-lo se acontecer novamente. Isso muitas vezes solicita a criação de testes relacionados enquanto você faz isso.

Karl Bielefeldt
fonte
2
+1 e se os dados de teste torna visual estática, verificar manualmente o resultado, pela primeira vez, em seguida, salvar a imagem para comparação programática depois
Steven A. Lowe
6

Tudo tem uma interface. Quando visto meu chapéu de teste, uso uma visão de mundo específica para escrever um teste:

  • Se algo existe, pode ser medido.
  • Se não puder ser medido, não importa. Se isso importa, eu ainda não encontrei uma maneira de medir isso ainda.
  • Os requisitos prescrevem propriedades mensuráveis ​​ou são inúteis.
  • Um sistema atende a um requisito quando faz a transição de um estado não esperado para o estado esperado prescrito pelo requisito.
  • Um sistema consiste em componentes em interação, que podem ser subsistemas. Um sistema está correto quando todos os componentes estão corretos e a interação entre os componentes está correta.

No seu caso, seu sistema possui três partes principais:

  • algum tipo de dados ou imagens, que podem ser inicializados a partir de arquivos
  • um mecanismo para exibir os dados
  • um mecanismo para modificar os dados

Aliás, isso me parece muito com a arquitetura original do Model-View-Controller. Idealmente, esses três elementos exibem acoplamentos frouxos - ou seja, você define limites claros entre eles com interfaces bem definidas (e, portanto, bem testáveis).

Uma interação complexa com o software pode ser traduzida em pequenas etapas que podem ser formuladas em termos dos elementos do sistema que estamos testando. Por exemplo:

Carrego um arquivo com alguns dados. Ele exibe um gráfico. Quando arrasto um controle deslizante na interface do usuário, o gráfico fica instável.

Parece ser fácil testar manualmente e difícil testar automatizado. Mas vamos traduzir essa história para o nosso sistema:

  • A interface do usuário fornece um mecanismo para abrir um arquivo: o controlador está correto.
  • Quando abro um arquivo, o Controller emite um comando apropriado para o Model: a interação Controller-Model está correta.
  • Dado um arquivo de teste, o modelo analisa isso na estrutura de dados esperada: o modelo está correto.
  • Dada uma estrutura de dados de teste, o View renderiza a saída esperada: o View está correto. Algumas estruturas de dados de teste serão gráficos normais, outras serão gráficos instáveis.
  • A interação View – Model está correta
  • A interface do usuário fornece um controle deslizante para deixar o gráfico instável: o controlador está correto.
  • Quando o controle deslizante é definido com um valor específico, o Controlador emite o comando esperado para o Modelo: a interação Controlador-Modelo está correta.
  • Ao receber um comando de teste referente à oscilação, o Modelo transforma uma estrutura de dados de teste na estrutura de dados do resultado esperado.

Agrupados por componente, terminamos com as seguintes propriedades para testar:

  • Modelo:
    • analisa arquivos
    • responde ao comando de abertura de arquivo
    • fornece acesso aos dados
    • responde ao comando vacilante
  • Visão:
    • renderiza dados
  • Controlador:
    • fornece fluxo de trabalho aberto de arquivo
    • emite o comando abrir arquivo
    • fornece fluxo de trabalho instável
    • emite comando instável
  • todo sistema:
    • a conexão entre os componentes está correta.

Se não decompormos o problema do teste em subtestes menores, o teste se tornará realmente difícil e realmente frágil. A história acima também pode ser implementada como "quando eu carrego um arquivo específico e defino o controle deslizante para um valor específico, uma imagem específica é renderizada". Isso é frágil, pois quebra quando qualquer elemento do sistema é alterado.

  • Ele é interrompido quando altero os controles de oscilação (por exemplo, alças no gráfico em vez de um controle deslizante no painel de controle).
  • Ele é interrompido quando altero o formato de saída (por exemplo, o bitmap renderizado é diferente porque alterei a cor padrão do gráfico ou porque adicionei anti-aliasing para tornar o gráfico mais suave. Observe isso nos dois casos).

Os testes granulares também têm a grande vantagem de me permitir evoluir o sistema sem medo de quebrar nenhum recurso. Como todo o comportamento necessário é medido por um conjunto de testes completo, os testes serão avisados ​​caso algo pare. Como são granulares, eles me indicarão a área problemática. Por exemplo, se eu acidentalmente alterar a interface de qualquer componente, apenas os testes dessa interface falharão e nenhum outro teste que use indiretamente essa interface.

Se o teste é fácil, isso requer um design adequado. Por exemplo, é problemático quando conecto os componentes em um sistema: se eu quiser testar a interação de um componente com outros componentes em um sistema, preciso substituir esses outros componentes por stubs de teste que permitam registrar, verificar, e coreografar essa interação. Em outras palavras, preciso de algum mecanismo de injeção de dependência e as dependências estáticas devem ser evitadas. Ao testar uma interface do usuário, é uma grande ajuda quando essa interface do usuário é programável.


Naturalmente, a maior parte disso é apenas uma fantasia de um mundo ideal, onde tudo é dissociado e facilmente testável e os unicórnios voadores espalham amor e paz ;-) Embora qualquer coisa seja fundamentalmente testável, muitas vezes é proibitivamente difícil fazê-lo, e é melhor usos do seu tempo. No entanto, os sistemas podem ser projetados para serem testados e, normalmente, até os sistemas independentes de teste apresentam APIs internas ou contratos que podem ser testados (caso contrário, aposto que sua arquitetura é uma porcaria e você escreveu uma grande bola de barro). Na minha experiência, mesmo pequenas quantidades de teste (automatizado) afetam significativamente a qualidade.

amon
fonte
2

Comece com um arquivo conhecido que produz uma imagem esperada. Verifique cada pixel. Cada um deve ter um valor esperado para um arquivo de teste conhecido e feito à mão. Você deve ter uma imagem de saída esperada para compará-la. Qualquer coisa que esteja "desativada" significa um erro no seu código.

Expanda seu arquivo de teste para que a imagem de saída seja alterada e atinja todos os recursos do seu software.

O script seria útil para esse tipo de teste de caixa preta. Um script simples que executa a versão mais recente do seu software para a entrada conhecida e a saída esperada.

O Teste de Unidade, por outro lado, deve ser um teste de caixa branca, onde você pega o menor pedaço possível de software, normalmente uma função ou qualquer outra coisa, e vê se ele se comporta como o esperado. Você pode ver que cor de pixel ele retorna ou o que for. Nesse caso, seu código se comporta como uma biblioteca, com APIs para todas as outras seções do seu código.

Se tudo estiver dobrado em um arquivo .c com todas as funcionalidades incluídas main(), você terá problemas maiores do que como testar.

Philip
fonte