Skip to content

DevOps ‐ Testes

Silmara Bittencourt edited this page Dec 4, 2024 · 34 revisions

Testes

Este documento descreve a aplicação dos testes, com o objetivo de assegurar que a aplicação seja confiável, segura e de alta qualidade. Os testes foram divididos em duas categorias principais: testes unitários e testes de integração.

Índice

Testes Unitários | Testes de Integração

Testes Unitários

1. Introdução

Este documento detalha a estratégia de testes unitários, com foco em identificar e corrigir erros existentes para garantir a qualidade do código e a estabilidade da aplicação. Os testes unitários são responsáveis por testar a menor unidade do nosso software de maneira isolada. Nesse caso, consideraremos as funções como menor unidade.
Esses testes validam o comportamento esperado de uma função: dada uma determinada execução espera-se uma saída.

2. Ferramentas e Tecnologias

Utilizamos as seguintes ferramentas e tecnologias:

  • Linguagens de Programação: React (Front-end), Node.js (Back-end)
  • Frameworks de Teste: Jest
  • Gerenciador de Pacotes: npm
  • Sistema de Controle de Versão: Git
  • Plataforma de CI: GitHub Actions
  • Banco de Dados: PostgreSQL

3. Estratégia de Testes Corretivos

Optamos por adotar testes corretivos.

3.1. Identificação de Erros

Nosso objetivo principal é identificar erros existentes no código para garantir a qualidade da aplicação. Para isso, seguimos estes passos:

  • Análise de Logs: Monitoramos os logs do sistema para identificar erros ou comportamentos inesperados.
  • Monitoramento de Erros: Implementamos mecanismos para capturar e registrar erros que ocorrem durante a execução da aplicação.
  • Feedback da Equipe: Incentivamos a equipe de desenvolvimento a relatar quaisquer problemas ou erros que encontrem durante o desenvolvimento.

3.2. Testes Unitários

Utilizamos testes unitários para verificar o comportamento individual de cada componente da aplicação:

  • Criação de Testes: Os testes unitários serão criados pelo desenvolvedor responsável por cada função importante, visando verificar se o código está funcionando como esperado. Os testes deverão ser independentes uns dos outros, evitando que a falha em um teste afete outros testes. O nome deverá seguir o padrão: nomedafuncaotestada.test.tsx.
    Cada teste deve ser focado em uma única funcionalidade, o que facilita o entendimento e a manutenção. Ademais, é importante manter os testes atualizados, refletindo as mudanças no código-fonte. Sempre que houver alterações no código, os testes unitários devem ser revisados e atualizados, assegurando que continuem testando adequadamente a funcionalidade desejada.
    Deverão conter mensagens sugestivas, apresentando o que era esperado e qual o retorno que está acontecendo, por exemplo: “Esperava-se 4, mas o retorno é 20”

  • Armazenamento dos teste: Os testes serão armazenados na pasta tests dentro da raiz do projeto.

  • Execução de Testes: Os testes unitários serão executados pelo desenvolvedor responsável pela criação da função.
    Os testes realizados após qualquer alteração significativa no código serão apenas aqueles que envolvem a parte em desenvolvimento, usando o comando "npm test nomedafuncaotestada.test.tsx"

  • Análise dos Resultados: Os resultados serão analisados para identificar erros e realizar as correções necessárias.

  • Ao encontrar os erros, o desenvolvedor deverá seguir os passos abaixo:

    1. Executar novamente o teste que falhou para analisar e documentar as condições em que o erro ocorre (entradas, estados dos componentes e mensagens de erro);
    2. Analisar as causas do erro, a partir de logs e mensagens de erro para entender o contexto.
    3. Verificar se o erro está relacionado a um bug no código, na lógica ou em problemas de configuração do ambiente de teste.
    4. Corrigir o erro:
      1. Erros de Lógica: Nesse caso o desenvolvedor deverá fazer a correção da lógica.
      2. Erros de Configuração: Problemas na configuração do ambiente de teste deverão ser relatados à equipe responsável para a devida correção.
        Se, ao executar o teste, o desenvolvedor encontrar erros em funcionalidades de outro desenvolvedor, caberá a ele corrigi-lo, se possível. Caso não consiga, deverá contatar o desenvolvedor responsável.
    5. Após a correção deve-se executar o teste novamente, até que não apresente erro algum.

4. Implementação

4.1. Ambiente de Teste

Para garantir testes confiáveis e independentes, criamos um ambiente de teste separado:

  • Configuração do Ambiente de Teste: Criamos um ambiente de teste separado com uma configuração próxima à do ambiente de produção.
  • Dados de Teste: Desenvolvemos dados específicos para testes unitários, de modo que os testes não interfiram na produção.

4.2. Implementação dos Testes

4.2.1 Teste Unitário para Visualização de Alertas

Este exemplo demonstra um teste unitário para o componente AlertView, que é responsável por exibir os alertas gerados no nosso sistema de coleta de dados de estações meteorológicas. O teste verifica dois cenários principais:

  1. Renderização correta dos dados:

    • Mock dos serviços de API (ocorrências, alertas e estações)
    • Verificação da exibição correta dos dados na interface
    • Validação do formato específico da mensagem (emoji + estação + alerta + valor)
  2. Tratamento de erro:

    • Simulação de falha nas chamadas da API
    • Verificação da exibição da mensagem de erro
    • Validação do comportamento do componente em caso de erro

Exemplo de implementação:

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import AlertView from '../components/AlertView';
import { listarOcorrencia } from '../services/ocorrenciaServices';
import { listarAlertas } from '../services/alertaServices';
import { listarEstacoes } from '../services/estacaoServices';

// Mocking axios
const mock = new MockAdapter(axios);

// Mocking the services
jest.mock('../services/ocorrenciaServices', () => ({
  listarOcorrencia: jest.fn(),
}));

jest.mock('../services/alertaServices', () => ({
  listarAlertas: jest.fn(),
}));

jest.mock('../services/estacaoServices', () => ({
  listarEstacoes: jest.fn(() => Promise.resolve({ data: { rows: [] } })),
}));

describe('AlertView', () => {
  beforeAll(() => {
    process.env.REACT_APP_API_BACK = 'http://localhost:3001';
  });

  afterEach(() => {
    mock.reset();
  });

  test('deve renderizar ocorrências, alertas e estações corretamente', async () => {
    const mockOcorrencias = {
      data: {
        rows: [
          { id: 1, id_alerta: 1, valor: 10 },
          { id: 2, id_alerta: 2, valor: 20 }
        ]
      }
    };
    const mockAlertas = {
      data: {
        rows: [
          { id: 1, nome: 'Alerta 1', id_estacao: 1 },
          { id: 2, nome: 'Alerta 2', id_estacao: 2 }
        ]
      }
    };
    const mockEstacoes = {
      data: {
        rows: [
          { id: 1, nome: 'Estação 1' },
          { id: 2, nome: 'Estação 2' }
        ]
      }
    };

    (listarOcorrencia as jest.Mock).mockResolvedValue(mockOcorrencias);
    (listarAlertas as jest.Mock).mockResolvedValue(mockAlertas);
    (listarEstacoes as jest.Mock).mockResolvedValue(mockEstacoes);

    render(<AlertView />);

    await waitFor(() => {
      expect(screen.getByText('📢 Estação 1 : Alerta 1 10')).toBeInTheDocument();
      expect(screen.getByText('📢 Estação 2 : Alerta 2 20')).toBeInTheDocument();
    });
  });

  test('deve lidar com erro na resposta da API', async () => {
    (listarOcorrencia as jest.Mock).mockRejectedValue(new Error('Erro ao buscar dados'));
    (listarAlertas as jest.Mock).mockRejectedValue(new Error('Erro ao buscar dados'));
    (listarEstacoes as jest.Mock).mockRejectedValue(new Error('Erro ao buscar dados'));

    render(<AlertView />);

    await waitFor(() => {
      const errorMessage = screen.getByTestId('error-message');
      expect(errorMessage).toBeInTheDocument();
      expect(errorMessage).toHaveTextContent('Erro ao buscar dados');
    });
  });
});

Explicação do Teste:

  • Mock do Serviço: Mockamos as funções listarOcorrencia, listarAlertas e listarEstacoes para evitar chamadas reais à API, simulando respostas específicas, sejam dados válidos ou erros.
  • Renderização do Componente: Renderizamos o componente AlertView isoladamente, sem interferência de outros componentes ou do contexto externo.
  • Cenário 1: Renderização Correta dos Dados: Simulamos respostas bem-sucedidas das APIs e verificamos se os dados fornecidos são exibidos corretamente na interface. Exemplo: O teste valida que as ocorrências, alertas e estações são renderizados no formato esperado, como: 📢 Estação 1 : Alerta 1 10.
  • Cenário 2: Tratamento de Erros: Simulamos falhas nas chamadas às APIs e verificamos se o componente exibe uma mensagem de erro apropriada. Exemplo: Garantimos que, em caso de erro, o componente exibe um elemento específico com a mensagem: "Erro ao buscar dados".

Resposta do teste:
image image

4.2.2 Teste Unitário para Cadastro de Estação

Este exemplo demonstra um teste unitário para o componente CadastroEstacao, que é responsável por cadastrar as estações no nosso sistema de coleta de dados de estações meteorológicas. O teste verifica:

  • Cadastro bem-sucedido com todos os campos
  • Tratamento de erro no cadastro
  • Integração com seleção de sensores

Exemplo de implementação:

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { CadastroEstacao } from '../pages/estacoes/CadastroEstacao';
import { cadastrarEstacao } from '../services/estacaoServices';
import { listarSensores } from '../services/sensorServices';

jest.mock('../services/estacaoServices');
jest.mock('../services/sensorServices');

describe('CadastroEstacao', () => {
  const mockEstacao = {
    nome: 'Estação Teste',
    latitude: -23.5505,
    longitude: -46.6333,
    sensores: [1, 2]
  };

  test('deve cadastrar uma estação com sucesso', async () => {
    (cadastrarEstacao as jest.Mock).mockResolvedValue({ data: mockEstacao });
    (listarSensores as jest.Mock).mockResolvedValue({ 
      data: { 
        rows: [
          { id: 1, nome: 'Sensor 1' },
          { id: 2, nome: 'Sensor 2' }
        ] 
      } 
    });

    render(<CadastroEstacao />);

    fireEvent.change(screen.getByLabelText(/nome/i), {
      target: { value: mockEstacao.nome }
    });
    fireEvent.change(screen.getByLabelText(/latitude/i), {
      target: { value: mockEstacao.latitude }
    });
    fireEvent.change(screen.getByLabelText(/longitude/i), {
      target: { value: mockEstacao.longitude }
    });

    fireEvent.click(screen.getByText(/cadastrar/i));

    await waitFor(() => {
      expect(cadastrarEstacao).toHaveBeenCalledWith(expect.objectContaining(mockEstacao));
      expect(screen.getByText(/estação cadastrada com sucesso/i)).toBeInTheDocument();
    });
  });
});

4.2.3 Teste Unitário para Autenticação

Este exemplo demonstra um teste unitário para o componente Login, que é responsável pela autenticação dos usuários no nosso sistema de coleta de dados de estações meteorológicas. O teste verifica:

  • Autenticação bem-sucedida
  • Armazenamento do token
  • Redirecionamento após login
  • Tratamento de credenciais inválidas

Exemplo de implementação:

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import Login from '../pages/login/Login';
import { login } from '../services/admServices';

jest.mock('../services/authService');
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => jest.fn()
}));

describe('Login', () => {
  test('deve realizar login com sucesso', async () => {
    const mockResponse = {
      data: {
        token: 'fake-token',
        user: { id: 1, email: 'teste@teste.com' }
      }
    };

    (login as jest.Mock).mockResolvedValue(mockResponse);

    render(<Login />);

    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'teste@teste.com' }
    });
    fireEvent.change(screen.getByLabelText(/senha/i), {
      target: { value: '123456' }
    });

    fireEvent.click(screen.getByText(/entrar/i));

    await waitFor(() => {
      expect(localStorage.getItem('token')).toBe('fake-token');
      expect(localStorage.getItem('usuarioId')).toBe('1');
    });
  });

  test('deve mostrar erro com credenciais inválidas', async () => {
    (login as jest.Mock).mockRejectedValue(new Error('Credenciais inválidas'));

    render(<Login />);

    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'invalido@teste.com' }
    });
    fireEvent.change(screen.getByLabelText(/senha/i), {
      target: { value: 'senha-errada' }
    });

    fireEvent.click(screen.getByText(/entrar/i));

    await waitFor(() => {
      expect(screen.getByText(/credenciais inválidas/i)).toBeInTheDocument();
    });
  });
});

4.2.3 Teste Unitário para Lista de Sensores

Este exemplo demonstra um teste unitário para o componente ListaSensores, que é responsável pela listagem dos sensores no nosso sistema de coleta de dados de estações meteorológicas. O teste verifica:

  • Listagem correta dos sensores
  • Integração com parâmetros
  • Funcionalidade de exclusão

Exemplo de implementação:

import { render, screen, waitFor } from '@testing-library/react';
import { ListaSensores } from '../pages/Sensores/ListaSensores';
import { listarSensores, deletarSensor } from '../services/sensorServices';
import { listarParametros } from '../services/parametroServices';

jest.mock('../services/sensorServices');
jest.mock('../services/parametroServices');

describe('ListaSensores', () => {
  const mockSensores = {
    data: {
      rows: [
        { id: 1, nome: 'Sensor 1', id_parametro: 1 },
        { id: 2, nome: 'Sensor 2', id_parametro: 2 }
      ]
    }
  };

  const mockParametros = {
    data: {
      rows: [
        { id: 1, nome: 'Temperatura' },
        { id: 2, nome: 'Umidade' }
      ]
    }
  };

  beforeEach(() => {
    localStorage.setItem('token', 'fake-token');
  });

  test('deve listar sensores corretamente', async () => {
    (listarSensores as jest.Mock).mockResolvedValue(mockSensores);
    (listarParametros as jest.Mock).mockResolvedValue(mockParametros);

    render(<ListaSensores />);

    await waitFor(() => {
      expect(screen.getByText('Sensor 1')).toBeInTheDocument();
      expect(screen.getByText('Sensor 2')).toBeInTheDocument();
      expect(screen.getByText('Temperatura')).toBeInTheDocument();
      expect(screen.getByText('Umidade')).toBeInTheDocument();
    });
  });

  test('deve mostrar mensagem quando não houver token', async () => {
    localStorage.removeItem('token');
    render(<ListaSensores />);

    expect(screen.getByText(/você precisa estar logado/i)).toBeInTheDocument();
  });
});

4.2.4 Teste Unitário para Checar Alerta (BACK)

Este exemplo demonstra um teste unitário para a função ChecaAlerta, que é responsável por verificar se um valor atende a uma condição específica definida em um alerta no nosso sistema de coleta de dados de estações meteorológicas. O teste verifica:

  • Condições corretas: Testa se a função ChecaAlerta retorna o valor esperado para diferentes condições (<, >, <=, >=, =).

  • Condições desconhecidas: Testa se a função ChecaAlerta retorna false para condições não reconhecidas.

Exemplo de implementação:

import { ChecaAlerta } from '../services/tratamento';
import { IAlertaParametro } from '../types/RecepcaoDados';

describe('ChecaAlerta', () => {
  test('deve retornar true quando valor é menor que alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '<', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 5);
    expect(resultado).toBe(true);
  });

  test('deve retornar false quando valor não é menor que alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '<', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 15);
    expect(resultado).toBe(false);
  });

  test('deve retornar true quando valor é maior que alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '>', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 15);
    expect(resultado).toBe(true);
  });

  test('deve retornar false quando valor não é maior que alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '>', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 5);
    expect(resultado).toBe(false);
  });

  test('deve retornar true quando valor é menor ou igual a alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '<=', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 10);
    expect(resultado).toBe(true);
  });

  test('deve retornar false quando valor não é menor ou igual a alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '<=', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 15);
    expect(resultado).toBe(false);
  });

  test('deve retornar true quando valor é maior ou igual a alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '>=', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 10);
    expect(resultado).toBe(true);
  });

  test('deve retornar false quando valor não é maior ou igual a alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '>=', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 5);
    expect(resultado).toBe(false);
  });

  test('deve retornar true quando valor é igual a alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '=', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 10);
    expect(resultado).toBe(true);
  });

  test('deve retornar false quando valor não é igual a alerta.valor', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: '=', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 5);
    expect(resultado).toBe(false);
  });

  test('deve retornar false para condição desconhecida', () => {
    const alerta: IAlertaParametro = { id: 1, nome_json: 'parametro1', condicao: 'desconhecida', nome: 'Alerta 1', valor: 10 };
    const resultado = ChecaAlerta(alerta, 10);
    expect(resultado).toBe(false);
  });
});

Não é necessário mockar serviços externos, pois a função ChecaAlerta é uma função pura que não depende de serviços externos.

Cenário 1: Verificação correta das condições
Testamos se a função ChecaAlerta retorna true ou false corretamente para diferentes condições (<, >, <=, >=, =). Por exemplo:

  • Para a condição <:

    • Retorna true quando o valor é menor que alerta.valor.
    • Retorna false caso contrário.
  • Para a condição >:

    • Retorna true quando o valor é maior que alerta.valor.
    • Retorna false caso contrário.
  • Para a condição <=:

    • Retorna true quando o valor é menor ou igual a alerta.valor.
    • Retorna false caso contrário.
  • Para a condição >=:

    • Retorna true quando o valor é maior ou igual a alerta.valor.
    • Retorna false caso contrário.
  • Para a condição =:

    • Retorna true quando o valor é igual a alerta.valor.
    • Retorna false caso contrário.

Cenário 2: Tratamento de condições desconhecidas
Testamos se a função ChecaAlerta retorna false para condições não reconhecidas. Por exemplo, se a condição é desconhecida, a função deve retornar false.

Estratégia de Testes

  1. Cobertura Completa de Condições Reconhecidas:

    • Garantir que todas as condições esperadas (<, >, <=, >=, =) sejam testadas com diferentes valores de entrada.
    • Validar os cenários positivos (onde a condição é verdadeira) e negativos (onde a condição é falsa).
  2. Tratamento de Condições Inválidas:

    • Testar entradas onde a condição fornecida é desconhecida, como valores inválidos ("???", "abc", etc.).
    • Confirmar que a função retorna sempre false nesses casos.
  3. Validação com Diferentes Tipos de Dados:

    • Garantir que a função se comporte corretamente com tipos de dados válidos (números inteiros e decimais).
    • Testar cenários com entradas inesperadas (como strings ou valores null) para verificar robustez.

Exemplos de Casos de Teste

  • Cenário 1: Condições Reconhecidas
Condição valor alerta.valor Resultado Esperado
< 5 10 true
< 10 5 false
> 15 10 true
> 5 10 false
<= 5 5 true
<= 6 5 false
>= 10 10 true
>= 9 10 false
= 20 20 true
= 15 20 false
  • Cenário 2: Condições Desconhecidas
Condição valor alerta.valor Resultado Esperado
??? 5 10 false
"abc" 10 10 false
null 10 5 false

Resposta do teste:
image

A função ChecaAlerta é validada para garantir que opere corretamente em todos os cenários esperados e que seja robusta contra entradas inválidas. A estratégia de testes cobre tanto os casos positivos quanto os negativos, além de tratar condições desconhecidas de forma previsível e segura.

Testes de Integração

1. Contexto do Projeto

A API 4 é responsável por coletar dados de estações meteorológicas, realizando operações como cadastro de dados, monitoramento de condições meteorológicas e geração de alertas. Para garantir que os componentes dessa API funcionem corretamente quando integrados, realizamos uma série de testes de integração, com foco em validar a interação entre as diferentes unidades e a comunicação entre os serviços e o banco de dados.

2. Objetivo dos Testes de Integração

Os testes de integração têm o objetivo de validar que os componentes da API interagem corretamente entre si, garantindo que os fluxos de dados funcionem como esperado. No caso específico da API 4, o foco dos testes foi:

  • Validar o funcionamento da comunicação entre a camada de controle, serviço, repositório e banco de dados.
  • Garantir que dados inseridos na API sejam corretamente persistidos e retornados ao usuário.
  • Testar a exibição de informações dinâmicas, como alertas, após interações com a base de dados.

2.1 Quem cria os testes e quando?

Os testes são elaborados antes da criação da funcionalidade, utilizando mocks para simular o comportamento esperado e evitar falhas durante a execução. Assim que o desenvolvedor implementar a funcionalidade, ele deve substituir os mocks por chamadas reais e ajustar o teste para trabalhar com os dados efetivos. Essa tarefa é geralmente atribuída a um desenvolvedor focado na área ou a um profissional mais experiente em testes. Eles utilizam os critérios de aceitação das funcionalidades como referência para criar os testes, começando com o uso de mocks para simular comportamentos esperados.

2.2 Documentação de testes

A documentação dos testes deve ser armazenada em um arquivo README.md dentro da pasta de testes de integração no repositório (/tests/integration). Cada teste deve conter:

  • Nome do Teste: Descrição sucinta, exemplo: "Cadastro de dados meteorológicos".
  • Propósito: O que o teste valida, exemplo: "Garantir que os dados meteorológicos sejam corretamente persistidos no banco".
  • Localização no Código: Caminho do arquivo que implementa o teste, exemplo: /tests/integration/data/Cadastro.test.ts.
  • Critérios de Sucesso: O que o teste deve verificar para ser considerado aprovado.

Esse arquivo serve como referência rápida para desenvolvedores e revisores, detalhando os testes implementados e sua organização.

2.3 Carregamento de banco para testes

Os testes utilizam o TypeORM para gerenciar a carga inicial do banco de dados. Arquivos de seeds e migrations específicos são armazenados em /tests/integration/database/seeds.

  • Seeds: Contêm dados mínimos necessários para os testes, como configurações iniciais e exemplos de registros.
  • Configuração: Os testes utilizam uma conexão de banco isolada, configurada em /tests/integration/config/database.ts. = Execução: As seeds são carregadas automaticamente antes de cada teste com scripts que utilizam o typeorm-seeding.

Exemplo:

import { DataSource } from "typeorm";
import { User } from "./entities/User";

const seedUsers = async (dataSource: DataSource) => {
  const userRepository = dataSource.getRepository(User);
  const users = Array.from({ length: 10 }, (_, i) => ({
    name: `User ${i}`,
    email: `user${i}@example.com`,
  }));
  await userRepository.save(users);
};

3. Descrição dos Testes Realizados

3.1 Envio de Dados e Captação Coerente

  • Objetivo: Garantir que, após o cadastro de um item (como estação, parâmetro, alerta ou sensor), os dados sejam corretamente capturados e exibidos na listagem.
  • Cenário de Teste:
    • Cadastrar um item (estação, parâmetro, alerta ou sensor).
    • Após o cadastro, acessar a página de listagem e verificar se o item foi adicionado corretamente.
  • Resultado Esperado: O item (estação, parâmetro, alerta ou sensor) deve ser exibido na lista de itens cadastrados.
  • Falha Esperada: O item não aparece na listagem após o cadastro, indicando que os dados não foram devidamente capturados ou exibidos.

3.2 CRUD das Entidades

  • Objetivo: Validar o processo completo de criação, listagem, edição e exclusão das entidades, assegurando que todas as operações sejam realizadas de forma consistente.
  • Cenário de Teste:
    • Cadastrar um novo item (ex.: estação, parâmetro, alerta ou sensor).
    • Verificar se o item aparece corretamente na listagem.
    • Editar o item cadastrado e verificar se as alterações são refletidas na listagem.
    • Excluir o item e verificar se ele desaparece da listagem.
  • Resultado Esperado:
    • O item deve ser cadastrado e listado corretamente.
    • As alterações feitas ao item devem ser refletidas após a edição.
    • O item deve ser removido da lista após a exclusão.
  • Falha Esperada:
    • Alguma das operações não é realizada corretamente: o item não aparece na lista após o cadastro, não é possível editar as informações, o item não é removido da lista após a exclusão, ou ele permanece visível após a exclusão.

3.3 Integração Geral do Sistema

  • Objetivo: Validar o fluxo completo de cadastros das entidades e a interação entre elas, garantindo que o processo de cadastro ocorra na ordem correta e que o alerta seja disparado adequadamente conforme as condições definidas.
  • Cenário de Teste:
    • Cadastrar as entidades na ordem correta: primeiro o parâmetro, depois o sensor, em seguida a estação e, por fim, o alerta.
    • Cadastrar um alerta com base em uma condição meteorológica (por exemplo, temperatura superior a 30°C).
    • Monitorar a página inicial para verificar se o alerta é exibido quando as condições são atendidas por uma estação.
  • Resultado Esperado:
    • O alerta deve ser exibido na página inicial sempre que as condições definidas (ex.: temperatura acima de 30°C) forem atendidas por alguma estação.
  • Falha Esperada: O alerta não aparece, mesmo que as condições para dispará-lo sejam atendidas por uma estação, indicando uma falha na integração entre os componentes ou na lógica de exibição do alerta.

4. Ferramentas e Tecnologias Utilizadas

  • Linguagem de Programação: TypeScript
  • Framework de Testes: Jest
  • Banco de Dados: PostgreSQL
  • Gerenciador de Pacotes: npm
  • Controle de Versão: Git

5. Detalhamento dos Fluxos de Teste

5.1 Envio de Dados e Captação Coerente

  • Objetivo: Garantir que os dados cadastrados sejam captados corretamente e exibidos de forma consistente na aplicação.
  • Cenário de Teste:
    1. Cadastrar um item (estação, parâmetro, alerta ou sensor) por meio da interface de cadastro.
    2. Após o cadastro, acessar a página de listagem para verificar se o item foi adicionado corretamente.
  • Passos:
    • Enviar uma requisição de cadastro para um item (por exemplo, cadastrar uma estação meteorológica).
    • Verificar a resposta para garantir que o item foi cadastrado corretamente, incluindo a atribuição de um identificador único.
    • Acessar a página de listagem e conferir se o item recém cadastrado aparece corretamente, com todas as informações esperadas.
  • Resultado Esperado: O item (estação, parâmetro, alerta ou sensor) deve ser corretamente exibido na página de listagem, com todos os dados relacionados (nome, tipo, etc.).
  • Falha Esperada: Caso o item não apareça na listagem ou os dados não estejam corretos, indicando que houve falha na persistência ou exibição dos dados.
  • Teste:
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { CadastroEstacao } from './CadastroEstacao';
import { ListaEstacoes } from './ListaEstacoes'; 
import { BrowserRouter as Router } from 'react-router-dom';
import { cadastrarEstacao, listarEstacoes } from '../../services/estacaoServices';
jest.mock('../../services/estacaoServices', () => ({
cadastrarEstacao: jest.fn(),
listarEstacoes: jest.fn(),
}));

test('Cadastro de estação deve refletir na lista de estações', async () => {
listarEstacoes.mockResolvedValue({
data: {
rows: [{ id: 1, nome: 'Estação 1', endereco: 'Rua 1', latitude: 10, longitude: 20, mac_address: '00:11:22:33:44:55' }],},});
cadastrarEstacao.mockResolvedValue({
data: { id: 2, nome: 'Estação 2', endereco: 'Rua 2', latitude: 30, longitude: 40, mac_address: '66:77:88:99:00:11' },
});
render(
<Router>
<ListaEstacoes />
<CadastroEstacao />
</Router>
);
await waitFor(() => screen.getByText('Estação 1'));
expect(screen.getByText('Estação 1')).toBeInTheDocument();
fireEvent.change(screen.getByPlaceholderText('Digite o nome...'), { target: { value: 'Estação 2' } });
fireEvent.change(screen.getByPlaceholderText('Digite o MAC adress UID...'), { target: { value: '66:77:88:99:00:11' } });
fireEvent.change(screen.getByPlaceholderText('Digite a localização e/ou ponto de referência...'), { target: { value: 'Rua 2' } });
fireEvent.click(screen.getByText('Salvar'));
await waitFor(() => screen.getByText('Estação 2'));
expect(screen.getByText('Estação 2')).toBeInTheDocument();
expect(screen.getByText('Estação 1')).toBeInTheDocument();
});

5.2 CRUD das Entidades

  • Objetivo: Validar que as operações completas de criação, listagem, edição e exclusão das entidades (estação, parâmetro, alerta, sensor, etc.) funcionem corretamente e de maneira coesa.
  • Cenário de Teste:
    1. Cadastrar uma nova entidade (ex.: estação meteorológica ou alerta).
    2. Verificar se a entidade aparece na lista de entidades cadastradas.
    3. Editar as informações da entidade cadastrada e verificar se a alteração é refletida corretamente.
    4. Excluir a entidade e confirmar que ela foi removida da lista.
  • Passos:
    • Enviar uma requisição para criar uma nova entidade (ex.: cadastrar um parâmetro ou sensor).
    • Acessar a página de listagem e confirmar se o item foi listado corretamente.
    • Realizar uma operação de edição na entidade (ex.: alterar o nome ou outro atributo).
    • Confirmar se a atualização é refletida na página de listagem.
    • Excluir a entidade e verificar se ela desaparece corretamente da listagem.
  • Resultado Esperado:
    • O item deve ser corretamente exibido na listagem após o cadastro.
    • As edições realizadas devem ser refletidas na listagem.
    • O item excluído não deve mais aparecer na listagem.
  • Falha Esperada:
    • O item não aparece após o cadastro ou não é possível editá-lo ou excluí-lo. Caso o item seja excluído, ele ainda deve aparecer na listagem, o que indicaria uma falha na operação de exclusão.
  • Teste:
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { CadastroEstacao } from './CadastroEstacao';
import { ListaEstacoes } from './ListaEstacoes';
import { BrowserRouter as Router } from 'react-router-dom';
import { cadastrarEstacao, listarEstacoes, editarEstacao, deletarEstacao } from '../../services/estacaoServices';

jest.mock('../../services/estacaoServices', () => ({
  cadastrarEstacao: jest.fn(),
  listarEstacoes: jest.fn(),
  editarEstacao: jest.fn(),
  deletarEstacao: jest.fn(),
}));

test('Cadastro de estacao deve refletir na lista de estacoes', async () => {
  listarEstacoes.mockResolvedValue({
    data: {
      rows: [
        { id: 1, nome: 'Estacao 1', endereco: 'Rua 1', latitude: 10, longitude: 20, mac_address: '00:11:22:33:44:55' },
      ],
    },
  });

  cadastrarEstacao.mockResolvedValue({
    data: { id: 2, nome: 'Estacao 2', endereco: 'Rua 2', latitude: 30, longitude: 40, mac_address: '66:77:88:99:00:11' },
  });

  render(
    <Router>
      <ListaEstacoes />
      <CadastroEstacao />
    </Router>
  );

  await waitFor(() => screen.getByText('Estacao 1'));
  expect(screen.getByText('Estacao 1')).toBeInTheDocument();

  fireEvent.change(screen.getByPlaceholderText('Digite o nome...'), { target: { value: 'Estacao 2' } });
  fireEvent.change(screen.getByPlaceholderText('Digite o MAC adress UID...'), { target: { value: '66:77:88:99:00:11' } });
  fireEvent.change(screen.getByPlaceholderText('Digite a localizacao e/ou ponto de referencia...'), { target: { value: 'Rua 2' } });
  fireEvent.click(screen.getByText('Salvar'));

  await waitFor(() => screen.getByText('Estacao 2'));
  expect(screen.getByText('Estacao 2')).toBeInTheDocument();
  expect(screen.getByText('Estacao 1')).toBeInTheDocument();
});

test('Editar estacao deve refletir as mudancas na lista de estacoes', async () => {
  listarEstacoes.mockResolvedValue({
    data: {
      rows: [
        { id: 1, nome: 'Estacao 1', endereco: 'Rua 1', latitude: 10, longitude: 20, mac_address: '00:11:22:33:44:55' },
      ],
    },
  });

  editarEstacao.mockResolvedValue({
    data: { id: 1, nome: 'Estacao Editada', endereco: 'Rua 1', latitude: 10, longitude: 20, mac_address: '00:11:22:33:44:55' },
  });

  render(
    <Router>
      <ListaEstacoes />
      <CadastroEstacao />
    </Router>
  );

  await waitFor(() => screen.getByText('Estacao 1'));
  expect(screen.getByText('Estacao 1')).toBeInTheDocument();

  fireEvent.click(screen.getByText('Editar'));
  await waitFor(() => screen.getByText('Estacao Editada'));

  fireEvent.change(screen.getByPlaceholderText('Digite o nome...'), { target: { value: 'Estacao Editada' } });
  fireEvent.click(screen.getByText('Salvar'));

  await waitFor(() => screen.getByText('Estacao Editada'));
  expect(screen.getByText('Estacao Editada')).toBeInTheDocument();
  expect(screen.queryByText('Estacao 1')).not.toBeInTheDocument();
});

test('Excluir estacao deve remove-la da lista de estacoes', async () => {
  listarEstacoes.mockResolvedValue({
    data: {
      rows: [
        { id: 1, nome: 'Estacao 1', endereco: 'Rua 1', latitude: 10, longitude: 20, mac_address: '00:11:22:33:44:55' },
      ],
    },
  });

  deletarEstacao.mockResolvedValue({
    data: { message: 'Estacao deletada com sucesso!' },
  });

  render(
    <Router>
      <ListaEstacoes />
      <CadastroEstacao />
    </Router>
  );

  await waitFor(() => screen.getByText('Estacao 1'));
  expect(screen.getByText('Estacao 1')).toBeInTheDocument();

  fireEvent.click(screen.getByText('Excluir'));
  await waitFor(() => expect(screen.queryByText('Estacao 1')).not.toBeInTheDocument());
});

5.3 Integração Geral do Sistema

  • Objetivo: Validar a integração e o fluxo de dados entre os componentes, garantindo que a sequência de operações de cadastro (parâmetro → sensor → estação → alerta) funcione conforme esperado e que o alerta seja disparado quando as condições forem atendidas.
  • Cenário de Teste:
    1. Cadastrar um parâmetro (ex.: temperatura).
    2. Cadastrar um sensor que utilizará o parâmetro cadastrado.
    3. Cadastrar uma estação e associá-la ao sensor.
    4. Cadastrar um alerta baseado em uma condição meteorológica (ex.: temperatura superior a 30°C).
    5. Verificar se, quando a condição do alerta é atendida por uma estação, o alerta é disparado e exibido na página inicial.
  • Passos:
    • Primeiramente, cadastrar o parâmetro (ex.: temperatura).
    • Criar um sensor, vinculando-o ao parâmetro previamente cadastrado.
    • Criar uma estação meteorológica e associá-la ao sensor.
    • Cadastrar um alerta com a condição de que a temperatura seja superior a 30°C.
    • Criar uma estação meteorológica com uma temperatura superior a 30°C.
    • Monitorar a página inicial para verificar se o alerta é exibido quando a condição for atendida.
  • Resultado Esperado:
    • O alerta deve ser exibido na página inicial quando a temperatura da estação ultrapassar o limite definido no alerta (ex.: temperatura acima de 30°C).
  • Falha Esperada:
    • O alerta não é exibido na página inicial, mesmo quando as condições para dispará-lo são atendidas, indicando que houve falha na lógica de integração entre as entidades e a exibição do alerta.
  • Teste:
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import { CadastroParametro } from './CadastroParametro';
import { CadastroSensor } from './CadastroSensor';
import { CadastroEstacao } from './CadastroEstacao';
import { CadastroAlerta } from './CadastroAlerta';
import { ListaAlertas } from './ListaAlertas';
import {
  cadastrarParametro,
  listarParametros,
  cadastrarSensor,
  listarSensores,
  cadastrarEstacao,
  listarEstacoes,
  cadastrarAlerta,
  listarAlertas,
} from '../../services/alertaServices';

// Mock dos servicos de API
jest.mock('../../services/alertaServices', () => ({
  cadastrarParametro: jest.fn(),
  listarParametros: jest.fn(),
  cadastrarSensor: jest.fn(),
  listarSensores: jest.fn(),
  cadastrarEstacao: jest.fn(),
  listarEstacoes: jest.fn(),
  cadastrarAlerta: jest.fn(),
  listarAlertas: jest.fn(),
}));

test('Cadastro integrado (parametro -> sensor -> estacao -> alerta) validando relacoes', async () => {
  // Mock dos dados iniciais vazios
  listarParametros.mockResolvedValue({ data: { rows: [] } });
  listarSensores.mockResolvedValue({ data: { rows: [] } });
  listarEstacoes.mockResolvedValue({ data: { rows: [] } });
  listarAlertas.mockResolvedValue({ data: { rows: [] } });

  // Mock dos cadastros
  cadastrarParametro.mockResolvedValue({
    data: { id: 1, nome: 'Temperatura', unidade_medida: '°C', fator: 1, offset: 0 },
  });
  listarParametros.mockResolvedValueOnce({
    data: { rows: [{ id: 1, nome: 'Temperatura', unidade_medida: '°C' }] },
  });

  cadastrarSensor.mockResolvedValue({
    data: { id: 1, nome: 'Sensor 1', parametro_id: 1 },
  });
  listarSensores.mockResolvedValueOnce({
    data: { rows: [{ id: 1, nome: 'Sensor 1', parametro_id: 1 }] },
  });

  cadastrarEstacao.mockResolvedValue({
    data: { id: 1, nome: 'Estacao 1', endereco: 'Rua 1', mac_address: '00:11:22:33:44:55' },
  });
  listarEstacoes.mockResolvedValueOnce({
    data: { rows: [{ id: 1, nome: 'Estacao 1', mac_address: '00:11:22:33:44:55' }] },
  });

  cadastrarAlerta.mockResolvedValue({
    data: { id: 1, mensagem: 'Alerta de temperatura alta!', estacao_id: 1, sensor_id: 1 },
  });
  listarAlertas.mockResolvedValueOnce({
    data: {
      rows: [
        { id: 1, mensagem: 'Alerta de temperatura alta!', estacao_id: 1, sensor_id: 1 },
      ],
    },
  });

  // Render dos componentes
  render(
    <Router>
      <ListaAlertas />
      <CadastroParametro />
      <CadastroSensor />
      <CadastroEstacao />
      <CadastroAlerta />
    </Router>
  );

  // Cadastro de Parametro
  fireEvent.change(screen.getByPlaceholderText('Nome do parametro'), {
    target: { value: 'Temperatura' },
  });
  fireEvent.change(screen.getByPlaceholderText('Unidade de medida'), {
    target: { value: '°C' },
  });
  fireEvent.click(screen.getByText('Salvar Parametro'));
  await waitFor(() => screen.getByText('Temperatura'));

  // Cadastro de Sensor (puxa o parametro cadastrado)
  await waitFor(() => screen.getByText('Temperatura'));
  fireEvent.change(screen.getByPlaceholderText('Nome do sensor'), {
    target: { value: 'Sensor 1' },
  });
  fireEvent.change(screen.getByLabelText('Parametro associado'), {
    target: { value: '1' }, // ID do parametro
  });
  fireEvent.click(screen.getByText('Salvar Sensor'));
  await waitFor(() => screen.getByText('Sensor 1'));

  // Cadastro de Estacao (puxa o sensor cadastrado)
  await waitFor(() => screen.getByText('Sensor 1'));
  fireEvent.change(screen.getByPlaceholderText('Nome da estacao'), {
    target: { value: 'Estacao 1' },
  });
  fireEvent.change(screen.getByPlaceholderText('Endereco'), {
    target: { value: 'Rua 1' },
  });
  fireEvent.change(screen.getByPlaceholderText('MAC address'), {
    target: { value: '00:11:22:33:44:55' },
  });
  fireEvent.change(screen.getByLabelText('Sensor associado'), {
    target: { value: '1' }, // ID do sensor
  });
  fireEvent.click(screen.getByText('Salvar Estacao'));
  await waitFor(() => screen.getByText('Estacao 1'));

  // Cadastro de Alerta (puxa a estacao e o sensor cadastrados)
  await waitFor(() => screen.getByText('Estacao 1'));
  fireEvent.change(screen.getByPlaceholderText('Mensagem do alerta'), {
    target: { value: 'Alerta de temperatura alta!' },
  });
  fireEvent.change(screen.getByLabelText('Estacao associada'), {
    target: { value: '1' }, // ID da estacao
  });
  fireEvent.change(screen.getByLabelText('Sensor associado'), {
    target: { value: '1' }, // ID do sensor
  });
  fireEvent.click(screen.getByText('Salvar Alerta'));
  await waitFor(() => screen.getByText('Alerta de temperatura alta!'));

  // Validacao do alerta na lista
  expect(screen.getByText('Alerta de temperatura alta!')).toBeInTheDocument();
  expect(screen.getByText('Estacao 1')).toBeInTheDocument();
  expect(screen.getByText('Sensor 1')).toBeInTheDocument();
});

6. Quando Executar os Testes de Integração?

  • Manualmente, sempre que o desenvolvedor desejar, porém essencialmente após alterações significativas, executando npm test localmente.
  • Após todas as funções serem feitas, pode descomentar a linha no CI, deixando o teste de integração automático.

7. Responsáveis pela Execução dos Testes

Os testes de integração devem ser executados pela mesma pessoa que fez o merge, uma vez que ela tem o melhor entendimento do contexto das mudanças. Caso haja falhas, a pessoa responsável pode investigar rapidamente, corrigir problemas e realizar uma nova rodada de testes. Se necessário, outros membros da equipe podem ser envolvidos na correção de problemas específicos.

Clone this wiki locally