domingo, 20 de junho de 2021

Criando endpoints pensando em TDD - desenvolvimento de softwares guiado por Testes

 TDD é a sigla para Test Driven Development, que em português significa Desenvolvimento Orientado por Testes. Esse é um método de desenvolvimento muito comum atualmente. Ele se baseia na aplicação de pequenos ciclos de repetições. Em cada um deles, um teste é aplicado.

Basicamente é o que o nome diz, o desenvolvimento de softwares guiado por Testes. Vale dizer que isso começou com o Kent Beck, que foi o criador do TDD.

Fiz esse desenho para ilustrar melhor sobre:





E a ideia é exatamente essa mesmo, você escrever primeiro o teste unitário no seu código fonte, antes mesmo de escrever a funcionalidade em si. Ou seja:

1- Escreva um teste que vai falhar (exatamente porque a funcionalidade ainda nem existe).

2- Escreva a funcionalidade em si, com o código que vai fazê-la funcionar

3- Refatore o seu código, eliminando redundâncias, deixando ele mais elegante e etc…


Hands on

Chega de resumir e vamos colocar a mão na massa. Bom, vou criar um endpoint simples com Spring que o único objetivo é retornar uma lista e outro método em uma service que o objetivo é saber se uma idade que será passada como parâmetro é maior de idade ou não.

Vamos ao nosso primeiro endpoint, lembrando que primeiro vamos criar uma classe chamada Usuário, ela ainda não faz parte da nossa funcionalidade em si, mas será uma classe importante:


import lombok.Builder;

import lombok.Data;

@Data

@Builder

public class Usuario {

    private String nome;

    private String email;

    private int idade;

}


Vamos adicionar ao nosso projeto essas duas dependências:

<!--junit-->

<dependency>

               <groupId>junit</groupId>

                                            <artifactId>junit</artifactId>

                              </dependency>

                              <!--mockito-->

                              <dependency>

                                            <groupId>org.mockito</groupId>

                                            <artifactId>mockito-core</artifactId>

</dependency>


Feito isso, vamos criar a nossa UsuarioController da seguinte forma:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("usuario")
public class UsuarioController {
       
}

Repare que ela está vazia, sem implementação alguma e de fato essa é a ideia!

Agora vamos criar o teste da nossa controller:


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@WebMvcTest(UsuarioController.class)
public class UsuarioControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testarListaUsuario() throws Exception {
        this.mockMvc.perform(MockMvcRequestBuilders.get("/usuario")) //teste para acessar o endpoint
                .andDo(MockMvcResultHandlers.print()) // print do resultado do teste
                .andExpect(MockMvcResultMatchers.jsonPath("$").isArray()); // verificando se o endpoint retorna um array
    }

Repare que nós já escrevemos o teste do que o nosso endpoint tem que fazer (retornar uma lista), porém ainda nem criamos a funcionalidade em si, por isso quando rodamos o teste, vai dar erro mesmo:






Perfeito! Agora que já sabemos o que o nosso endpoint tem que fazer e inclusive testamos isso, vamos criar de fato a funcionalidade:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("usuario")
public class UsuarioController {

    @GetMapping
    public List<Usuario> listaUsuario() {
        List<Usuario> listaUsuarios = new ArrayList<>();
        listaUsuarios.add(new Usuario("Nataniel", "nataniel.paiva@gmail.com", 29));
        return listaUsuarios;
    }

}

Esse é o passo 2, como a funcionalidade está criada, agora podemos rodar de fato o teste trazendo um resultado correto:

Agora, vamos ao passo 3, que é o de refatorar o código do nosso endpoint:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("usuario")
public class UsuarioController {

    @GetMapping
    public List<Usuario> listaUsuario() {
        return Arrays.asList(
                Usuario
                .builder()
                .nome("Nataniel")
                .email("nataniel.paiva@gmail.com")
                .idade(29).build());
    }

}

Agora no passo 3 podemos deixar o nosso código mais elegante! E a ideia do TDD é exatamente essa, você vai construindo as suas funcionalidades guiado pelos testes.

Agora vamos para a nossa segunda funcionalidade. Vamos precisar de uma Service com um método para validar se uma idade passada como parâmetro é maior de idade ou não.

Criamos apenas a “casca” da nossa service:

import org.springframework.stereotype.Service;

@Service
public class UsuarioService {
  
  public boolean verificaMaiorIdade(int idade){
        return true;
    }

}

Até criei o método, mas repare que ele apenas tem o retorno para não ter erro de compilação. Agora vamos para o nosso teste:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class UsuarioServiceTests {

    @InjectMocks
    public UsuarioService usuarioService;

    @Test
    public void verificaMaiorIdade() {
        Boolean teste = usuarioService.verificaMaiorIdade(18);
        Assert.assertTrue(teste);
    }

    @Test
    public void verificaMenorIdade() {
        boolean teste = usuarioService.verificaMaiorIdade(17);
        Assert.assertFalse(teste);
    }

}

Repare que um dos testes escritos vai trazer um falso positivo, porém estamos testando o que queremos de fato na funcionalidade. Então vamos criar a funcionalidade de fato.

import org.springframework.stereotype.Service;

@Service
public class UsuarioService {

    public boolean verificaMaiorIdade(int idade){
        boolean retorno = false;
        if(idade >= 18){
            retorno = true;
        }
        return retorno;
    }

}

Repare que agora os dois testes que fizemos irão passar. Agora vamos tentar melhorar o nosso código:

import org.springframework.stereotype.Service;

@Service
public class UsuarioService {

    public boolean verificaMaiorIdade(int idade){
        return idade >= 18;
    }

}





Nenhum comentário:

Postar um comentário