Automatização de massa de testes automatizados

12 views
Skip to first unread message

Juan Lopes

unread,
Sep 5, 2017, 1:28:10 PM9/5/17
to tdd-no-m...@googlegroups.com
Olá :)

Acho que nunca iniciei um tópico aqui, mas acho que há primeira vez para tudo. Inspirado pelo tópico dos 14 milhões de testes do ReactOS, gostaria de discutir um tema que gosto muito: automatização da geração de testes automatizados.

Sou um praticante bem ávido de TDD, apesar de ser bem liberal com a questão semântica entre test-first, test-along, e test-later. Sou liberal até com a inserção de testes com I/O no meio de suites de unit tests (especialmente testes que tocam o disco). Minha única restrição é quanto à estabilidade e ao tempo de execução dos testes, que deve se manter na casa dos 10 segundos ou menos para cada 5 mil test cases.

Como alguns sabem, trabalho em uma empresa que desenvolve um mesmo produto há uns dez anos, com a mesma codebase (naquele esquema do Navio de Teseu). Testes são importantes aqui não apenas para corretude ou bom design. Testes são essenciais para sobreviver. Trabalhar em projetos antigos e não ter >80% de cobertura tornaria qualquer evolução impossível. Com bons testes, é possível não apenas a evolução, como até reescrever subsistemas inteiros com alguma segurança. Não vou dizer que é impossível introduzir bugs, mas digo que dá para manter a sanidade durante refactorings.

Um dos projetos que temos aqui é uma DSL para processamento de eventos (já falei sobre ela em alguns eventos). Esta DSL permite ao usuário escrever consultas que executam sobre fluxos de dados em tempo real. Como estive no projeto desde o começo, pude guiar o design com mão de ferro. Buscamos e atingimos sempre ter 100% de cobertura. Entretanto, conforme a linguagem foi sendo cada vez mais usada pelos clientes, percebemos que os testes de unidade que escrevíamos para as features não eram suficientes para garantir que novas features não introduziriam regressões. Já gastávamos um bom tempo escrevendo testes de unidade de boa qualidade para tudo. Não é como se fosse questão de "testar mais".

Por isso, acabamos investindo um tempo em automatizar alguns testes mais "black box". Com isso, acabamos com três tipos de teste no projeto: além dos testes de unidade, temos testes de comportamento e testes de compatibilidade.

Não sei se essa nomenclatura é usada em outro lugar, então explico.

Testes de unidade são somente os testes de cada classe, testadas individualmente. Como falei, sou um pouco liberal com isso, então uma vez ou outra é mais cômodo testar duas ou três classes em conjunto, então ok. Às vezes não vale a pena isolar o I/O de uma classe que só faz I/O, então deixo fazer. Mas sempre mantendo os testes rápidos e o mais determinísticos possível. A codebase tem uns 1.8k testes desses (esse projeto tem 31k linhas de código Java).

Testes de comportamento pegam uma feature específica e testam em alto nível. Geralmente escrevo esses testes com @RunWith(Parameterized.class). São testes mais ou menos automatizados, no sentido em que defino uma lista de entradas e saídas esperadas no teste principal, mas aproveito para testar um monte de outras propriedades desses testes. Deixo um exemplo real de teste desse tipo aqui. Esse teste verifica se o compilador de uma sublinguagem de específicação de períodos está compilando para a representação canônica esperada. É um bem feio, mas que ajuda *muito* a verificar rapidamente regressões introduzidas sem querer. E o melhor é que adicionar mais um caso é só uma questão de duplicar uma linha. Tenho uns 11 ou 12 suites de testes desse tipo, totalizando cerca de 2 mil testes (perceba que são mais numerosos que os testes de unidade).

Testes de compatibilidade são os mais automatizados (e o foco deste email). Fiz um script que varre todas as instalações de todas os clientes pegando todas as consultas escritas pelos clientes, compilo cada uma delas com a versão da linguagem que está rodando no cliente e extraio metadados do valor compilado. Rodo essas consultas compiladas com alguns dados aleatórios e registro o comportamento que ela teve na versão em que ela foi escrita. Tudo isso resulta em um arquivo com a consulta e todos os comportamentos registrados na versão de origem. Adiciono isso ao projeto e o teste automatizado consiste em pegar cada consulta, compilar com a versão de desenvolvimento e verificar se o comportamento não mudou entre a versão onde a consulta foi escrita e a versão mais nova. Tudo isso é automatizado. Digo, a captura das consultas de clientes é feita através de um script, mas é necessário ativá-la manualmente.

Além do benefício óbvio desses testes permitirem detectar regressões muito mais facilmente, eles também servem como massa de dados disponível para adição de algumas features ortogonais que acabam mexendo em muitas classes ao mesmo tempo. Então acabo colocando alguns asserts a mais nesses testes, para testar em massa alguma feature com queries reais. É claro que, sempre que pego alguma regressão usando esses testes, escrevo um teste de unidade que a replica isoladamente. Mas os testes de compatibilidade são uma "safe net" bem útil. 

Implemento esses testes com um único teste do JUnit, porém minha massa de dados atual totaliza 6.2k consultas com seus respectivos metadados.

Inline image 1

Escrevi esse textão para compartilhar um pouco da minha experiência com automatização de massa de testes, mas queria ouvir um pouco da opinião de vocês sobre o assunto.

--

Maurício Aniche

unread,
Sep 6, 2017, 5:12:51 AM9/6/17
to tdd-no-m...@googlegroups.com
Oi Juan,

Feliz com a sua participação por aqui! :)

Me agrada a ideia de gerar testes para garantir que não há regressões. Acho que faz todo sentido no seu cenário, dado que existem provavelmente infinitas combinações de queries que um usuário pode fazer.

A ideia de gerar teste a partir de dados aleatórios, capturar a saída, e transformar em um teste não é tão nova e já se provou bem eficiente. O Randoop faz isso (acho que discutimos isso um tempo atrás por aqui). 

Minhas perguntas sobre sua abordagem são:

1) O problema de geração aleatória é que vc não sabe se está testando tudo. E provavelmente gera muitos testes repetidos no caminho. Ferramentas mais modernas como Evosuite tentam gerar testes que maximizam métricas como cobertura.

No seu caso, vc já pensou em procurar por testes que são similares (i.e., quebram e passam nos mesmos casos) e deletá-los? Ou não tem problema a sua suíte ser grande?

2) Você não tem problemas de privacidade por pegar queries reais dos seus usuários?

3) Já tentou comparar a diferença dos testes gerados por vc, de maneira "automatizada/manual" com os gerados por uma ferramenta existente? Seria uma comparação bem legal para ver como está o estado-da-arte dessas ferramentas.

Um abraço,
Maurício

image.png

Escrevi esse textão para compartilhar um pouco da minha experiência com automatização de massa de testes, mas queria ouvir um pouco da opinião de vocês sobre o assunto.

--

--
Você recebeu essa mensagem porque está inscrito no grupo "TDD no mundo real" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para tdd-no-mundo-r...@googlegroups.com.
Para mais opções, acesse https://groups.google.com/d/optout.
--
Maurício Aniche
Postdoc researcher
Delft University of Technology
@mauricioaniche
Reply all
Reply to author
Forward
0 new messages