- Teste de integração de um banco de dados usando MariaDB para substituir MySql
- Implementação de aplicação multilíngue
- Salvando arquivos no aplicativo e dados sobre eles no banco de dados
Tipos de testes
O que é um teste? Como diz o Wiki: “ Um teste ou teste é uma forma de estudar os processos subjacentes de um sistema, colocando o sistema em diferentes situações e rastreando mudanças observáveis nele.” Ou seja, trata-se de um teste do correto funcionamento do nosso sistema em determinadas situações. Bem, vamos ver que tipos de testes existem:-
Testes unitários são testes cuja tarefa é testar cada módulo do sistema individualmente. É desejável que estas sejam peças minimamente divisíveis do sistema, por exemplo, módulos.
-
O teste de sistema é um teste de alto nível para testar a operação de uma parte maior de um aplicativo ou do sistema como um todo.
-
O teste de regressão é um teste usado para verificar se novos recursos ou correções de bugs afetam a funcionalidade existente do aplicativo e se bugs antigos reaparecem.
-
O teste funcional verifica a conformidade de parte do aplicativo com os requisitos declarados nas especificações, histórias de usuários, etc.
Tipos de testes funcionais:
- teste “caixa branca” para conformidade de parte da aplicação com os requisitos com conhecimento da implementação interna do sistema;
- Teste “caixa preta” para conformidade de parte da aplicação com os requisitos sem conhecimento da implementação interna do sistema.
- O teste de desempenho é um tipo de teste escrito para determinar a velocidade na qual um sistema ou parte dele é executado sob uma determinada carga.
- Teste de carga - testes projetados para verificar a estabilidade do sistema sob cargas padrão e encontrar o pico máximo possível no qual o aplicativo funciona corretamente.
- O teste de estresse é um tipo de teste projetado para verificar a funcionalidade de um aplicativo sob cargas fora do padrão e para determinar o pico máximo possível no qual o sistema não travará.
- Testes de segurança - testes usados para verificar a segurança de um sistema (contra ataques de hackers, vírus, acesso não autorizado a dados confidenciais e outras alegrias da vida).
- O teste de localização é um teste de localização para um aplicativo.
- O teste de usabilidade é um tipo de teste que visa verificar a usabilidade, compreensibilidade, atratividade e capacidade de aprendizagem para os usuários. Tudo isso parece bom, mas como funciona na prática? É simples: é usada a pirâmide de testes de Mike Cohn: Esta é uma versão simplificada da pirâmide: agora está dividida em partes menores. Mas hoje não vamos perverter e considerar a opção mais simples.
-
Unidade - testes unitários usados em várias camadas da aplicação, testando a menor lógica divisível da aplicação: por exemplo, uma classe, mas na maioria das vezes um método. Esses testes geralmente tentam isolar o máximo possível da lógica externa, ou seja, criar a ilusão de que o restante da aplicação está funcionando em modo padrão.
Deve haver sempre muitos desses testes (mais do que outros tipos), pois eles testam peças pequenas e são muito leves, não consumindo muitos recursos (por recursos quero dizer RAM e tempo).
-
Integração - teste de integração. Ele verifica partes maiores do sistema, ou seja, é uma combinação de várias partes da lógica (vários métodos ou classes), ou a correção de trabalhar com um componente externo. Geralmente há menos desses testes do que testes de unidade, pois são mais pesados.
Como exemplo de testes de integração, você pode considerar conectar-se a um banco de dados e verificar a execução correta dos métodos que trabalham com ele .
-
UI - testes que verificam o funcionamento da interface do usuário. Eles afetam a lógica em todos os níveis da aplicação, por isso também são chamados de ponta a ponta. Via de regra, são muito menos, portanto são os mais pesados e devem verificar os caminhos mais necessários (usados).
Na figura acima vemos a proporção das áreas das diferentes partes do triângulo: aproximadamente a mesma proporção é mantida no número desses testes no trabalho real.
Hoje daremos uma olhada mais de perto nos testes mais usados - testes unitários, já que todos os desenvolvedores Java que se prezem devem ser capazes de usá-los em um nível básico.
- material sobre cobertura de código no JavaRush e no Habré ;
- teoria fundamental dos testes .
- Estamos escrevendo nosso teste.
- Executamos o teste, tenha passado ou não (vemos que está tudo vermelho - não se desespere: é assim que deve ser).
- Adicionamos o código que deve satisfazer este teste (executamos o teste).
- Refatoramos o código.
- Especificando os dados a serem testados (acessórios).
- Usando o código em teste (chamando o método em teste).
- Verificando os resultados e comparando-os com os esperados.
assertEquals(Object expecteds, Object actuals)
— verifica se os objetos transmitidos são iguais.assertTrue(boolean flag)
— verifica se o valor passado retorna verdadeiro.assertFalse(boolean flag)
— verifica se o valor passado retorna falso.assertNull(Object object)
– verifica se o objeto é nulo.assertSame(Object firstObject, Object secondObject)
— verifica se os valores passados referem-se ao mesmo objeto.assertThat(T t, Matcher<T> matcher)
— verifica se t satisfaz a condição especificada no matcher.
Conceitos-chave de testes unitários
A cobertura de teste (cobertura de código) é uma das principais avaliações da qualidade dos testes de aplicações. Esta é a porcentagem de código que foi coberto pelos testes (0-100%). Na prática, muitas pessoas correm atrás desse percentual, o que não concordo, pois passam a adicionar testes onde não são necessários. Por exemplo, nosso serviço possui operações CRUD padrão (criar/obter/atualizar/excluir) sem lógica adicional. Esses métodos são puramente intermediários que delegam trabalho à camada que trabalha com o repositório. Nesta situação, não temos nada para testar: talvez se este método chama um método do Tao, mas isto não é sério. Para avaliar a cobertura do teste, geralmente são utilizadas ferramentas adicionais: JaCoCo, Cobertura, Clover, Emma, etc. Para um estudo mais detalhado deste assunto, guarde alguns artigos adequados:Estágios de teste
A prova consiste em três etapas:Ambientes de teste
Então agora vamos ao que interessa. Existem vários ambientes de teste (frameworks) disponíveis para Java. Os mais populares deles são JUnit e TestNG. Para nossa revisão, usamos: Um teste JUnit é um método contido em uma classe usado apenas para teste. Uma classe normalmente recebe o mesmo nome da classe que está testando, com +Test no final. Por exemplo, CarService → CarServiceTest. O sistema de compilação Maven inclui automaticamente essas classes na área de teste. Na verdade, essa classe é chamada de classe de teste. Vamos examinar um pouco as anotações básicas: @Test - definição deste método como método de teste (na verdade, o método marcado com esta anotação é um teste unitário). @Before – marca o método que será executado antes de cada teste. Por exemplo, preenchimento de dados de teste de classe, leitura de dados de entrada, etc. @After - colocado acima do método que será chamado após cada teste (limpeza de dados, restauração de valores padrão). @BeforeClass - colocado acima do método - análogo a @Before. Mas este método é chamado apenas uma vez antes de todos os testes de uma determinada classe e, portanto, deve ser estático. Ele é usado para realizar operações mais pesadas, como levantar um banco de dados de teste. @AfterClass é o oposto de @BeforeClass: executado uma vez para uma determinada classe, mas executado após todos os testes. Usado, por exemplo, para limpar recursos persistentes ou desconectar-se do banco de dados. @Ignore - observa que o método abaixo está desabilitado e será ignorado durante a execução geral dos testes. É utilizado em diversos casos, por exemplo, se o método base foi alterado e não houve tempo para refazer o teste. Nesses casos, também é aconselhável adicionar uma descrição - @Ignore("Alguma descrição"). @Test (expected = Exception.class) - usado para testes negativos. São testes que verificam como um método se comporta em caso de erro, ou seja, o teste espera que o método lance alguma exceção. Tal método é indicado pela anotação @Test, mas com um erro a ser detectado. @Test(timeout=100) - verifica se o método é executado em no máximo 100 milissegundos. @Mock - uma classe é usada sobre um campo para definir um determinado objeto como um mock (isso não é da biblioteca Junit, mas do Mockito), e se precisarmos, definiremos o comportamento do mock em uma situação específica , diretamente no método de teste. @RunWith(MockitoJUnitRunner.class) - o método é colocado acima da classe. Este é o botão para executar testes nele. Os corredores podem ser diferentes: por exemplo, existem os seguintes: MockitoJUnitRunner, JUnitPlatform, SpringRunner, etc.). No JUnit 5, a anotação @RunWith foi substituída pela anotação @ExtendWith mais poderosa. Vamos dar uma olhada em alguns métodos para comparar resultados:assertThat(firstObject).isEqualTo(secondObject)
aqui falei sobre os métodos básicos, já que o restante são variações diferentes dos métodos acima.
Prática de teste
Agora vamos examinar o material acima usando um exemplo específico. Testaremos o método para o serviço - atualização. Não consideraremos a camada dao, pois é o nosso padrão. Vamos adicionar um starter para testes:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
Então, a classe de serviço:
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
8 - extraia o objeto atualizado do banco de dados 9-14 - crie o objeto através do construtor, se o objeto de entrada tiver um campo - configure-o, se não - deixe o que está no banco de dados E veja nosso teste:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
1 — nosso Runner 4 — isolar o serviço da camada dao substituindo um mock 11 — definir uma entidade de teste para a classe (aquela que usaremos como hamster de teste) 22 — definir um objeto de serviço que testaremos
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Aqui vemos uma divisão clara do teste em três partes: 3-9 - configurando os fixtures 11 - executando a parte testada 13-17 - verificando os resultados Mais detalhes: 3-4 - configurando o comportamento do moka dao 5 - configurando a instância que atualizaremos sobre nosso padrão 11 - use o método e pegue a instância resultante 13 - verifique se não é zero 14 - verifique o ID do resultado e os argumentos do método especificado 15 - verifique se o nome foi atualizado 16 - veja o resultado pela cpu 17 - como não definimos isso no campo update instance, deve permanecer igual, vamos verificar. Vamos lançar: O teste é verde, pode expirar)) Então, vamos resumir: o teste melhora a qualidade do código e torna o processo de desenvolvimento mais flexível e confiável. Imagine quanto esforço teríamos que gastar ao redesenhar software com centenas de arquivos de classe. Assim que tivermos testes unitários escritos para todas essas classes, poderemos refatorar com confiança. E o mais importante, nos ajuda a encontrar erros facilmente durante o desenvolvimento. Pessoal, isso é tudo para mim hoje: dêem curtidas, escrevam comentários)))
GO TO FULL VERSION