Como escrever testes de unidade de manutenção, e não quebradiços, para uma GUI?

16

Tentei escrever testes de unidade de interface do usuário para meus aplicativos de GUI e enfrento o problema de que, embora funcionem bem quando os escrevo inicialmente, eles se tornam quebradiços e quebram sempre que o design é alterado (ou seja, com frequência). Estou lutando para encontrar um conjunto de diretrizes que me levem a realizar testes de unidade sustentável para a GUI.

Por enquanto, uma coisa que eu descobri é que os testes dizendo "esse componente deve mostrar seus dados de entrada em algum lugar" são bons (e isso é muito fácil com o HTML). Testes que verificam um estado específico de uma parte específica do componente geralmente são frágeis. Testes como click-click-click-expect, que tentam seguir o comportamento do usuário e a lógica comercial subjacente (que é a parte mais importante) geralmente acabam quebradiços. Como escrevo bons testes?


Para ser mais preciso, eu gostaria de conhecer alguns padrões sobre o que eu poderia testar na minha interface do usuário, não exatamente como testá-lo. Convenções de nomenclatura e identificadores fixos são bons, mas não resolvem o problema principal, ou seja, as GUIs mudam muito. Eu gostaria de testar os comportamentos que provavelmente não mudarão. Como encontrar a coisa certa para testar?

mik01aj
fonte
1
@ MichaelDurrant: Você editou a pergunta como sendo sobre testes de interface do usuário em geral, enquanto eu originalmente perguntava sobre testes de unidade. Acho os testes de integração mais difíceis de manter e prefiro os testes de unidade sobre eles, sempre que possível.
mik01aj
2
Eu acho que isso é parte do problema, no entanto, fundamentalmente, você não pode realmente testar nenhuma interface apenas por testes unitários, pois a razão de ser deles é a interface com alguma coisa. As GUIs não são diferentes nesse aspecto.
jk.
m01, você pode mudar de volta. Penso nos testes de interface do usuário como sendo geralmente testes integrados. Os testes tendem a depender dos dados de sementes e acessórios presentes e da interface do usuário trabalhando com eles. Se você tiver testes de interface do usuário verdadeiros que não dependem de outros dados excelentes. No entanto, eu achei isso relativamente raro.
Michael Durrant
2
relacionados, mas não uma duplicata vez que este é sobre testes de GUI: programmers.stackexchange.com/questions/109703/...
k3b

Respostas:

3

Um problema comum com os testes da GUI ... A principal razão pela qual esses testes são considerados frágeis é porque eles não podem sobreviver a uma mudança na GUI que não é uma alteração nos requisitos . Você deve estruturar seu código de teste de forma que uma alteração na GUI seja isolada em um único local nos testes.

Como exemplo, considere um teste com a seguinte redação:

Quando o usuário digita '999' no campo número de telefone e clica no botão Salvar, o pop-up da mensagem de erro deve dizer 'número de telefone inválido'.

Aqui há bastante espaço para que esse teste seja interrompido quando você refaz a interface, mesmo que o requisito para a validação permaneça.

Agora, vamos colocar isso em um pequeno texto alternativo:

Quando o usuário digita '999' como um número de telefone e salva a página de perfil, ele deve mostrar um erro que diz 'número de telefone inválido'

O teste é o mesmo, os requisitos são os mesmos, mas esse tipo de teste sobreviverá a uma reforma na sua interface do usuário. Você precisará alterar o código, obviamente, mas o código será isolado. Mesmo que você tenha dez ou vinte testes para a sua página de perfil e mova sua lógica de validação que exibe mensagens de erro de alertas javascript para jquery-pop-ups, por exemplo, você só precisa alterar a parte do teste que verifica as mensagens de erro.

JDT
fonte
4

Esse é um problema comum. Eu prestaria atenção a:

  • Como você nomeia elementos

    Use css id ou classe para identificar elementos. Favor usar o ID do CSS quando o objeto for único. Considere a estrutura que você está usando, por exemplo, com o Ruby on Rails, o nameatributo é atribuído automaticamente e pode (não intuitivamente) ser melhor do que usar o id ou classe css

  • Como você identifica elementos.

    Evite identificadores posicionais como table/tr/td/tdem favor de formas comotd[id="main_vehicle" ou td[class='alternates']. Considere usar atributos de dados quando apropriado. Ainda melhor, tente evitar as tags de layout, como <td>as anteriores, para adicionar um span e usá-lo, por exemplo, <span id="main_vehicle">ou um seletor de curinga, como *[id="main_vehicle"]onde *agora pode ser um div, span, td, etc.

  • Usando atributos de dados específicos de teste que são usados ​​apenas para qa e teste.

  • Evite qualificação desnecessária para elementos. Você pode encontrar o seguinte:

    body.main div#vehicles > form#vehicle input#primary_vehicle_name

    No entanto, isso exige que o campo de entrada permaneça em um formulário com um ID exato do veículo e em uma página com uma carroceria que possua uma classe de main e uma div com um ID de veículos que tenha um filho imediato de um formulário com um ID de veículo. Qualquer alteração em qualquer estrutura e o teste é interrompido. Nesse caso, você pode achar que

    input#primary_vehicle_name

    é suficiente para identificar exclusivamente o elemento.

  • Evite testes que se refiram ao texto visível. O texto na página que é mostrado ao usuário geralmente muda com o tempo, à medida que o site é mantido e atualizado; portanto, use identificadores como id css e classe css ou atributos de dados. Elementos como form, inpute selectusados ​​em formulários, também são boas partes dos elementos de identificação, geralmente em combinação com ID ou classe, por exemplo, li.vehicleou input#first-vehicle Você também pode adicionar seus próprios identificadores, por exemplo<div data-vehicle='dodge'> . Dessa forma, você pode evitar o uso de IDs ou classes de elementos, que provavelmente serão alterados por desenvolvedores e designers. Na verdade, descobri com o tempo que é melhor trabalhar apenas com desenvolvedores e designers e chegar a um acordo sobre nomes e escopos. É difícil.

  • Como os dados fixos são mantidos.

    Semelhante à identificação de elementos reais, tente evitar o seletor codificado em linha, identificando valores em favor dos objetos de página - pequenos pedaços de texto que são mantidos em variáveis ​​ou métodos e, portanto, podem ser reutilizados e também mantidos centralmente. Exemplos de variáveis ​​javascript seguindo este padrão para valores codificados:

    storedVars["eqv_auto_year"] = "2015";
    storedVars["eqv_auto_make_1"] = "ALFA ROMEO";
    storedVars["eqv_auto_make_2"] = "HONDA";`  
    

    Mais informações sobre objetos de página no selenium wiki e selenium docs

  • Comunicação com desenvolvedores.

    Independentemente da abordagem técnica em termos de 'desenvolvedores fazem alterações e interrompem a automação do controle de qualidade', isso é um problema do fluxo de trabalho. Você precisa ter certeza de que: todos são da mesma equipe; o desenvolvedor executa os mesmos testes integrados; os padrões são acordados e seguidos pelos dois grupos; a definição de done inclui executar e possivelmente atualizar os testes da interface do usuário; desenvolvedores e testadores fazem par de planos de teste e participam da preparação do ticket (se estiver fazendo o Agile) e falam sobre o teste da interface do usuário como parte da preparação. Você deve garantir que qualquer abordagem e estratégia usada para nomear seja coordenada com os desenvolvedores de aplicativos. Se você não chegar à mesma página, entrará em conflito com a nomeação de objetos. Alguns exemplos de métodos de objeto de página que criei recentemente para um projeto ruby:

    def css_email_opt_in_true
      'auto_policy[email_opt_in][value=1]'
    end 
    
    def css_phone_opt_in
      '*[name="auto_policy[phone_opt_in]"]'
    end 
    
    def css_phone_opt_in_true
      'input[name=phone_opt_in][value=true]'
    end 
    
    def css_credit_rating
      'auto_policy[credit_rating]'
    end
    

    Aqui estão os mesmos objetos de página que as variáveis ​​javascript:

    storedVars["css_email_opt_in"] = "css=*[name='auto_policy[email_opt_in]']";
    storedVars["css_phone_opt_in"]="css=*[name='auto_policy[phone_opt_in]']";
    storedVars["css_phone_opt_in_true"]="css=input[name='phone_opt_in'][value=true]";
    storedVars["css_credit_rating"]="css=select[name='auto_policy[credit_rating]']";
    
Michael Durrant
fonte
2
Essas são dicas úteis, e na verdade eu já sigo a maioria delas. Meu problema era que eu escrevia testes para algum botão e, em seguida, esse botão é removido enquanto a mesma ação é manipulada de outro lugar. Ou o botão permanece lá, mas o rótulo muda e o botão também executa alguma ação adicional.
Mk01aj 19/10/2015
1
Ahh, isso é bom de saber. Sim para as coisas que eu iria incidir sobre o fluxo de trabalho e organizacional interação qa-desenvolvedor
Michael Durrant
1
Também estou postando informações para os outros que encontrarem sua pergunta e talvez não possuam todas as peças que você possui ou talvez nem conheça.
Michael Durrant
1
Expandi meu último ponto de bulllet com base nos seus comentários.
Michael Durrant
1
E atualizei as partes sobre nomear e identificar elementos e sobre não usar texto visível.
Michael Durrant
3

A razão pela qual as pessoas desenvolveram coisas como MVC, MVP e apresentador primeiro e padrões de design semelhantes foi separar a lógica de negócios da interface do usuário.

Obviamente, a parte da visualização só pode ser testada iniciando o programa e verificando o que mostra - em outras palavras, ela só pode ser testada em testes de aceitação.

Por outro lado, o teste da lógica de negócios pode ser feito em testes de unidade. E essa é a resposta para sua pergunta. Teste tudo no modelo e, se puder e quiser, também pode testar o código do controlador.


As GUIs mudam bastante

Isso só pode acontecer quando você muda os requisitos. Quando um requisito muda, não há como contorná-lo, exceto para modificar o código. Se você conseguir criar um bom design e arquitetura, a mudança não será propagada em muitos lugares.

BЈовић
fonte
2
Eu acho que é um bom argumento. Talvez eu esteja apenas fazendo o MVC errado :) Btw, acredito que a mudança de requisitos é inevitável quando você desenvolve uma plataforma da Web para muitos usuários. Você não sabe como seus usuários se comportarão até que eles comecem a usar a GUI.
mik01aj
2

Os testes de interação da GUI não devem ser mais ou menos frágeis do que qualquer outro tipo de teste. Isso é; se o seu aplicativo estiver mudando de alguma forma, os testes precisam ser atualizados para refletir isso.

Como comparação:

Teste de unidade

Original : validateEmail()deve lançar uma InvalidDataexceção. Qual é coberto corretamente no seu teste de unidade.

Mudança : validateEmail()deve lançar uma InvalidEmailexceção. Agora seu teste está incorreto, você o atualiza e tudo fica verde novamente.

Teste da GUI

Original : a inserção de um email inválido resultará em uma caixa de erro pop-up contendo "Dados inválidos digitados". Detectado corretamente pelos seus testes.

Alteração : a inserção de um email inválido resultará em um erro embutido contendo "Email inválido digitado". Agora seu teste está incorreto, você o atualiza e tudo fica verde novamente.


Lembre-se de que você está testando entradas e saídas - algum comportamento bem definido. Independentemente de se tratar de um teste de GUI ou de unidade ou de integração, etc.

Jess Telford
fonte