Menos de um casal. Outra forma de reduzir o número de testes

Qualquer QA conhece esse método para minimizar os casos de teste como Teste Pairwise - teste pairwise. O método é excelente, bastante simples e comprovado por muitas equipes. Mas e se depois de usá-lo sobrarem muitas caixas?



Foi exatamente isso que aconteceu no meu projeto, e hoje vou contar como é possível reduzir ainda mais o número de casos de teste sem perder qualidade.



imagem



Objeto de teste



Em primeiro lugar, vou falar um pouco sobre o produto. Na Tinkoff, nossa equipe desenvolveu blocos - são componentes React que consistem em implementação e configuração. A implementação é o próprio componente, que desenvolvemos e que o usuário vê no navegador. A configuração é JSON que define os parâmetros e o conteúdo deste objeto.



A principal tarefa dos blocos é serem bonitos e ter a mesma aparência para diferentes usuários. Ao mesmo tempo, o bloco pode mudar muito significativamente da configuração e do conteúdo.

Por exemplo, um bloco pode ser assim - sem fundo, com um botão e uma imagem à direita:



imagem



Ou assim - com um fundo, sem um botão e com uma imagem à esquerda:



imagem



Ou, em geral, assim - com um link em vez de um botão e sem uma lista no texto:



imagem



Todos os exemplos acima são o mesmo bloco que tem uma versão da configuração (uma estrutura JSON que este componente React específico pode manipular), mas seu conteúdo diferente.



O próprio circuito:



{
  components: {
    background: color,

    panel: {
      panelProps: {
        color: {
          style: ['outline', 'color', 'shadow', 'custom'],

          background: color
        },

        size: ['s', 'm', 'l'],

        imagePosition: ['left', 'right']
      },

      title: {
        text: text,

        size: ['s', 'l'],

        htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
      },

      description: {
        text: html,

        htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
      },

      image: {
        alt: text,

        title: text,

        image: {
          src: image,

          srcset: [{
            src: image,

            condition: ['2x', '3x']
          }],

          webpSrcset: [{
            src: image,

            condition: ['1x', '2x', '3x']
          }]
        },

        imageAlign: ['top', 'center', 'bottom']
      },

      button: {
        active: boolean,

        text: text,

        color: {
          style: ['primary', 'secondary', 'outline', 'outlineDark', 'outlineLight', 'textLink', 'custom'],

          backgroundColor: color
        },

        onClick: {
          action: ['goToLink', 'goToBlock', 'showBlock', 'crossSale', 'callFormEvent'],

          nofollow: boolean,

          url: url,

          targetBlank: boolean,

          title: text,

          noindex: boolean,

          guid: guid,

          guidList: [{
            guid: guid
          }],

          formId: guid,

          crossSaleUrl: url,

          eventName: text
        },

        htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
      },

      href: url
    }
  }
}


Neste caso, o bloco com a imagem à direita terá um valor components.panel.imagePosition = right. E o bloco com a foto à esquerda sim components.panel.imagePosition = left. Para um bloco com um botão - components.button.active = truee assim por diante. Espero que o princípio esteja claro. É assim que todos os parâmetros do bloco são definidos.



Casos de uma combinação de parâmetros



Neste artigo, não irei tocar nas questões de controle de versão do diagrama de blocos, regras de preenchimento de conteúdo ou de onde os dados vêm. Todos esses são tópicos separados que não afetam a compilação de um conjunto de casos de teste. A principal coisa a saber: temos muitos parâmetros que afetam nosso componente, e cada um deles pode assumir seu próprio conjunto de valores.



imagem



Para o exemplo acima, escolhi um bloco com uma configuração bastante simples. Mas, mesmo nele, verificar todas as combinações dos valores de todos os parâmetros levará um tempo inaceitavelmente longo, especialmente se você tiver que levar em consideração a compatibilidade entre navegadores. Normalmente, o teste de pares vem ao resgate aqui, ou o teste de pares. Toneladas de artigos já foram escritos sobre ele e até há treinamento . Se de repente você não perceber - certifique-se de ler.



Vamos estimar quantos casos de teste obteremos ao aplicá-lo. Temos mais de 25 parâmetros e alguns deles levam até 7 e 9 variantes de valores. Sim, você pode negligenciar algo: por exemplo, se você está verificando o layout, o guid não é importante para você. Mas, usando o teste Pairwise, você ainda terá mais de 80 casos de teste. E isso, como já escrevi, não é para o bloco mais complexo e sem levar em conta a compatibilidade entre navegadores. Agora temos mais de 150 blocos, e o número deles está crescendo, então não podemos pagar tantos casos se quisermos manter a velocidade de testes e lançamento de novas versões.



Casos de um parâmetro



O método de teste de pares é baseado na afirmação de que a maioria dos defeitos é causada pela interação de não mais do que dois fatores. Ou seja, a maioria dos bugs se manifesta em um valor de um parâmetro ou em uma combinação dos valores de dois parâmetros. Decidimos ignorar a segunda parte desta instrução e presumimos que a verificação de um parâmetro ainda encontraria a maioria dos bugs.



Em seguida, verifica-se que, para testar, precisamos verificar cada valor de cada parâmetro pelo menos uma vez. Mas, ao mesmo tempo, cada bloco carrega toda a configuração. Então, em cada novo caso, você pode verificar o máximo de valores ainda não verificados para minimizar o número de casos.



Vamos analisar o algoritmo para construir casos usando um exemplo simplificado. Vamos pegar o componente de botão de nosso esquema e compor casos de teste para ele:



 button: {
        active: boolean,

        text: text,

        color: {
          style: ['primary', 'secondary', 'outline', 'custom'],

          backgroundColor: color
        }


Para simplificar o exemplo, reduzi o comprimento da lista para button.color.style.



Etapa 1. Compor opções de conteúdo para cada campo



Tudo aqui é como um teste de pares: você precisa entender quais valores cada um dos campos pode assumir. Por exemplo, button.activeem nosso caso, pode haver apenas dois valores: trueou false. Teoricamente, mais opções podem surgir, por exemplo, a undefinedausência da própria chave.



Aqui, na minha opinião, é importante definir muito claramente os limites e a funcionalidade do seu sistema e não verificar coisas desnecessárias. Ou seja, se a verificação de chaves obrigatórias ou a validação de um valor for implementada em um sistema de terceiros, essa funcionalidade precisará ser verificada em um sistema de terceiros. E devemos usar apenas dados “corretos” como casos.



Em geral, o mesmo princípio é usado na pirâmide de teste. Se desejar, os testes de integração mais críticos podem ser adicionados a nós - por exemplo, para verificar o processamento de uma chave que não chegou. Mas deve haver um número mínimo de tais testes. Outra abordagem é a busca por testes exaustivos, o que, como todos sabem, é impossível.



Assim, identificamos as opções de conteúdo para cada campo e fizemos a seguinte tabela:



imagem



Esta tabela inclui cada classe de equivalência de cada parâmetro, mas apenas uma vez.



Estas são as classes de valor em nosso caso, as classes:



  • text_s - string curta;
  • text_m - string mais longa;
  • no_color - sem cor;
  • rnd_color é qualquer cor.


Etapa 2. Enriquecendo a tabela com dados



Já que cada bloco carrega a configuração completa, precisamos adicionar alguns dados relevantes às células em branco:



imagem



Agora, cada coluna é um caso.



Ao mesmo tempo, como nós mesmos selecionamos os dados ausentes, podemos gerar casos com base na prioridade. Por exemplo, se sabemos que um texto curto é usado em um botão com mais frequência do que um texto médio, vale a pena checá-lo com mais frequência.



No exemplo acima, você também pode prestar atenção aos casos "descartados" - casos em que algum parâmetro não é verificado de forma alguma, embora esteja presente na tabela. Neste caso, button.color.style: secondarynão será verificado a aparência, pois não importa o estilo do botão desativado.



Para evitar que os casos "descartados" levassem a bugs, costumávamos analisar os conjuntos de valores resultantes. A análise foi realizada uma vez ao gerar os casos de teste, e todos os casos “descartados” foram adicionados manualmente ao caso de teste final. Essa solução para o problema é um tanto desajeitada, mas barata (a menos, é claro, que você raramente altere a configuração dos objetos testados).



Uma solução mais geral é dividir todos os valores em dois grupos:



  1. valores inseguros (aqueles que podem levar à “perda” de processos);
  2. seguro (que não pode levar à "queda").


Cada valor inseguro é verificado em seu próprio caso de teste, você pode enriquecer o caso com quaisquer dados seguros. Para valores seguros, uma tabela é compilada de acordo com as instruções acima.



Etapa 3. Esclarecendo os valores



Agora, tudo o que resta é gerar valores concretos em vez de classes de equivalência.



imagem



Aqui, cada projeto terá que escolher suas próprias variantes de valores, com base nas características do objeto testado. Alguns valores são muito fáceis de gerar. Por exemplo, você pode simplesmente escolher qualquer cor para a maioria dos campos. Para alguns blocos, ao verificar a cor, você deve adicionar um gradiente, mas ele é movido para uma classe de equivalência separada.



Com texto, é um pouco mais complicado: se você gerar uma string a partir de caracteres aleatórios, hifenizações, listas, tags e espaços não separáveis ​​não serão testados. Geramos strings curtas e médias a partir de texto real, reduzindo-o ao número de caracteres desejado. E no texto longo verificamos:



  • tag html (qualquer um);
  • ligação;
  • lista não numerada.


Este conjunto de casos origina-se diretamente de nossa implementação de bloco. Por exemplo, todas as tags html são incluídas juntas, portanto, não há motivo para testar cada uma. Nesse caso, o link e a lista são verificados separadamente, pois possuem processamento visual separado (destaque em hover e tiroteios).



Acontece que para cada projeto você precisa compor seu próprio conjunto real de conteúdo com base na implementação do objeto testado.



Algoritmo



Claro, à primeira vista pode parecer que o algoritmo é complexo e não vale o esforço. Mas se você omitir todos os detalhes e exceções que tentei descrever em cada parágrafo acima, o resultado será muito simples.



Etapa 1. Adicionar todos os valores possíveis à tabela de parâmetros:



imagem



Etapa 2. Duplicar valores em células vazias:



imagem



Etapa 3. Transformar valores abstratos em valores concretos e obter casos:



imagem



Cada coluna da tabela é um caso.



Vantagens da abordagem



Este método de geração de casos de teste tem várias vantagens importantes.



imagem



Menos casos



Em primeiro lugar, há significativamente menos casos do que no teste de pares. Se pegarmos um exemplo simplificado com um botão, teremos 4 casos em vez de 8 no teste de pares.



Quanto mais parâmetros houver no objeto testado, mais significativa será a economia de caixa. Por exemplo, para o bloco completo apresentado no início do artigo, obtemos 11 casos, e com a ajuda de pares - 260.



O número de casos não aumenta com a complicação da funcionalidade



A segunda vantagem é que, quando novos parâmetros são levados em consideração durante o teste, o número de casos nem sempre aumenta.



Por exemplo, suponha que um parâmetro button.color.textColorcom as classes de equivalência de valor no_colorseja adicionado ao nosso botão rnd_color. Então 4 casos permanecerão, apenas mais um parâmetro será adicionado a cada um deles: O



imagem



número de casos aumentará somente se algum parâmetro tiver mais valores do que havia casos.



Você pode verificar o importante com mais frequência



Ao enriquecer os valores (etapa 2 no algoritmo), valores de prioridade mais alta ou mais arriscados podem ser verificados com mais frequência.



Por exemplo, se sabemos que os usuários anteriores usavam textos mais curtos com mais frequência e agora usam textos mais longos, podemos enriquecer os casos com textos mais longos e, com mais frequência, entrar em casos reais de usuários.



Pode ser automatizado



O algoritmo acima é bastante adequado para automação. Obviamente, os casos gerados pelo algoritmo parecerão menos reais do que os gerados por humanos. Pelo menos combinando cores e recortando texto.



Mas por outro lado, já em processo de desenvolvimento sem a participação de um testador, aparecem casos, o que reduz bastante o ciclo de feedback.



imagem



desvantagens



Naturalmente, essa geração de case está longe de ser uma solução mágica e tem suas desvantagens.



Dificuldade em analisar o resultado



Acho que você percebeu que, no processo de geração de casos, os dados de teste são misturados uns com os outros. Por isso, quando a caixa cai, fica mais difícil identificar a causa da queda. Afinal, alguns dos parâmetros usados ​​no caso não afetam de forma alguma o resultado do teste.



Isso realmente torna difícil analisar os resultados do teste, por um lado. Mas, por outro lado, se o objeto em teste requer uma grande quantidade de parâmetros exigidos, isso também torna difícil encontrar a causa do bug.



Bugs podem ser perdidos



Voltando ao início do artigo: ao utilizar este método, permitimos a possibilidade de pular bugs causados ​​pela combinação de dois ou mais parâmetros. Mas ganhamos em velocidade, então cabe a você decidir o que é mais importante para cada projeto específico.



Para não perder bugs duas vezes, introduzimos a Política de Zero Bug e começamos a fechar cada bug perdido com um caso de teste adicional - não mais gerado automaticamente, mas escrito à mão. Isso deu excelentes resultados: agora temos mais de 150 blocos (objetos testados), vários lançamentos por dia e de 0 a 3 bugs não críticos perdidos por mês.



conclusões



Se seu objeto testado tem uma ampla gama de parâmetros de entrada e você deseja tentar reduzir o número de casos e, como resultado, o tempo para teste, recomendo tentar o método acima de geração de casos usando um parâmetro.



Na minha opinião, é ideal para componentes front-end: você pode reduzir o tempo em mais de três vezes, por exemplo, para verificar a aparência por meio de testes de captura de tela. E o desenvolvimento será mais rápido devido ao surgimento de casos nos estágios iniciais.



Claro, se você estiver testando o piloto automático do novo Tesla, não pode negligenciar nem mesmo a pequena probabilidade de perder um bug. Mas na maioria dos casos, não se esqueça que a velocidade no mundo moderno é um critério de qualidade muito importante. E o aumento na velocidade dá resultados mais positivos do que alguns problemas menores encontrados.



imagem



E para os mais responsáveis, no próximo artigo vou dizer como você pode se proteger adicionalmente de bugs complicados causados ​​por uma combinação de parâmetros usando casos personalizados e StoryBook.



All Articles