Combinando Test Driven Development
(TDD) com Design By Contract (DBC)
João Arthur Brunet Monteiro
Universidade Federal de Campina Grande - UFCG
Centro de Engenharia Elétrica e Informática - CEEI
Departamento de Sistemas e Computação - DSC
Roteiro
Introdução
Backgroud
Test Driven Development
Design By Contract
Combining Formal Specifications with Test Driven Development
Visão Geral
Técnica Proposta
Resultados
Discussões
Introdução
Combining Formal Specifications with Test Driven Development
Design By Contract e Test Driven Development
Trabalho relativamente recente (2004).
Por quê?
Por quê combinar as duas técnicas?
Test Driven Development
Técnica de desenvolvimento de software baseada em testes
Testes são desenvolvidos antes do código
Técnica ágil
Pequenos passos (baby steps)
Manter a simplicidade (KISS)
Test Driven Development
Não constitui somente uma maneira de testar. TDD é uma
técnica desenvolvida que, ao aplicada, força o programador a
pensar no design do código antes dele ser implementado.
Passos:
Escrever o teste
Obter red bar
Escrever o código mais simples que passe no teste
Obter green bar
Refatorar o código
Testes
Descrevem parcialmente, através de exemplos, o
comportamento do sistema
Usados como documentação de um programa
Aumentam o entendimento sobre o código
Aumentam a qualidade do código
Mas...
Não generalizam propriedades do sistema
Design By Contract
Metodologia que se baseia na construção de contratos entre partes do software.
Usa especificações formais para estabelecer contratos entre partes do software
pré-condições, pós-condições e invariantes
Especificações formais são também usadas como documentação sem fazer
referência ao código. Por isso, aumentam a reusabilidade do software e,
consequentemente, sua qualidade
Especificações formais descrevem o comportamento do sistema de maneira
mais completa que testes o fazem
Mas...
A tarefa de mostrar que um software está de acordo com as especificações é
geralmente muito complexa
Por quê não?
Por quê não combinar as técnicas?
Combining Formal Specifications with
Test Driven Development
Objetivos
Combinar especificação formal com TDD
Não perder a agilidade de TDD
Ganhar com as generalizações das especificações formais
Técnica Proposta
Os testes usados para conduzir o desenvolvimento do código
também são usados para conduzir o desenvolvimento das
especificações
Especificações generalizam os testes
Asserções são geradas a partir das especificações afim de checar
inconsistências entre testes, código e especificações
Três Variáveis de interesse
Testes
Código
Especificações
Manter a consistência entre as variáveis aumenta a qualidade do
código desenvolvido e da documentação uma vez que as
especificações formais não dão margem a interpretações ambíguas
Tecnologias Utilizadas
Junit
Código
+
Java
Testes
+
JML
Especificações
Tecnologias utilizadas
Junit e Java dispensam apresentações!
Java Model Language (JML) é uma linguagem para
formulações de pré-condições, pós-condições e invariantes
Especificações escritas em JML são simples de entender
JML vem com um checador de asserções em tempo de
execução
As tecnologias foram usadas para exemplificar a técnica, no
entanto, a mesma não exige tecnologias específicas!
Técnica Proposta
Consiste em, ao estilo TDD, uma sequência de passos que se
repetem ao longo do desenvolvimento do software.
1.
2.
3.
4.
5.
Escrever o teste
Implementar o código
Refatorar o código
Generalizar os testes para especificações
Refatorar as especificações
Aplicando a técnica
Exemplo usado: Programa que, dado um inteiro n, retorna os
números primos até n.
Passo 1: Escrever o teste
public void testZero() {
List primes Primes.primes(0);
assertTrue(primes.isEmpty());
}
Passo 2: Escrever o código
public static List primes(int n) {
return new LinkedList();
}
Passo 3: Refatorar o código
Aplicando a técnica
Passo 4: Generalizar o teste para especificações
public bahavior
requires n<=1;
ensures \result.isEmpty();
}
Veja que o caso de n ser igual a 1 foi coberto pela
especificação, mas não pelo teste. Analisando a especificação é
possível notar que falta um teste. No entanto, a inclusão do
novo teste não irá apontar faltas no código.
Aplicando a técnica
Passo 1: Escrever o teste
public void testTwo() {
List primes = new Primes.primes(2);
assertEquals(1, primes.size());
assertTrue( primes.contains(new Integer(2)));
}
Passo 2: Escrever o código
public static List primes(int n) {
List primes = new LinkedList();
if (n==2) primes.add(new Integer(2));
return primes;
}
Aplicando a técnica
Passo 4: : Generalizar o teste para especificações
public bahavior
requires n<=1;
ensures \result.isEmpty();
also
requires n ==2;
ensures \result.size() == 1 && \result.contains(new Integer(2));
Passo 1: Escrever o teste
public void testLosts1() {
...
for (int i = 1; i<=n ; i++) {
if (isPrime(i)) assertTrue(primes.contains(new Integer(i)))
}
}
Aplicando a técnica
Especificação da função isPrime(int i)
public bahavior
ensures
\result < = = > (n > 1 && (\forall int i; i > 1 && i < n; n % i != 0));
Passo 2: Escrever o código
public static List primes(int n) {
List primes = new LinkedList();
for (int i = 2; i <= n; i++) primes.add(new Integer(i));
return primes;
}
Aplicando a técnica
Passo 4: : Generalizar o teste para especificações
public bahavior
requires n <= 1;
ensures \result.isEmpty();
also
requires n == 2;
ensures \result.size() == 1 && \result.contains(new Integer(2));
also
requires n > 2;
ensures (\forall int i; i >= 2 && i <= n; isPrime(i) == > \result.contains(new
Integer(i)));
Aplicando a técnica
A especificação diz que, para um dado n, a função retorna
uma lista contendo os números primos de 2 até n. Porém,
não assegura que a lista só contém os números primos de 2 a
n.
Passo 1: Escrever o teste
public void testLosts1() {
List primes = Primes.primes(1000);
...
for (Integer i : primes) {
assertTrue(isPrime(i));
assertTrue(prime <= n);
}
}
Aplicando a técnica
Passo 2 : Escrever o código ... (crivo de Eratóstenes)
Passo 3: Refatorar o código ...
Passo 4: : Generalizar o teste para especificações
Aplicando a técnica
public bahavior
requires n <= 1;
ensures \result.isEmpty();
also
requires n == 2;
ensures \result.size() == 1 && \result.contains(new Integer(2));
also
requires n > 2;
ensures (\forall int i; i >= 2 && i <= n; isPrime(i) == > \result.contains(new
Integer(i)));
also
requires n > 2;
ensure (\forall Integer i; \result.contains(i));
isPrime(i);
Aplicando a técnica
Em TDD é argumentado que testes não devem ser
refatorados.
Refatorar especificações tornam-as mais claras e concisas.
Passo 5: Refatorar especificações
public bahavior
ensures
(\forall int i; i >= 2 && i <= n;
isPrime(i) == > \result.contains(new Integer(i)));
&&
(\forall Integer i; \result.contains(i);
isPrime(i);
Observações
Rodando os testes instrumentados com as asserções geradas
pelas especificações em JML, é possível garantir que a
especificação não entra em conflito com a implementação do
código
O caso de haver números maiores que n não foi considerado.
Mas neste caso, basta adicionar na especificação que i deve ser
menor que n
Resultados
Como resultado do processo temos um conjunto de testes,
código e especificações que assegura que o código
implementa o comportamento esperado, documentado pela
especificação
As especificações podem ser usadas como documentação para
descrever o comportamento do programa sem fazer
referência ao código fonte
Note que a especificação é mais simples de entender do que
os testes e o código
O poder de abstração aumenta com o uso das especificações
Resultados
Bugs que não eram encontrados fazendo uso de testes, são
revelados quando a técnica é usada
O uso da técnica ajuda a aumentar o conjunto de testes
Assim como testes, não garante a ausência de bugs
Discussão
Dúvidas?
Referências
Hubert Baumeister. Combining Formal Specifications with
Test Driven Development. XP/Agile Universe 2004: 1-12
Bertrand Meyer: Applying "Design by Contract, in
Computer (IEEE), vol. 25, no. 10, October 1992, pages 4051.
BECK, K. and FOWLER, M. (2001). Planning Extreme
Programming. Addison Wesley.