Testes unitários - ifspcodelab/gestao-estagios-backend GitHub Wiki

O que são testes unitários?

Os testes unitários conferem se as pequenas partes de códigos em um projeto estão funcionando da maneira esperada, como os métodos presentes nas classes. Lembrando que deve ser testados os métodos de forma individual, sem fazer a integração entre eles.

Qual o objetivo dos testes?

Automatizar a verificação da consistência de um programa, que muitas vezes pode ser demorado e bastante custoso para um projeto, além de possibilitar a diminuição de ocorrências de erros em um sistema.

Ferramentas a serem utilizadas

Frameworks Mockito e JUnit, além da biblioteca AssertJ.

O que é o Mockito?

Um framework Java que ajuda na realização de testes. Ele é muito importante pois simula os métodos e as instâncias das classes. Essa simulação é chamada de mock, que pode ser traduzido como "zombaria", é um nome interessante, pois realmente é o que acontece, quando se utiliza o mock para instanciar uma classe, ela "pensa" que seus métodos realmente foram chamados, o que na realidade não acontece.

Principais funcionalidades

  • Mock: cria uma instancia da classe de forma "mockada", como foi dito acima, será possível chamar os métodos, mas eles não serão invocados de maneira real. Exemplo: na classe CampusServiceImpl há uma dependência da classe CampusRepository, portanto na classe de testes do service, será necessário usar a anotação @Mock, para simular a instância da classe.

    @Mock
    private CampusRepository campusRepository;
    
  • InjectMocks: muitas vezes as classes terão dependências, para sinaliza-las é necessário usar a anotação @injectMocks, dessa forma na classe que utilizar essa anotação será injetada as dependências anotadas com o @Mock.

    @Mock
    private CampusRepository campusRepository;
    
    @InjectMocks
    private CampusServiceImpl campusService;
    
  • Verify: verifica se teve ou não alguma interação com os métodos implementados, além disso é possível verificar quantas vezes a operação ocorreu.

    verify(campusRepository, times(1)).save(any(Campus.class));

    Verifica se o método save foi chamado pelo menos uma vez com qualquer objeto da classe Campus.

  • When: utilizado para configurar o que acontece na chamada de um método, como o seu retorno.

    when(campusRepository.existsByAbbreviation(any(String.class))).thenReturn(true);

    Quando o método existsByAbbreviation da classe CampusRepository, passando como argumento qualquer valor da classe String for chamado, ele retornará o valor true.

O que é o JUnit?

Um framework Java, assim como o Mockito, visa contribuir para a execução de testes unitários. Com ele é possível automatizar os processos de testagem de todos os testes quando uma nova versão estável do sistema for lançada, ou até mesmo a cada commit realizado, garantindo assim, a estabilidade e integridade do sistema após novas mudanças.

Principais funcionalidades

  • @ExtendWith: determina extensões a serem utilizadas dentro da classe de testes
@ExtendWith(MockitoExtension.class)
public class CampusServiceTest {}
  • @Test: determina que será um método de teste
@Test
public void createCampus() {}
  • @ParameterizedTest: com essa anotação é possível rodar os testes múltiplas vezes com valores diferente**
  • @ValueSource: utilizado em conjunto com o @ParameterizedTest, com ele é possível determinar um array de valores a serem testados.
 @ParameterizedTest
    @ValueSource(ints = {0, -2})
    public void testSetLadoDoesNotAssignValueWhenLadoEqualsOrLessThanZero(int lado) {

        assertThrows(IllegalArgumentException.class, () -> new Quadrado(lado));
    }

  • @BeforeEach: determina que o método devera ser executado antes de cada @Test ou @ParemeterizedTest
  @BeforeEach
    public void setUp() {
        campusService = new CampusServiceImpl(campusRepository);
        campusService.setCityService(cityService);
        campusService.setDepartmentService(departmentService);
    }

Biblioteca AssertJ

Principais funcionalidades

  • assertThat: utilizado para comparar objetos, normalmente vem acompanhado de outros métodos, como isEqualsTo, isTrue.
  • isEqualTo: verifica se dois valores são iguais.
assertThat(campusFound.getId()).isEqualTo(campus.getId());
  • isTrue: verifica se o valor é verdadeiro.
 assertThat(result).isTrue();
  • isFalse: verifica se o valor é falso.
 assertThat(result).isFalse();
  • isNotNull: verifica se um objeto não é nulo.
assertThat(campusFound).isNotNull();
  • assertThatThrownBy: verifica se um método chama uma exceção.
  • isInstanceOf: utilizado em conjunto com o assertThatThrownBy para verificar qual é a exceção lançada.
assertThatThrownBy(() -> campusService.create(sampleCampusCreateDto(campus)))
                .isInstanceOf(CampusAlreadyExistsByEmailException.class);

Quais testes devem ser feitos?

Devem ser criados testes para todos os possíveis comportamentos do método, como quando ele realiza a ação corretamente, lança uma exceção, etc.

Como fazer os testes

Primeiro, é necessário configurar a classe de teste para que os testes possam ser executados com o Mockito. Isso pode ser feito ao adicionar a anotação @RunWith referenciando a classe MockitoJUnitRunner.class na classe de teste. Veja o exemplo abaixo:

@ExtendWith(MockitoExtension.class)
public class CampusServiceTest

Na sequência, é necessário mockar as classes que são as dependências da que será testada. Para tanto, basta adicionar a anotação @Mock, tal como é mostrado a seguir:

    @Mock
    private CampusRepository campusRepository;
    @Mock
    private CityService cityService;
    @Mock
    private DepartmentService departmentService;

Essas classes foram mockadas por conta de serem as dependências da classe CampusServiceImpl, o que pode ser observado a seguir:

   @Autowired
    public void setCityService(CityService cityService) {
        this.cityService = cityService;
    }

    @Autowired
    public void setDepartmentService(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    @Autowired
    public void setCampusRepository(CampusRepository campusRepository) {
        this.campusRepository = campusRepository;
    }

Depois é necessário construir os testes de fato. Para tanto, basta adicionar a anotação @Test no método de teste. Veja um exemplo a seguir:

@Test
public void findAllByStatus(){
   State state = StateFactoryUtils.sampleState();
   City city = CityFactoryUtils.sampleCity(state);
   Campus campus = CampusFactoryUtils.sampleCampus(city);
   when(campusRepository.findAllByStatus(any(EntityStatus.class))).thenReturn(List.of(campus));

   List<Campus> campusFound = campusService.findAllByStatus(campus.getStatus());

   assertThat(campusFound).hasSize(1);
}

No teste acima é criado o objeto campus, sendo que o mesmo estará contido em uma lista retornada pelo método findAllByStatus quando este receber um EntityStatus qualquer. Observe que o comportamento do objeto mockado está sendo definido pelo método when. Além disso, o campus obtido na chamada do método findAllByStatus, que será apenas o campus criado anteriormente, será colocado em uma lista, a qual sofrerá uma verificação se possui o tamanho especificado.

Boas práticas

Uma boa forma de escrever os testes de unidade é seguir o padrão AAA. Nesse padrão, o teste unitário deve seguir três etapas: Arrange (Preparar o teste), Act (Rodar o teste) e Assert (Verificar as asserções).

  • Na primeira etapa (Arrange) é feita a preparação de tudo que é necessário para que o teste possa ser executado. Então é nesse momento em que são simulados os comportamentos de um mock, inicializados objetos, declaradas as variáveis que serão passadas como parâmetros para o método a ser testado, etc.

  • Na segunda etapa (Act) é realizada a execução do método a ser testado, sendo passado para o mesmo as variáveis e objetos definidos anteriormente.

  • Na última etapa (Assert) é verificado se a operação realizada na etapa anterior surtiu o resultado esperado por meio dos asserts.

Exemplo de código no padrão AAA

No Arrange, são construídos os objetos state, city e campus e é configurado o comportamento do mock CampusRepository para que o método findAll do service funcione corretamente. No Act, o método a ser testado é executado e o seu resultado é colocado na lista de campus chamada campuses. No Assert, é verificado se a lista de campus possui o tamanho especificado.

 @Test
    public void findAll() {
        //Arrange
        State state = StateFactoryUtils.sampleState();
        City city = CityFactoryUtils.sampleCity(state);
        Campus campus = CampusFactoryUtils.sampleCampus(city);
        when(campusRepository.findAll()).thenReturn(List.of(campus));

        //Act
        List<Campus> campuses = campusService.findAll();

        //Assert
        assertThat(campuses).hasSize(1);
    }

Referências

⚠️ **GitHub.com Fallback** ⚠️