Testes pra que te quero.

Uma introdução a testes unitários e TDD

Yan Magalhães

  • Full stack web developer na 4YouSee
  • GDGBH Organizer
  • FEMUG-MG Co-organizer
  • Amante da Web
  • Open Source
  • F1 Fan

1.

A importância dos Testes

Desenvolver Software é complexo.

Um cenário bem comum...

Testes são importantes?

Testes vão lhe dar segurança sobre sua aplicação.

Por quê devemos testar?

Testes manuais são caros.

Então por quê os testes não são automatizados?

  • Escrever testes é difícil.
  • Escrever um código para testar algo que ainda não existe?
  • Testar é custoso.
  • Não vou ter tempo para escrever um teste
  • Vou ser menos produtivo.

Alguns Motivos

Crie testes automáticos

  • Rápido resultado dos testes
  • Uma vez criado, esse teste poderá ser reaproveitado "N" vezes
  • O custo da realização do teste é menor

Pontos Positivos

Falando em custos...

Study: Buggy software costs users, vendors nearly $60B annually

  • Testes Manuais

  • Testes Unitários

  • Testes de Integração

  • Testes de Aceitação

Tipos de Teste

Testes Unitários

São testes que visam certificar o funcionamento de cada pequena parte do nosso sistema (classes e métodos), de forma isolada.

Testes De Integração

São os testes responsáveis por garantir o funcionamento da integração entre partes do sistema. Utilizamos classes do sistema que se comunicam, e a testamos de uma única forma, garantindo assim o funcionamento entre elas.

Testes De Aceitação

São os testes que garantem o funcionamento do sistema, verificando as funcionalidades implantadas e garantindo que o software realmente está fazendo o que fato era esperado.

Vamos Precisar de Ferramentas.

PHP

JS

Nosso primeiro teste

Queremos adicionar produtos em um carrinho de compras, e depois descobrir qual é o produto de maior e menor valor.

class OrderProductTest extends PHPUnit
{

    public function testSmallerAndLarguer()
    {
        $cart = new ShoppingCart();
        $cart->add(
                new Product("Celular", 900.00));
        $cart->add(
                new Product("Grill Churrasco", 550.00));
        $cart->add(
                new Product("Pen Drive", 100.00));
        
        $order = new OrderProduct();
        $order->find($carrinho);
        
        $this->assertEquals("Pen Drive", 
            $order->getSmaller()->getName());
        $this->assertEquals("Celular", 
            $order->getLarguer()->getName());
    }
}

O que fizemos:

  • Pensamos um Contexto
  • Definimos uma Ação
  • Validamos se o resultado era o esperado

2.

O famoso TDD

O TDD

Criado por Kent Beck, o Test Driven Development é uma metodologia de desenvolvimento de software, onde primeiro escrevemos o nosso teste e a partir daí, fazemos a implementação.

Vantagens

  • O código produzido já sai testado
  • Caso uma alteração quebre a regra dos testes, o feedback é muito rápido.
  • Produzimos um código melhor e de mais fácil evolução.
  • Focamos na solução

Vantagens

  • Testamos blocos isolados de código, então saberemos se aquele trecho está funcionando corretamente ou não
  • Simplicidade: ficamos longe de implementações complexas
  • Conseguimos pensar melhor no design de classes

Segundo Teste

Iremos receber uma string que representa os números romanos, e em seguida, retornar o seu valor em decimal.

public function testReturnISymbol()
{
    $rome = new RomeNumberConversor();
    $number = $rome->convert("I");
    $this->assertEquals(1, $number);
}

Primeiro o Teste

class RomeNumberConversor
{

    public function convert($romeNumber)
    {
        return;
    }

}

Implemente

class RomeNumberConversor
{

    public function convert($romeNumber)
    {
        return 1;
    }

}

Implemente

Execute os testes

Baby Steps

O tão famoso "passinhos de bebê" nos ajuda a entender melhor o contexto do problema. Sendo assim, buscamos fazer o teste passar, da forma mais simples e mais rápida possível.

Continuando...

/* Criando um Novo Teste */

public function testReturnVSymbol()
{
  $rome = new RomeNumberConversor();
  $number = $rome->convert("V");
  $this->assertEquals(5, $number);
}

Continuando...

/* Alterando a implementação */

public function convert($romeNumber)
{
   if ($romeNumber === "V") {
     return 5;
   }
   return 1;
}

Refactoring

Processo pelo qual se realiza a reescrita do código que será utilizado para a solução final, com o objetivo de eliminar redundâncias, sem que ocorra a mudança do resultado final produzido.

/* Agora nosso código cobre os casos Fixos de conversao */
protected $fixedNumbers = array(
   "I" => 1,
   "V" => 5,
   "X" => 10,
   "L" => 50,
   "C" => 100,
   "D" => 500,
   "M" => 1000
);

public function convert($romeNumber)
{
 if (array_key_exists($romeNumber, $this->fixedNumbers)) {
            return $this->fixedNumbers[$romeNumber];
 }
 return 0;
}

Demais Testes e Implementações

/* Novos testes para II, XXII */

public function testReturnsIISymbol()
{
  $rome = new RomeNumberConversor();
  $number = $rome->convert("II");
  $this->assertEquals(2, $number);
}

public function testReturnsXXIISymbol()
{
  $rome = new RomeNumberConversor();
  $number = $rome->convert("XXII");
  $this->assertEquals(22, $number);
}
/* Novos testes para IX, XXIV */

public function testReturnsIXSymbol()
{
  $rome = new RomeNumberConversor();
  $number = $rome->convert("IX");
  $this->assertEquals(9, $number);
}

public function testReturnsXXIVSymbol()
{
  $rome = new RomeNumberConversor();
  $number = $rome->convert("XXIV");
  $this->assertEquals(24, $number);
}
/* Implementacao final da conversao */
public function convert($romeNumber)
    {
        $sumNumbers = 0;
        $rightLast = 0;
        for ($i = strlen($romeNumber) - 1; $i >= 0; $i--) {
          $now = 0;
            $current = $romeNumber[$i];
            if (array_key_exists($current, 
                $this->fixedNumbers)) {
                $now = $this->fixedNumbers[$current];
            }
              $factor = 1;
            if ($now < $rightLast) {
                $factor = -1;
            }
            $sumNumbers += ($this->fixedNumbers[$current] 
                * $factor);
          $rightLast = $now;
        }
        return $sumNumbers;
}

Mock Objects

Permitem simular instâncias de uma classe, permitindo realizar ações nestas classes. Classes que por algum motivo ou outro, estão fora do nosso ambiente de testes, ou que não queremos utiliza-la

class Rainfall
{

    public function __construct($institute)
    {
        $this->intitute = $institute;
    }

    public function average()
    {
        $total = 0;
        for ($i=0;$i<5;$i++) {
            $total += $this->institute->getLastRainfall();
        }
        return $total/5;
    }

}
class RainfallTest extends PHPUnit_Framework_TestCase
{

    public function tearDown()
    {
        Mockery::close();
    }

    public function testGetsAverageRainfall()
    {
        $institute = Mockery::mock('\Path\To\Institute');
        $institute->shouldReceive('getLastRainfall')
        ->times(5)
        ->andReturn(70, 90, 150, 20, 50);

        $raifallObj = new Rainfall($institute);
        $this->assertEquals(76, 
            $raifallObj->getLastRainfall());
        $this->assertFalse(100, 
            $raifallObj->getLastRainfall());
    }

}

Não deixe o teste para o fim.

Sobre seus testes:

  • Precisam ser simples.
  • Precisam fornecer feedbacks rápidos sobre o funcionamento ou não da aplicação.
  • Precisam ser independentes.
  • O código do seu teste precisa ser fácil de ser lido. Use as boas práticas de programação e da linguagem.

Sobre seus testes:

  • Ajudam no design de classes do seu projeto.
  • Informam se uma determinada classe possui responsabilidades demais. Suas classes precisam ser Coesas.
  • Informam sobre o acoplamento entre suas classes. Classes muito acopladas são difíceis de serem testadas e evoluídas. Opte por outras implementações, como polimorfismo e interfaces.

Algumas pesquisas:

Artigo acadêmico de David Janzen, mostrando que uma equipe de desenvolvimento que utiliza TDD gasta menos tempo debugando, e a complexidade do código produzido era menor. 

 

Algumas Pesquisas:

Artigo acadêmico de Nagappan, mostrando um estudo de caso da Microsoft e da IBM. Este estudo mostrou que em 4 produtos, o número de erros caiu entre 40 a 90%, em comparação com os mesmos produtos, que não utilizavam TDD.

 

3.

Dicas e Referências

http://www.amazon.com.br/Test-Driven-Development-By-Example/dp/0321146530

https://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627

https://www.casadocodigo.com.br/products/livro-tdd-php

http://bit.ly/2xlNt3r

Vamos evoluir a qualidade de nossos projetos!

Escreva testes.

E agora ... a parte mais importante da palestra.

@yanmagale

www.yanmagalhaes.com.br

Obrigado Galera \o/

https://slides.com/yanmagale/testes-pra-que-te-quero-donuz

Testes pra que te quero

By Yan Magalhães

Testes pra que te quero

Palestra sobre Testes Unitários e TDD, apresentada na semana das Crianças na Donuz, em Betim, 06/10/2017

  • 1,160