quarta-feira, 26 de março de 2014

Você testa direito?

Conforme prometido, aqui vai mais uma série de tutoriais, para comemorar o terceiro ano do Bom Programador! Esta série é sobre testes: Tudo o que você sempre quis saber sobre teste de aplicação, mas tinha vergonha de perguntar. Acompanhe essa série e as outras séries de tutoriais aqui, na sua fonte de informação sobre engenharia e qualidade de software. Neste primeiro tutorial, veremos como automatizar testes funcionais em aplicações legadas.




Parece até piada...

Certa vez, em uma determinada empresa, fui chamado para avaliar um determinado sistema aplicativo, desenvolvido por uma equipe interna. Tudo parecia certinho, exceto por um pequeno detalhe: Não consegui encontrar nenhum caso de teste! Nenhum código para testar a aplicação estava disponível. Bem, pode ser que eles mantenham o código de teste em separado, então eu perguntei: "Onde estão os casos de teste?" E o "Gerente" de projeto prontamente buscou alguns arquivos em seu computador, e me chamou para ver. Então, ele perguntou: "Você quer que eu imprima ou basta ver na máquina?"

Para meu espanto, ele estava me mostrando uma planilha Microsoft Excel, contendo casos de teste escritos, e "Screenshots" de resultados. Putz! Aquilo eram os casos de teste! Eles os escreviam em uma planilha e o testador repetia, como um robô, o roteiro, copiando e colando as telas.

Não é piada! E existem versões mais "espertas", por exemplo, alguns softwares de bug-tracking ou de ALM, permitem que o usuário escreva casos de teste e cole resultados. Como diria um grande amigo meu, "é tão ingênuo que chega a ser bunitinhu"...

Teste é produto

Existem algumas coisas que você precisa saber sobre teste de software. Para começar:

Teste é produto de trabalho

O Cliente te pagou para desenvolver e entregar o software funcionando, logo, se você escreveu algum código para testar a aplicação, ele pertence ao Cliente, e deve ser mantido junto com o próprio código fonte da aplicação.

Outra coisa importante:

Teste deve ser automatizado

Hoje em dia, com as várias ferramentas de "build" disponíveis (Apache Maven, Ant etc) não há desculpa! Podemos escrever testes (JUnit ou TestNG) e automatizar sua execução, atrelando-a ao próprio processo de "build" da aplicação. Testes manuais são sujeitos a falhas, e requerem um esforço maior para realização.

E, finalmente, mais uma coisinha:

Testes ideais são idempotentes

Deve ser possível repetir a execução de testes, sem afetar o estado do sistema aplicativo. Resultados intermediários devem ser descartados, e transações devem ser removidas. O sistema deve permanecer no mesmo estado inicial em que estava. Testes manuais ou testes que não preservam essa condição, podem gerar resultados imprevisíveis, requerendo grande esforço de "setup".

Testes e qualidade de software

Se você possui testes disponíveis, automatizados e idempotentes, então você pode provar que o seu sistema foi efetivamente testado. Sem isso, não adiantam planilhas e nem entradas no "Jira". Nada disso prova que o seu sistema foi testado.

Ok, mas não basta provar... É necessário saber o QUANTO do seu sistema foi testado? Qual o percentual das linhas de código realmente executadas nos testes? E as ramificações testadas? Este é um conceito que se encaixa dentro da classificação de testes "Caixa Branca", que consideram a implementação interna ao testar. Por outro lado, testes "Caixa Preta" só consideram os estímulos e as respostas.

Saber o quanto do seu código foi efetivamente testado é um indicador importante de qualidade de software!

Entendemos por "Cobertura de teste" (leia mais em "O nirvana do bom código fonte") as métricas que indicam o quanto do seu código foi executado nos testes automatizados, geralmente expressas em percentuais.

Agora, se você não tem testes automatizados, então não tem como calcular a cobertura.

Existem Testes e Testes...

Segundo o SWEBOK, podemos classificar os testes de software de várias maneiras diferentes. Para começar, podemos diferenciar os testes pelo seu Nível:

  • Alvo do teste: Qual é a parte do sistema que está sendo testada? É uma unidade, ou um módulo? Existe uma classificação de acordo com o alvo:
    • Teste unitário: Testa se uma unidade (um pacote, uma classe) está funcionando de acordo com as especificações;
    • Teste de integração: Testa se as várias unidades estão funcionando em conjunto, conforme o esperado. Geralmente, é repetido periodicamente, a cada nova unidade integrada, por exemplo;
    • Teste de sistema: Testa o sistema inteiro, focando nos Requisitos Não Funcionais, como: desempenho, escalabilidade, segurança, usabilidade etc;
  • Objetivo do teste: Por que estamos testando? 
    • Testes de aceitação: Para saber se os critérios de aceitação foram atendidos. O Cliente pode estabelecer critérios e pode participar do teste, para saber se o produto está dentro do que ele pediu;
    • Teste de instalação: Após ser aceito, o sistema precisa ser instalado em seu ambiente final (também conhecido como "produção"), logo, é necessário saber se ele está funcionando. Este tipo de teste pode ser repetido a cada nova instalação do sistema aplicativo;
    • Testes alfa e beta: Quando o software possui um grande número de usuários, ou é complexo demais ou crítico demais, é costume gerar versões intermediárias, submetendo-as a grupos selecionados de usuários para teste em condições limitadas. As diferenças entre os testes "alfa" e "beta" podem ser: quantidade de usuários (alfa é um grupo muito pequeno) e completeza do software (versões beta geralmente estão completas, versões alfa podem ter funcionalidades faltando); 
    • Testes de regressão: Verifica se uma alteração não violou os requisitos e as modificações já efetuadas no software. Por exemplo, podemos selecionar um componente e passá-lo por vários casos de teste passados, para ver se os problemas já resolvidos foram afetados por uma nova alteração. Podem ser aplicados em todos os níveis de teste;
    • Teste de desempenho: O objetivo é suprir uma carga de transações ao sistema aplicativo, de modo a verificar se ele se comporta de acordo com os requisitos não funcionais;
    • Teste de segurança ou de vulnerabilidades: Verifica se existem vulnerabilidades que possam ser exploradas, e avalia a resistência ao uso malicioso;
E, ainda segundo o SWEBOK, existem várias técnicas de teste, que podem ser classificadas como "Caixa Branca", quando você conhece a implementação do software, usando esse conhecimento para melhorar a qualidade do teste, e "Caixa Preta", quando você não conhece ou não quer considerar a implementação nos testes. As várias técnicas de teste propostas pelo SWEBOK podem ser classificadas dessas duas maneiras. Por exemplo, eis algumas técnicas: 
  • Ad Hoc: É uma técnica intuitiva, baseada na experiência do desenvolvedor;
  • Teste exploratório: É quando, ao mesmo tempo estamos aprendendo a lidar com um novo sistema, escrevendo testes e executando. O objetivo é aprender a usar o sistema e também como testá-lo;
  • Técnicas baseadas no domínio das entradas: Como o nome diz, se baseiam na manipulação das entradas, de modo a produzir testes mais eficientes. Alguns exemplos: 
    • Particionamento: Dividir as entradas em grupos, de acordo com determinados critérios, ou resultados esperados;
    • Pairwise: Testes com combinações, geralmente em pares, de entradas, que podem produzir resultados combinados;
    • Valor limítrofe: São realizados com os valores próximos ou nos limites especificados. Também podemos testar a robustez, com valores fora dos limites;
  • Técnicas baseadas na codificação: São testes "Caixa Branca", cujo objetivo é cobrir o máximo de código possível. Algumas técnicas são:
    • Fluxo de controle: Tentam executar o máximo de comandos possível. Entre essas técnicas está o "path testing", que visa executar os vários caminhos do código da entrada até o fim;
    • Fluxo de dados: São baseados em controle de variáveis, por exemplo, a técnica "definition-use" mapeia todos os caminhos a partir da definição e do uso das variáveis;
São muitas técnicas, que são descritas com detalhes no SWEBOK v3 (download gratuito), logo, não vamos nos aprofundar mais.

Desenvolvimento e testes

Apesar da importância dos testes, tradicionalmente a técnica mais empregada é "Ad Hoc", e os testes, quando existem, ficam nas estações dos desenvolvedores, sendo considerados como instrumentos particulares. Quase nenhum projeto armazena os testes em seu Sistema de Controle de Versão (SVN, CVS, Git etc). 

Esta abordagem é muito baseada na experiência dos desenvolvedores, logo, a qualidade do software está ligada à qualidade do desenvolvedor ou à sua boa vontade. 

Existem técnicas de desenvolvimento modernas, que pregam e aplicam a maior relevância dos testes. A mais famosa (talvez) seja a "Test Driven Development" (TDD).. Outra, mais recente é o "Behavior Driven Development" (BDD), que  foca mais no comportamento esperado. 

TDD é baseado em "test first", ou seja, os casos de teste são escritos ANTES do código estar pronto. Alguns desenvolvedores, como eu, preferem o "test last", ou seja criar casos de teste DEPOIS do código pronto, ou uma mistura de ambos. 

Test last é vantajoso pois já temos o código e podemos usar esse conhecimento para aumentar a cobertura. O modelo ideal é combinar ambas as práticas, escrevendo casos de teste "genéricos", baseados em requisitos ANTES, e escrever testes específicos, baseados na implementação, DEPOIS do código pronto.

O elefante é grande...


O negócio é "comer pelas beiradas"! Se você quiser sair do "Ad Hoc" para alcançar o estado da arte em testes, vai ficar completamente maluco(a), pois são muitas técnicas, e nem todas elas podem ou devem ser usadas em conjunto. O ideal é começar devagar, e é exatamente essa a abordagem que vamos usar.

Se você tem um sistema, sem nenhum caso de teste, deve começar criando alguns casos automatizados, acrescentando a verificação de cobertura.

Em Java, temos o JUnit, que é o framework padrão de testes. Ele funciona no Eclipse, na Console e também via Maven, o que o torna extremamente vantajoso.

Depois, pode ser interessante adotar um sistema de "build", como o Maven, pois assim você pode automatizar a execução dos testes, jogando em um sistema de Integração Contínua e avaliando o resultado e a cobertura dos testes continuamente.

Vou contar um "causo"...

Vamos montar um pequeno tutorial, cujo objetivo é mostrar como você pode automatizar seus testes, mesmo em aplicações difíceis, que não foram criadas com técnicas modernas. Para começar, vamos escolher uma aplicação baseada em Javax/Swing, e totalmente monolítica, ou seja, o pior tipo de caso.

Então, vamos aos requisitos:
"A Cooperativa de Crédito ABCD empresta dinheiro aos seus associados, a taxas menores que as praticadas no Mercado. 
Ela precisa de uma aplicação simples, que calcule o valor da prestação de empréstimos, de modo a facilitar a consulta pelos associados em sua sede.  
Essa aplicação será instalada em um computador de consulta, que ficará sempre disponível para que os associados possam consultar o valor antes de solicitar um empréstimo.  
Atualmente, a Cooperativa trabalha com alguns tipos de empréstimos, cujas taxas são diferenciadas: 
1. Empréstimo CDC, não vinculado a financiamento de bens. A taxa utilizada é a taxa padrão do dia, em seu valor pleno. O prazo não pode exceder 12 meses, e o valor é limitado a 12 salários mínimos; 
2. Financiamento de veículos pessoais, vinculado à compra de 01 (um) veículo zero kilômetro para uso exclusivo pessoal do associado. A taxa padrão tem seu valor reduzido em 10% (dez por cento), e o carro ficará alienado. O valor do financiamento não pode exceder 40 salários mínimos, e o prazo máximo é de 50 meses. O valor do veículo não pode ser inferior a 30 salários mínimos, e o associado tem que contribuir com 10% do valor do veículo, não incluídos no financiamento; 
3. Auxílio doença, para tratamento de doenças, compra de remédios e cirurgias, vinculado a um laudo pericial. A taxa padrão tem seu valor reduzido em 30%, e o valor limite é de 50 (cinquenta) salários mínimos, com um prazo máximo de 60 (sessenta) meses. O prazo mínimo é de 24 meses. Há um valor-caução de seguro obrigatório, descontado do valor total emprestado, de 5% (cinco por cento) do valor do Empréstimo; 
O cálculo é feito por juros simples ((c.i.t) / 100). O valor das prestações, por enquanto, é fixo. Futuramente, a Cooperativa pensa em adotar outras formas de cálculo, como: Tabela Price ou SAC.  
As informações para cálculo da prestação, a serem digitadas pelos associados, são: 
Capital: Valor total do empréstimo. Se for um empréstimo dos tipos 2 ou 3, não devem ser descontados o valor-caução e a participação do associado. A aplicação deverá calcular isso;
Tipo de empréstimo: O usuário deverá selecionar o tipo de empréstimo desejado; 
Prazo: O usuário deverá informar o prazo para pagamento, representado pelo número de meses (ou parcelas); 
A aplicação deverá calcular e exibir: 
1. Valor-caução ou participação do associado, em Reais, para os casos de empréstimo do tipo 2 ou 3; 
2. Valor da parcela; 
3. Montante: total a ser pago;"

Foi criada uma aplicação Java/Swing simples, que atende a este requisito. O projeto está todo no Github.

A classe principal (CalculadorPrestacao) estende JFrame e é uma aplicação Swing típica. Todos os três aspectos (Apresentação, Lógica de Negócios e Persistência) estão misturados. Poderíamos gastar um dia inteiro analisando a qualidade do código, mas não é o foco desse tutorial. O que importa, é que a aplicação funciona. Eis um exemplo típico de cálculo (CDC):


O Analista de Requisitos criou um roteiro de testes, com três casos básicos, escritos em uma planilha:

Caso de teste 1



Taxa Salário Min Capital Prazo Tipo
2,5 724 1000 12 CDC
V. Retido V. líquido Prestação

0 1000 108,33











Caso de teste 2



Taxa Salário Min Capital Prazo Tipo
2,5 724 25340 50 Veículo
V. Retido V. líquido Prestação

0 22806 969,26






Caso de teste 3



Taxa Salário Min Capital Prazo Tipo
2,5 724 32580 50 Doença
V. Retido V. líquido Prestação

1629 30951 1221,75


E, a cada manutenção, é só repetir manualmente todos estes testes, colando a imagem capturada da tela ("Screenshot") na planilha.

E aí? Qual é o problema?

O problema é que não existe evidência de testes. Você tem que acreditar que a aplicação foi testada, ou então, tem que repetir manualmente todos os testes. Imagine o que vai acontecer durante a vida útil da aplicação... Manutenções corretivas e evolutivas, que exigirão mais testes etc. Como os testes não são automatizados, tudo depende da boa vontade e da capacidade das pessoas que vão testar a aplicação. Se não tomarem cuidado, alguma coisa poderá passar despercebida.

O que podemos fazer?

Eu sei que você, provavelmente, já olhou o código fonte e está pensando: "Hummm, e se eu criar uma classe Empréstimo, separando a lógica de negócios da camada de apresentação e..." Você está certo(a), mas esse não é o maior problema, pelo menos no momento. Não há evidência de testes! Os testes são manuais e o risco de passar algum problema é muito alto. Note que o texto do requisito dá margem à interpretação de que existe um "roadmap" evolucionário, talvez com novas modalidades de empréstimo ou até mesmo novos tipos de plataforma (Web, Mobile etc). E isso tudo exige que exista código de teste disponível, automatizado e idempotente!

Bem, uma primeira alternativa seria tentar separar o código Swing do código funcional, criando uma classe de negócios dedicada, um POJO. Assim, fugimos do "Thread de UI", exigido pelo Swing, e podemos testar pelo menos a lógica. Seria uma alternativa boa, mas a cobertura de testes não seria a ideal, pois muita coisa ficaria ainda na camada Swing. Além disso, o trabalho de separar as camadas será grande e difícil de justificar para o Cliente.

Automação de testes funcionais

Vamos resolver esse problema, pelo menos em uma primeira abordagem, usando a técnica de "Automação de testes funcionais". E existem alternativas para automatizar o teste dessa aplicação, mesmo usando uma interface Swing.

Automação de testes funcionais serve para gerar testes de aceitação (ou instalação), automatizados, com o uso de alguma ferramenta de simulação de entrada. No caso do Swing existem algumas ferramentas, logo, eu escolhi a FEST. O objetivo desse tutorial não é ensinar a usar o FEST... Há um bom tutorial de uso da FEST com Swing no blog da Codehaus.

O que vamos fazer?

Vamos começar automatizando os testes da planilha, usando casos de teste JUnit com os componentes FEST. Vamos começar com um empréstimo CDC normal, exatamente como o que está na planilha. Vamos simular as operações necessárias e testar o resultado:

  1. Abrir a aplicação, passando a taxa e o prazo;
  2. Observar se a tela abriu;
  3. Verificar se o título da Janela confere com: o título esperado;
  4. Digitar 1000,00 no campo "Capital";
  5. Digitar 12 no campo "Número de parcelas";
  6. Selecionar o tipo de empréstimo "CDC";
  7. Clicar no botão "Calcular";
  8. Verificar se o painel "Resultado..." foi exibido e se o painel "Atenção" (mensagem de erro) continua invisível;
  9. Observar se o valor retido é "R$ 0,00";
  10. Observar se o valor líquido é "R$ 1.000,00";
  11. Observar se o valor da prestação é "R$ 108,33";
  12. Clicar no botão "Limpar";
  13. Observar se o painel "Resultado..." foi ocultado.
São treze passos a verificar, e tudo isso para executar apenas um único caso de teste, para um único empréstimo! Temos que automatizar isso...

E fizemos! Veja o Gist do testcase, que está dentro de "src/test/java": 


Bem, é claro que, para entender completamente o test case você terá que estudar um pouco a API do FEST. Mas eu vou mostrar o passo a passo bem resumido, para que você entenda como estou implementando os passos do teste.

Para começar, eu tenho que instanciar uma Framefixture, contendo uma instância da minha classe. Isso tudo é feito no método "setup", chamado antes da execução do teste:


@Before public void setUp() {
  CalculadorPrestacao calc = GuiActionRunner.execute(new GuiQuery() {
      protected CalculadorPrestacao executeInEDT() {
      // Inicializando com taxa padrão de 2,5% e Salário Mínimo de R$ 724,00
       CalculadorPrestacao calc = new CalculadorPrestacao(2.5, 724.00);
       return calc; 
      }
  });
  window = new FrameFixture(calc);
  window.show();
}

Então, eu passo a ter um objeto "window" que representa a minha janela Swing, e posso acessar todos os seus elementos.  Os dois primeiros passos do teste (1 e 2) são fáceis de implementar:

// A janela está visível?
window.requireVisible();
// O título está correto?
assertThat(window.component().getTitle()).isEqualTo("Cooperativa ABCD - Cálculo de Empréstimos");

O código não fica muito bem nessa formatação, é melhor você conferir no Gist. Eu simplesmente uso as condições do FEST para testar se a janela está visível e se o seu título é o que eu espero. Depois, tenho que digitar o valor do empréstimo.

Como a aplicação Swing não foi criada para usar o FEST, nenhum dos componentes Swing tem a propriedade "name" preenchida. O que dificulta a sua localização na Janela. Eu preciso encontrar o campo para digitar o valor, porém, só sei que seu conteúdo inicial é "R$ 0,00", então vou usar essa informação para tentar localizá-lo:

// Vamos fazer um empréstimo OK
// Capital: R$ 1.000,00, prazo: 12 meses
GenericTypeMatcher textMatcher = 
  new GenericTypeMatcher(JFormattedTextField.class) {
    @Override protected boolean isMatching(JFormattedTextField capital) {
    return "R$ 0,00".equals(capital.getText());
   }
};        
window.textBox(textMatcher).enterText("1000,00");

Bem, estou usando um "GenericTypeMatcher", para informar as condições que necessito para localizar a caixa de texto para digitar o valor do Capital. Ela deve ser do tipo "JFormattedTextField", e deve ter a propriedade "Text" = "R$ 0,00". Se eu encontrar, então digito o valor "1000,00" nela.

Faço algo parecido com a caixa de textos do prazo.

Agora, tenho que encontrar o "JRadioButton" do CDC e clicar nele:

GenericTypeMatcher textMatcher3 = 
  new GenericTypeMatcher(JRadioButton.class) {
    @Override protected boolean isMatching(JRadioButton cdcRadio) {
       return "CDC".equals(cdcRadio.getText());
    }
}; 
window.radioButton(textMatcher3).check();

O procedimento é quase o mesmo, só mudando a classe do componente Swing que estou procurando, e o texto que deve ter. Note que eu aciono o método "check()" do radio button do CDC.

Agora, eu preciso seguir o passo 7 e clicar no botão "Calcular":

window.button(JButtonMatcher.withText("Calcular")).click();
Pause.pause();

Já existe um "matcher" genérico para encontrar um JButton com determinado texto, então eu posso usar o método "withText()" para encontrá-lo e acionar o Click. Agora, só faltam os outros passos. Por exemplo, verificar se panel de mensagens foi exibido, o que denota um erro, e se o panel de resultados está visível:

GenericTypeMatcher panelMatcher = 
  new GenericTypeMatcher(JPanel.class) {
    @Override protected boolean isMatching(JPanel resultados) {
       boolean resultado = false;
       TitledBorder border = (TitledBorder) resultados.getBorder();
       if (border != null) {
          if ("Resultado da simulação".equals(border.getTitle())) {
             resultado = true;
          }
       }
       return resultado;
    }
}; 
GenericTypeMatcher msgMatcher = 
  new GenericTypeMatcher(JPanel.class) {
    @Override protected boolean isMatching(JPanel resultados) {
       boolean resultado = false;
       TitledBorder border = (TitledBorder) resultados.getBorder();
       if (border != null) {
          if ("Atenção:".equals(border.getTitle())) {
             resultado = true;
          }
       }
       return resultado;
    }
};        
// Tem que exibir o JPanel com os resultados
window.panel(panelMatcher).requireVisible();
// Não pode exibir o JPanel com a mensagem de erro
window.panel(msgMatcher).requireNotVisible();

Encontrar os painéis é mais difícil... Como eu usei uma "TitleBorder" para colocar rótulos neles, então tenho que usar isso para tentar encontrá-los.

Agora, só falta verificar se os resultados conferem e depois mandar limpar a tela, verificando se os painéis sumiram:

// Verificando o resultado do cálculo
window.label(JLabelMatcher.withText("R$ 0,00")).requireVisible();
window.label(JLabelMatcher.withText("R$ 1.000,00")).requireVisible();
window.label(JLabelMatcher.withText("R$ 108,33")).requireVisible();
window.button(JButtonMatcher.withText("Limpar")).requireVisible();
// Agora. vamos clicar para limpar tudo: 
window.button(JButtonMatcher.withText("Limpar")).click();
Pause.pause();
window.panel(panelMatcher).requireNotVisible();
window.panel(msgMatcher).requireNotVisible();

Pronto! Ai está um test case automatizado, que testa a aplicação, pelo menos um caso de empréstimo CDC normal.

Porém, o requisito cita várias condições para os empréstimos, e temos que testar todas elas. Por exemplo, podemos criar um caso de teste para valores fora do limite de 12 salários mínimos para o CDC. O test case é muito parecido com esse, só que precisamos saber se deu erro. Eis a parte do código que testa isso:


window.panel(panelMatcher).requireNotVisible();
window.panel(msgMatcher).requireVisible();
window.label(JLabelMatcher.withText("Valor maior que 12 salários mínimos (" 
        + Double.toString(limite12salarios)
        + ")!")).requireVisible();

Agora, o painel de resultados tem que ficar invisível e o de mensagens tem que aparecer, mostrando o texto que eu previ no código fonte.

Se rodarmos com o JUnit, veremos que tudo funciona bem:


E, como o projeto é Maven, podemos até rodar diretamente pelo Maven, seja no Eclipse ou fora dele:


E esse script pode ser automatizado até em um Servidor de Integração Contínua!

Não vou criar todos os outros testes, porém, aconselho você a criar, apenas para exercício. E dou uma dica: Faça testes de valor limítrofe, pois o prazo está aceitando valores negativos!

Rodando sem interface gráfica

O comportamento default do FEST é exibir a janela Swing, logo, o computador onde ele está rodando os testes deve ter uma interface gráfica. O problema é que nem sempre desejamos esse comportamento, ou, no caso de servidores Linux, sequer temos Xwindows ou GNome Desktop. Como fazer?

Felizmente, há solução para isso. No caso de servidores Linux, o que não vamos demonstrar nesse momento, é só usar o Xvfb, que é um Servidor X11 que não exibe janelas. A Codehaus tem um tutorial sobre o assunto. Desta forma, podemos executar os testes de aplicações que usam Janelas, sem necessidade de exibi-las, e sequer precisamos de Xwindows no Servidor.

Conclusão

Neste primeiro tutorial, falamos muito sobre conceitos e fundamentos de testes, e mostramos um cenário, no qual temos uma aplicação legada, SEM UM ÚNICO caso de teste. Então, usando a técnica de "Automação de testes funcionais", criamos alguns test cases para ela.

Vamos ver muito mais sobre testes, incluindo outros softwares de automação de testes funcionais, além do FEST.





Nenhum comentário:

Postar um comentário