Skip to content

Manual do Desenvolvedor – Testes Unitários

Vítor E. Silva Souza edited this page Aug 4, 2020 · 2 revisions

A realização de testes é uma boa prática para garantir a integridade da aplicação. Nesta seção do manual, focaremos nos testes unitários e iremos exemplificar como eles validam código existente no Marvin. Em alguns desses testes, uma validação mais simples pode ser suficiente, entretanto, alguns métodos irão demandar uso de algumas técnicas específicas, como por exemplo o uso de mocks.

Os testes unitários, como o próprio nome indica, focam numa única unidade de código, ou seja, uma única classe. Idealmente, você tem um teste para cada método significativamente complexo de cada classe do seu sistema. Desta maneira, se alguém faz alguma alteração em alguma classe e isso compromete o funcionamento de um destes métodos, os testes indicam imediatamente uma falha que deve ser corrigida. Simularemos esta situação ao longo desta seção do manual.

Mais especificamente, os Mock Tests são testes nos quais criam-se objetos falsos chamados mocks e estes objetos servirão para simular o comportamento de objetos reais da aplicação. Por exemplo, o teste unitário de uma classe de aplicação não pode chamar o DAO (classe de persistência) e acessar o banco de dados, pois desta forma deixaria de ser um teste unitário. Utilizando o conceito de mock, é possível simular o uso do DAO e validar apenas as funcionalidades da classe que está sendo testada.

Para nos auxiliar na criação de testes, utilizaremos os frameworks JUnit e Mockito, cujas dependências já estão incluídas no pom.xml 🔗 do Marvin. A seguir, serão exemplificadas a criação de testes nos quatro pacotes de código-fonte da arquitetura do Marvin: controlador, aplicação, domínio e persistência.

Onde ficam e como executar os testes?

Antes de começar a implementar testes, é preciso primeiro saber que há uma pasta de código-fonte (Source Folder, como o Eclipse chama) separada para testes, localizada no diretório src/test/java. Dentro desta pasta, os testes ficam no mesmo pacote das classes que estão sendo testadas. Como não pode ter duas classes com mesmo nome qualificado (pacote + classe), há uma convenção de incluir o termo Test no nome da classe de teste.

Assim, por exemplo, em src/main/java há a classe de domínio br.ufes.informatica.marvin.research.domain.Venue 🔗. Para testá-la, em src/test/java há a classe br.ufes.informatica.marvin.research.domain.VenueTest 🔗.

Para executar os testes, basta clicar com o botão direito numa classe de testes e escolher a opção Run As > JUnit Test. Você pode também executar todos os testes de um mesmo pacote clicando com o botão direito no pacote e escolhendo esta opção, bem como executar todos os testes do projeto inteiro clicando com o botão direito na pasta src/test/java ou na raiz do projeto e escolher esta mesma opção. O resultado da execução dos testes é exibido na visão JUnit, que será exemplificada a seguir.

Testando as classes de domínio

Na camada de domínio, criaremos o teste mais simples do manual. Testaremos o método setCategory(String) da classe Venue 🔗, que faz parte do módulo research, como mostram a figura e o código-fonte abaixo:

package br.ufes.informatica.marvin.research.domain;

@Entity
public class Venue extends PersistentObjectSupport implements Comparable<Venue> {
  public void setCategory(String category) {
    if (category.equals("Conference"))
      this.category = VenueCategory.CONFERENCE;
    else
      this.category = VenueCategory.JOURNAL;
  }

  /* ... */
}

Como podemos ver, a classe Venue encontra-se no pacote br.ufes.informatica.marvin.research.domain. O primeiro passo para se criar um teste é criar um pacote com esse mesmo nome no diretório de código-fonte de testes, caso ele já não exista: clique com o botão direito em src/test/java, escolha a opção New > Package, use como nome do pacote br.ufes.informatica.marvin.research.domain e clique Finish. Como resultado, temos o pacote criado:

Com o pacote criado na pasta de testes, podemos criar a classe que conterá os testes unitários. Para isso, clique com o botão direito no pacote br.ufes.informatica.marvin.research.domain, escolha New > Class, dê o nome VenueTest à nova classe e clique Finish.

Com a classe criada, podemos enfim escrever nossos testes unitários. Como a classe Venue não possui nenhuma dependência de alguma outra classe que faça acesso a banco de dados ou alguma função mais complexa, não será necessário utilizarmos objetos mock, apenas os recursos do JUnit serão suficientes. Implementamos, então, dois testes simples na classe VenueTest 🔗:

package br.ufes.informatica.marvin.research.domain;

public class VenueTest {
  private Venue venue;

  @Before
  public void setup() {
    venue = new Venue();
  }

  @Test
  public void testSetCategoryToConference() {
    String category = "Conference";
    venue.setCategory(category);
    assertEquals(VenueCategory.CONFERENCE, venue.getCategory());
  }

  @Test
  public void testSetCategoryToJournal() {
    String category = "Not a conference!";
    venue.setCategory(category);
    assertEquals(VenueCategory.JOURNAL, venue.getCategory());
  }
}

Primeiramente, precisamos criar o objeto Venue, que será o responsável por chamar o método que queremos testar. Isso é feito no método setup(), anotado com a tag @Before. Esta tag indica que o método anotado por ela será executado antes de cada cenário de teste. No método apenas inicializamos nosso objeto para ser usado nos cenários.

A tag @Test indica que o método anotado por ela é um cenário de teste. Podem existir quantos cenários forem necessários em uma mesma classe de teste. A convenção é começar o nome dos métodos de teste com o prefixo test e seguir com o que acontece naquele cenário, preferencialmente referenciando o nome do método que está sendo testado, como pode ser visto na listagem acima. Na sequência, vamos analisar os dois cenários criados nesta classe de teste.

O primeiro cenário é o seguinte: se eu passo a string "Conference" como parâmetro do método setCategory() em um objeto da classe Venue, o valor VenueCategory.CONFERENCE deve ser atribuído ao atributo category daquele objeto Venue. Portanto, o método de testes testSetCategoryToConference() cria a string com o valor "Conference" e chama o método setCategory() no objeto venue criado no setup() passando a string como parâmetro, normalmente. Por fim, ele valida se o objeto venue recebeu devidamente a categoria esperada utilizando o método assertEquals() do JUnit, que como primeiro parâmetro recebe um valor esperado e no segundo parâmetro, o valor atual de um objeto. Caso os valores coincidam, o teste passará. Caso contrário, o teste falhará.

O segundo cenário (testSetCategoryToJournal()) é análogo, se baseando no comportamento do método setCategory() da classe Venue de classificar qualquer outra string diferente de "Conference" como VenueCategory.JOURNAL.

Ao executar o teste (clicando com o botão direito na classe e escolhendo Run as > JUnit Test), a visão JUnit deverá abrir com a resposta sobre a validação dos dois testes incluídos nesta classe:

A barra verde indica que todos os nosso cenários passaram. Cada cenário também possui um ícone próprio indicando sua validade.

Vamos agora simular um erro sendo inserido na classe Venue. Se, por acaso, mudarmos na implementação original do método a string "Conference" para, por exemplo, "Conferense":

public void setCategory(String category) {
  if (category.equals("Conferense"))
    this.category = VenueCategory.CONFERENCE;
  else
    this.category = VenueCategory.JOURNAL;
}

Da próxima vez que executarmos os testes unitários, obteremos uma falha no primeiro cenário da classe VenueTest, nos indicando que nossa última ação inseriu um problema na base de código do Marvin:

A aba Failure Trace indicará o que houve de errado com os testes. Em nosso caso, era esperado o valor CONFERENCE, mas o valor encontrado foi JOURNAL, pois para qualquer string diferente de "Conference" (ex.: "Conference", erro inserido propositalmente na classe Venue) faz com que o valor atribuído ao atributo category seja VenueCategory.JOURNAL.

Note que a classe de testes não estende nenhuma outra classe, porém utiliza o método assertEquals() do JUnit como se fosse dela. Na verdade, este método pertence à classe org.junit.Assert e está sendo importado de forma estática (import static org.junit.Assert.assertEquals;), como pode ser visto no código completo da classe VenueTest 🔗. Alguns detalhes das classes são omitidos nas listagens de código do manual.

Testando as classes de aplicação

Na camada de aplicação, criaremos um teste em que vamos necessitar do uso da técnica de mock. O método que será testado se chama setNewPassword(), da classe ChangePasswordServiceBean do módulo core e tem como objetivo realizar a troca de senha de um usuário. Para tal, ele precisa recuperar esse usuário do banco de dados, fazer as alterações necessárias e depois salvá-lo novamente:

package br.ufes.informatica.marvin.core.application;

@Stateless
public class ChangePasswordServiceBean implements ChangePasswordService {
  @Override
  public void setNewPassword(String passwordCode, String password)
      throws InvalidPasswordCodeException, OperationFailedException {
    try {
      // Retrieves the academic given her password code.
      logger.log(Level.INFO, "Setting a new password for academic with password code: {0}",
          new Object[] {passwordCode});
      Academic academic = academicDAO.retrieveByPasswordCode(passwordCode);

      // Sets the new password, removes the password code.
      academic.setPassword(TextUtils.produceBase64EncodedMd5Hash(password));
      academic.setPasswordCode(null);

      // Saves the academic.
      academicDAO.save(academic);
    }

    // In case the password code fails to retrieve a single academic, report it as invalid.
    catch (PersistentObjectNotFoundException | MultiplePersistentObjectsFoundException e) {
      logger.log(Level.WARNING,
          "Unable to retrieve an academic with password code: " + passwordCode, e);
      throw new InvalidPasswordCodeException(e, passwordCode);
    }

    // In case the password cannot be encoded.
    catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
      // Logs and rethrows the exception for the controller to display the error to the user.
      logger.log(Level.SEVERE, "Could not find MD5 algorithm for password encription!", e);
      throw new OperationFailedException(e);
    }
  }

  /* ... */
}

Na implementação percebemos que o atributo academicDAO é responsável por resgatar um usuário do banco de dados e também por salvá-lo no mesmo. Logo, precisamos fazer o mock desta classe, para simular o seu comportamento em nosso teste unitário.

De forma análoga ao teste que criamos para a classe de domínio anteriormente, criamos o pacote br.ufes.informatica.marvin.core.application na pasta de testes src/test/java e, dentro deste pacote, criamos a classe de testes ChangePasswordServiceBeanTest com um cenário de testes, conforme mostrado na listagem abaixo:

package br.ufes.informatica.marvin.core.application;

@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class ChangePasswordServiceBeanTest {
  @Mock
  private AcademicDAO academicDAO;

  @InjectMocks
  private ChangePasswordServiceBean changePasswordServiceBean;

  private String password = "senha";

  private String passwordCode = "83997689-22b6-4a7e-a801";

  @Test
  public void testSetNewPasswordSuccess()
      throws InvalidPasswordCodeException, OperationFailedException,
      PersistentObjectNotFoundException, MultiplePersistentObjectsFoundException,
      NoSuchAlgorithmException, UnsupportedEncodingException {


    // Create a simulated academic on database
    Academic academic = new Academic();
    academic.setPassword("oldpasswd");
    academic.setPasswordCode(passwordCode);

    // Expected password
    String expectedPassword = TextUtils.produceBase64EncodedMd5Hash(password);

    // Tells how "retrieveByPasswordCode should behave itself
    when(academicDAO.retrieveByPasswordCode(passwordCode)).thenReturn(academic);

    // Calls testing method
    changePasswordServiceBean.setNewPassword(passwordCode, password);

    // Evaluation
    assertEquals(expectedPassword, academic.getPassword());
    assertEquals(null, academic.getPasswordCode());
    verify(academicDAO, times(1)).save(academic);
  }
}

A primeira diferença que percebemos em relação ao teste anterior é o uso da tag @RunWith. Ela é responsável por fazer o teste rodar com o Mockito e também por inicializar nossos mocks.

A tag @Mock indica que abaixo dela será declarado um objeto mock, no exemplo a propriedade academicDAO do tipo AcademicDAO. O Mockito irá criar uma cópia da estrutura dessa classe, com retornos padronizados. Não é necessário, portanto, inicializar esse objeto.

A tag @InjectMocks indica que abaixo dela será declarada a classe que queremos testar. Uma instância real da classe será criada e serão injetadas dependências em todos os objetos mock declarados. Portanto também não é necessário instanciar este objeto.

É importante ressaltar que as exceções lançadas no método que queremos testar (ChangePasswordServiceBean.setNewPassword()) também devem ser declaradas (com a palavra-chave throws) no método de teste (ChangePasswordServiceBeanTest.testSuccessfullyChangePassword()), assim como as possíveis exceções de chamadas de métodos dentro da implementação. Assim, podemos evitar erros do tipo unhandled exception no JUnit.

No método que queremos testar, um objeto do tipo Academic é recuperado através do seu passwordCode. O passwordCode é um código aleatório e temporário, servindo apenas no escopo do próprio método (e anulado logo em seguida). Após isso, é gerada uma senha em MD5 e atribuída ao academico (mais especificamente, seu atributo password), que é posteriormente salvo no banco de dados.

É importante que conheçamos como nossa aplicação funciona para podermos testá-la corretamente. Podemos agora então comentar sobre a implementação de nosso teste unitário:

  1. Os atributos password e passwordCode, são, respectivamente, a nova senha que queremos atribuir, e o código temporário que o objeto academic receberá para ser resgatado do banco de dados:
  private String password = "senha";
  private String passwordCode = "83997689-22b6-4a7e-a801";
  1. Criamos agora um objeto academic, que simulará um acadêmico que terá sua senha trocada. Lhe atribuímos uma senha antiga, bem como o passwordCode que definimos anteriormente:
  Academic academic = new Academic();
  academic.setPassword("oldpasswd");
  academic.setPasswordCode(passwordCode);
  1. Sabemos que nosso programa gera uma senha em MD5 para atribuir ao acadêmico, logo, podemos então criar uma variável que armazenará a senha esperada, que será gerada com base na nova senha, que declaramos anteriormente:
  String expectedPassword = TextUtils.produceBase64EncodedMd5Hash(password);
  1. Dentro do método que queremos testar, uma requisição ao banco de dados é realizada e é retornado um objeto do tipo Academic. O objeto responsável por chamar esse método é o academicDAO. Mas no nosso caso, não temos um banco de dados, mas podemos dizer ao nosso programa o que fazer quando o método retrieveByPasswordCode() for chamado pelo academicDAO, que no em nosso teste, é um mock. O parâmetro passado ao método when() indica qual método chamado pela classe que está sendo testada queremos tratar. Já o parâmetro passado dentro de thenReturn() é o objeto que o mock irá retornar, fingindo ser um AcademicDAO de verdade. Neste caso, é o próprio objeto que criamos em nosso cenário de teste:
  when(academicDAO.retrieveByPasswordCode(passwordCode)).thenReturn(academic);
  1. Após isso, chamamos o método que queremos testar normalmente, passando como parâmetro nossos valores declarados:
  changePasswordServiceBean.setNewPassword(passwordCode, password);
  1. Por fim, fazemos a validação, que é o que determina se nosso teste passará ou não:
  assertEquals(expectedPassword, academic.getPassword());
  assertEquals(null, academic.getPasswordCode());
  verify(academicDAO, times(1)).save(academic);

O primeiro assertEquals() irá validar que a senha esperada foi devidamente atribuida ao objeto academic. Já o segundo, validará se o passwordCode desse academic é nulo, pois é o que acontece no método original após uma bem-sucedida troca de senha. Por fim, o verify() é responsável por validar se algum objeto chamou algum método. O método times() é opcional e serve para indicar a quantidade de chamadas que queremos verificar. No nosso caso, queremos verificar que o método save() foi chamado apenas uma única vez.

Como exercício, experimente executar este teste e observe o resultado. Insira propositalmente algum erro de lógica na classe ChangePasswordServiceBean e observe se o teste falha. Note que faltam cenários que não foram implementados nesta classe de testes (no momento da escrita do manual): e se o DAO lança uma exceção PersistentObjectNotFoundException ou MultiplePersistentObjectsFoundException? E se há problemas na codificação MD5 resultando em NoSuchAlgorithmException ou UnsupportedEncodingException? Estes também podem ser criados como exercício.

Testando as classes de controle

Seguindo com o uso de mock, o método que testaremos nessa camada tem como objetivo checar se um código que foi recebido por e-mail é válido e iniciar o processo de troca de senha de um acadêmico. Na classe ChangePasswordController 🔗 do pacote core, testaremos o método checkCode():

package br.ufes.informatica.marvin.core.controller;

@Named
@ConversationScoped
public class ChangePasswordController extends JSFController {
  /** The JSF conversation. */
  @Inject
  private Conversation conversation;

  /** The "Change Password" service. */
  @EJB
  private ChangePasswordService changePasswordService;

  /**
   * Checks the code the academic received via e-mail (loaded as a parameter in the URL) and begins
   * the password changing process.
   */
  public void checkCode() {
    logger.log(Level.INFO, "Checking code: {0}", passwordCode);

    // First check if a password code was given at all, then check if it's valid.
    validCode = passwordCode != null;
    if (validCode)
      try {
        // Attempt to find the academic with the given password code.
        academic = changePasswordService.retrieveAcademicByPasswordCode(passwordCode);
        validatedPasswordCode = passwordCode;

        // If all went well, begin the conversation (if one hasn't begun already).
        logger.log(Level.INFO, "Beginning conversation. Current conversation transient? -> {0}",
            new Object[] {conversation.isTransient()});
        if (conversation.isTransient())
          conversation.begin();
      }

      // If an academic could not be found, deem the code invalid.
      catch (InvalidPasswordCodeException e) {
        validCode = false;
      }
  }

  /* ... */
}

No método, percebemos que o objeto changePasswordService realiza um acesso ao banco de dados para recuperar um acadêmico específico. Portanto, esse objeto em nosso teste deverá ser um mock. Precisamos também, validar que o atributo validatedPasswordCode recebeu sua atribuição correta (passwordCode), porém, esse atributo é privado e devemos usar uma técnica específica para conseguir acessá-lo. Também devemos verificar que a chamada do método conversation.begin() ocorreu, ou não, a depender da condição anteriormente imposta. Por fim, é preciso testar o cenário em que o changePasswordService retorna uma exceção indicando que o código especificado é inválido (InvalidPasswordCodeException).

Criamos então o pacote br.ufes.informatica.marvin.core.controller na pasta de testes src/test/java e, dentro deste pacote, criamos a classe de testes ChangePasswordControllerTest 🔗 com três cenários de testes, conforme mostrado na listagem abaixo:

package br.ufes.informatica.marvin.core.controller;

@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class ChangePasswordControllerTest {
  @Mock
  private ChangePasswordService changePasswordService;

  @Mock
  private Conversation conversation;

  @InjectMocks
  private ChangePasswordController changePasswordController;

  private Academic academic;

  private String passwordCode;

  private String expectedPasswordCode;

  private Field field;

  @Before
  public void setup() throws NoSuchFieldException, SecurityException {
    academic = new Academic();
    passwordCode = "a4bn6-sd5465-br4fd";
    changePasswordController.setPasswordCode(passwordCode);

    // Using reflection, we can get the value of "validatedPasswordCode", that is a private field.
    field = ChangePasswordController.class.getDeclaredField("validatedPasswordCode");
    field.setAccessible(true);
  }

  @Test
  public void testCheckCodeSuccessWithTransientConversation()
      throws InvalidPasswordCodeException, IllegalArgumentException, IllegalAccessException {

    // Behavior of methods
    when(changePasswordService.retrieveAcademicByPasswordCode(passwordCode)).thenReturn(academic);
    when(conversation.isTransient()).thenReturn(true);

    // Calling the method being tested
    changePasswordController.checkCode();

    // getting the value of "validatedPasswordCode", should be after the testing method
    expectedPasswordCode = (String) field.get(changePasswordController);

    // Evaluation
    assertEquals(passwordCode, expectedPasswordCode);
    assertTrue(changePasswordController.isValidCode());
    verify(changePasswordService, times(1)).retrieveAcademicByPasswordCode(passwordCode);
    verify(conversation, times(1)).begin();
  }

  @Test
  public void testCheckCodeSuccessWithLongRunningConversation()
      throws InvalidPasswordCodeException, IllegalArgumentException, IllegalAccessException {

    // Behavior of methods
    when(changePasswordService.retrieveAcademicByPasswordCode(passwordCode)).thenReturn(academic);
    when(conversation.isTransient()).thenReturn(false);

    // Calling the method being tested
    changePasswordController.checkCode();

    // getting the value of "validatedPasswordCode", should be after the testing method
    expectedPasswordCode = (String) field.get(changePasswordController);

    // Evaluation
    assertEquals(passwordCode, expectedPasswordCode);
    assertTrue(changePasswordController.isValidCode());
    verify(changePasswordService, times(1)).retrieveAcademicByPasswordCode(passwordCode);
    verify(conversation, times(0)).begin();
  }

  @Test
  public void testCheckCodeFailWithInvalidCode()
      throws InvalidPasswordCodeException, IllegalArgumentException, IllegalAccessException {

    // Behavior of methods
    when(changePasswordService.retrieveAcademicByPasswordCode(passwordCode))
        .thenThrow(new InvalidPasswordCodeException(passwordCode));

    // Calling the method being tested
    changePasswordController.checkCode();

    // getting the value of "validatedPasswordCode", should be after the testing method
    expectedPasswordCode = (String) field.get(changePasswordController);

    // Evaluation
    assertNull(expectedPasswordCode);
    assertFalse(changePasswordController.isValidCode());
    verify(changePasswordService, times(1)).retrieveAcademicByPasswordCode(passwordCode);
    verify(conversation, times(0)).begin();
  }
}

No início de nossa classe criamos nosso objetos mock e os objetos que servirão de auxílio para validarmos nosso teste. O objeto field servirá para acessarmos um atributo que, na classe sendo testada, é privado.

No método setup(), que será executado antes de todos os nossos cenários, podemos atribuir um valor arbitrário de passwordCode para o nosso controlador, pois o mesmo é verificado na classe de aplicação e, portanto, está fora do escopo deste teste unitário. Além disso, também inicializamos o atributo field para determinar qual atributo privado queremos acessar, usando o seguinte comando:

  field = ChangePasswordController.class.getDeclaredField("validatedPasswordCode");

E, para torná-lo acessível:

  field.setAccessible(true);

Dessa maneira, já temos condições de acessar o atributo validatedPasswordCode após a execução do nosso método de teste.

Finalizado o setup(), podemos implementar os cenários de teste. Dois cenários testam o uso bem sucedido do método para verificar o código e a única diferença entre eles é o retorno do método conversation.isTransient(). No primeiro cenário é retornado true (ainda não há conversação de longa duração), e no segundo false (já havia uma conversação de longa duração), mas a verificação do código acontece nas duas situações. O terceiro cenário simula a classe de aplicação tendo retornado uma exceção InvalidPasswordCodeException, indicando que o código fornecido pelo usuário é inválido (não confere com o que há no banco de dados).

Vamos descrever em mais detalhes o primeiro cenário de testes, i.e., o método testCheckCodeSuccessWithTransientConversation(). Primeiro, determinamos o comportamento dos métodos chamados por nossos objetos mock. Para a chamada retrieveAcademicByPasswordCode(), podemos apenas retornar o objeto acadêmico que inicializamos no setup(). Para o método isTransient() retornamos true neste cenário.

Chamamos então o método sendo testado:

  changePasswordController.checkCode();

Logo após a chamada deste método, podemos acessar a variável validatedPasswordCode, que tornarmos acessível anteriormente, por meio da seguinte chamada usando reflexão:

  expectedPasswordCode = (String) field.get(changePasswordController);

Como pode ser visto na linha acima, o valor é armazenado na variável expectedPasswordCode, para podermos usa-lo em fins de validação, na última etapa do teste. Nesta última etapa, verificamos que expectedPasswordCode recebeu seu devido valor na chamada do método de teste e que os objetos mock realmente chamaram os métodos corretos e na quantidade de vezes corretas.

O último cenário, implementado pelo método testCheckCodeFailWithInvalidCode(), tem como diferença o comportamento dos mocks (é preciso apenas dizer que ao chamar o método da classe de aplicação será retornada uma exceção) e a avaliação ao final (expectedPasswordCode será nula, isValidCode() será false e a conversação não será iniciada, assim como acontece no segundo cenário).

Testando as classes de persistência

As classes de persistência no Marvin são DAOs que apenas isolam código de acesso a dados num local centralizado (por classe de domínio), sem lógica de negócio, que deve estar em outra camada (pacotes de aplicação e domínio). Sendo assim, não faz sentido fazer testes unitários em DAOs.

Como um experimento, criamos a classe AcademicJPADAOTest 🔗 para testar métodos da classe AcademicJPADAO 🔗. Se você abrir o código fonte desta classe de testes, no entanto, vai ver que a forma de testar se um DAO que utiliza a API de Critérios do JPA é testar cada passo da construção da consulta e fica muito similar ao código original sendo testado, o que realmente não tem muito propósito. Portanto, deixamos esta classe (AcademicJPADAOTest) apenas como exemplo, mas não é necessário criar testes unitários para seus DAOs.

No futuro, podemos investir esforços em criar testes de integração para os DAOs, criando uma base de dados falsa com alguns dados predefinidos, efetuando as consultas e verificando se o resultado é o que esperamos. No momento, no entanto, o Marvin está focado apenas em testes unitários.

➡️ Implementação: desenvolvimento do front-end

⬆️ Voltar ao índice