Muitas pessoas me perguntam como escrever testes de unidade de classes que lidam com datas. E, geralmente o problema está em como testar classes que usam a data/hora atual.

Esse problema acontece pois grande parte das APIs que lidam com datas, tanto no mundo .Net quanto no mundo Java, fazem uso de métodos estáticos. Por exemplo:

DateTime.Now // C#
Calendar.getInstance() // Java

Generalizando o problema, a dificuldade nao é testar datas, mas sim qualquer classe que faz uso de métodos estáticos. Por exemplo, como escrever um teste para o método abaixo?

public int DiasEntreHjEAData(DateTime data) {
  return (DateTime.Now - data).TotalDays;
}

A propriedade Now sempre irá devolver a data corrente, dificultando assim a escrita do teste; como escrever um teste onde o cenário muda o tempo todo?

Não conseguir simular o comportamento do método Now, e esse eh um dos problemas de usar métodos estáticos: dificulta o teste das classes que os utilizam (além de não permitir o uso decente de polimorfismo, mas isso é uma outra discussão…)

Para resolver esse problema, precisamos deixar de usar métodos estáticos. Mas e como fazer com as APIs que já existem, e não podemos mudá-las, como é o caso da API de DateTime?

Isso não nos impede de criarmos uma abstração em cima disso! Veja o código abaixo:

public interface Relogio {
  DateTime Hoje();
}

public class RelogioDoSistema : Relogio {
  public DateTime Hoje() {
    return DateTime.Now;
  }
}

Veja que criamos a interface Relogio, que abstrai o problema de calcular a hora atual. Nosso método acima agora, em vez de invocar o método estático, faz uso da nova abstração:

public class Algoritmo {

  // recebido pelo construtor
  private Relogio relogio;

  public int DiasEntreHjEAData(DateTime data) {
    return (relogio.Hoje() - data).TotalDays;
  }
}

Pronto. Veja agora que testar essa classe é facil. Basta passarmos um mock e simular o comportamento esperado do Relogio.

Resumindo, métodos estáticos dificultam a escrita de testes de unidade. Para resolver isso podemos: evitar a escrita de métodos estáticos, ou criar abstrações que escondem esses métodos. Nao é feio criar abstrações como a Relogio; feio é não testar! :)

Share

Mito: não ter 100% de cobertura é a mesma coisa que nada!

Muitas pessoas discutem a necessidade de ter 100% de cobertura em um código. Mas não vejo problemas em códigos que não tenham 100% de cobertura de testes de unidade.

Acredito que isso deve ser uma meta da equipe, buscar sempre a maior cobertura possível; mas essa é uma meta que você provavelmente não vai alcançar; alguns trechos de código simplesmente não valem a pena serem testados de maneira isolada!

Explico: Veja essa classe do Restfulie.NET, por exemplo, chamada AspNetMvcUrlGenerator: ela serve para gerar URLs para Actions em Controllers, utilizando as rotas pré-definidas. Repare que ela faz uso intenso das APIs do Asp.Net MVC, utilizando inclusive alguns métodos estáticos (que sabemos que é difícil de testar) como no HttpContext.

public class AspNetMvcUrlGenerator : IUrlGenerator
    {
        public string For(string controller, string action, IDictionary values)
        {
            var httpContextWrapper = new HttpContextWrapper(HttpContext.Current);
            var urlHelper = new UrlHelper(new RequestContext(httpContextWrapper,
              RouteTable.Routes.GetRouteData(httpContextWrapper)));

            return FullApplicationPath(httpContextWrapper.Request) +
              urlHelper.Action(action, controller, new RouteValueDictionary(values));
        }

        private string FullApplicationPath(HttpRequestBase request)
        {
            var url = request.Url.AbsoluteUri.Replace(request.Url.AbsolutePath,
               string.Empty) + request.ApplicationPath;
            return url.EndsWith("/") ? url.Substring(0, url.Length - 1) : url;
        }
    }

Eu até poderia ter feito alguma mágica e escrito um teste de unidade para esse código. Mas para quê? Apenas para aumentar o número? Não faz sentido! Esse trecho de código precisa de um teste de integração, e não de um teste de unidade!

Um outro exemplo é o teste de propriedades (Properties do C#). Preciso realmente deles? A própria linguagem implementou isso pra mim. O mesmo acontece no caso dos getters/setters do Java, onde o programador geralmente usa o Eclipse para gerá-los.

Você precisa cobrir seu código de testes, mas você pode usar testes de diferentes níveis para isso (testes de unidade, de integração, de sistema, etc)! Escrever testes de unidade inúteis, apenas para chegar nos 100% de cobertura, é desperdício.

Share

O código dos testes é tão importante quanto código de produção. E provavelmente você já ouviu aquela famosa frase: “melhor do que escrever código, é apagar código!”. Quando é então que eu apago código de teste?

A primeira e mais óbvia resposta é: quando o teste deixar de fazer sentido! Se a funcionalidade foi removida, você deve atualizar sua bateria de testes e apagar todos os testes relacionados à ela. Bateria de testes desatualizada não serve pra nada! Se a funcionalidade evoluir, você deve evoluir seus testes juntos.

Até aí nada de novidade… Mas vamos lá.

A segunda resposta é: quando você tem testes repetidos! Em algumas situações, quando estamos em dúvida sobre como implementar determinada funcionalidade, optamos por escrever testes parecidos para, de alguma forma, triangularizar até chegar na implementação correta.

Voltando ao velho exemplo da calculadora. Suponha que implementar um algoritmo de soma fosse algo complicado. Você começou com testes simples, como (1+1), depois (1+2), depois (2+2). Nesse momento você encontrou uma maneira de resolver o problema para quaquer (m+n). Seus testes de unidade ficam parecidos com esses:

public class CalculadoraTest {
  @Test
  public void deveSomarUmMaisUm() {
    assertEquals(2, new Calculadora().soma(1,1));
  }

  @Test
  public void deveSomarUmMaisDois() {
    assertEquals(3, new Calculadora().soma(1,2));
  }

  @Test
  public void deveSomarDoisMaisDois() {
    assertEquals(4, new Calculadora().soma(2,2));
  }
}

Esses testes, muito úteis durante o tempo de desenvolvimento do algoritmo, agora se tornaram repetidos. Você, portanto, deve apagá-los! Eles, além de serem inúteis, ainda dificultam o trabalho do desenvolvedor. Se um dia o método testado mudar, você terá que mudar em 10, 20 testes diferentes (mas que testam a mesma coisa!). Lembre-se do acoplamento entre seu código de teste e seu código de produção (sim, ele existe!).

Mas poxa, um testezinho só não é pouco? Não! Você precisa de apenas um teste para garantir a funcionalidade. Não adianta testar a mesma coisa duas vezes.

Você deve ter apenas um teste para cada conjunto de estados válidos e inválidos para uma condição de entrada. A ideia é que todos os elementos de uma classe se comportem de maneira similar. A esses conjuntos damos o nome de classes de equivalência. Escrever apenas um teste por classe de equivalência é uma prática muito comum em testes de caixa preta e é conhecida como particionamento em classes de equivalência. Apesar disso, acredito que ela faça sentido também para testes de caixa branca, como os testes de unidade.

No nosso exemplo da calculadora, poderíamos ter testes para, por exemplo:

  • soma de dois números positivos;
  • soma de um número positivo com outro negativo;
  • soma de um número negativo com outro positivo;
  • soma de dois números negativos;
  • soma com um dos elementos sendo zero;
public class CalculadoraTest {
  @Test
  public void deveSomarDoisNumerosPositivos() {
    assertEquals(4, new Calculadora().soma(2,2));
  }

  @Test
  public void deveSomarPositivoComNegativo() {
    assertEquals(4, new Calculadora().soma(6,-2));
  }

  @Test
  public void deveSomarNegativoComPositivo() {
    assertEquals(-4, new Calculadora().soma(-6,2));
  }

  @Test
  public void deveSomarDoisNumerosNegativos() {
    assertEquals(-4, new Calculadora().soma(-2,-2));
  }

  @Test
  public void deveSomarComZero() {
    assertEquals(4, new Calculadora().soma(0,4));
    assertEquals(4, new Calculadora().soma(4,0));
  }
}

Obviamente, encontrar todas as classes de equivalência não é um trabalho fácil, e por isso temos a gigante área de testes de software. Mas não é repetindo testes que você garante que seu código funciona.

Referências

Maldonado, Jino, Delamaro. Introdução ao teste de software. Editora Campus, 2007.

Share
© 2011 Mauricio Aniche Suffusion theme by Sayontan Sinha