Skip to content

Latest commit

 

History

History
1046 lines (888 loc) · 28.6 KB

File metadata and controls

1046 lines (888 loc) · 28.6 KB

Estratégias de Teste - Varion

🧪 Visão Geral

Este documento abrange todas as estratégias, ferramentas e práticas de teste implementadas no projeto Varion, garantindo qualidade, confiabilidade e manutenibilidade do código.

✅ Status Atual dos Testes

  • Backend: ✅ 98.58% de cobertura (517 testes implementados)
  • Frontend: 🟡 Configuração básica (em desenvolvimento)
  • E2E: 🟡 Configuração planejada com Playwright
  • Performance: 🟡 Configuração planejada com Artillery

📋 Tipos de Teste

1. Testes Unitários ✅ IMPLEMENTADO

  • Escopo: Funções, métodos e componentes isolados
  • Framework: Jest + TypeScript
  • Cobertura Atual: 98.58% (Target: > 80%)
  • Total de Testes: 517 testes

2. Testes de Integração ✅ IMPLEMENTADO

  • Escopo: Interação entre módulos e APIs
  • Framework: Jest + Supertest + SQLite em memória
  • Foco: APIs completas e fluxos de dados
  • Banco de Teste: SQLite em memória para isolamento

3. Testes End-to-End 🟡 PLANEJADO

  • Escopo: Fluxos completos de usuário
  • Framework: Playwright
  • Ambiente: Docker containers

4. Testes de Performance 🟡 PLANEJADO

  • Escopo: Carga e stress
  • Ferramentas: Artillery, Lighthouse
  • Métricas: Response time, throughput

🎯 Backend Testing ✅ IMPLEMENTADO

Estrutura de Testes (Atual)

backend/
├── src/
│   ├── __tests__/
│   │   ├── basic.test.ts                    # Testes básicos de configuração
│   │   ├── integration.test.ts              # Testes de integração principais
│   │   ├── integration/                     # Testes de integração específicos
│   │   │   ├── states.integration.test.ts   # 436 linhas - Testes completos de Estados
│   │   │   └── todo.integration.test.ts     # Testes de TODO items
│   │   ├── examples/                        # Exemplos e templates
│   │   │   ├── unit.example.test.ts         # Exemplo de testes unitários
│   │   │   └── integration.example.test.ts  # Exemplo de testes de integração
│   │   ├── setup/                           # Configuração de testes
│   │   │   └── database.ts                  # Setup do banco de teste
│   │   ├── mocks/                           # Mocks reutilizáveis
│   │   └── templates/                       # Templates para novos testes
│   └── modules/                             # Testes distribuídos por módulo
│       ├── projects/
│       │   ├── project.controller.test.ts   # Testes do controller
│       │   ├── project.service.test.ts      # Testes do service
│       │   ├── project.entity.test.ts       # Testes da entidade
│       │   ├── project.route.test.ts        # Testes das rotas
│       │   └── project.schema.test.ts       # Testes de validação
│       ├── states/
│       │   ├── state.controller.test.ts
│       │   ├── state.service.test.ts
│       │   ├── state.entity.test.ts
│       │   └── state.schema.test.ts
│       ├── todo/
│       │   ├── todo.service.test.ts
│       │   ├── todo.entity.test.ts
│       │   └── todo.schema.test.ts
│       └── comment/
│           ├── comment.service.test.ts
│           ├── comment.entity.test.ts
│           └── comment.schema.test.ts
├── jest.config.js                          # Configuração principal do Jest
├── jest.setup.js                           # Setup global dos testes
└── coverage/                               # Relatórios de cobertura

Estatísticas de Cobertura (Atual)

-----------------------------------|---------|----------|---------|---------|
File                               | % Stmts | % Branch | % Funcs | % Lines |
-----------------------------------|---------|----------|---------|---------|
All files                          |   98.58 |    83.06 |   96.62 |     100 |
 config                            |     100 |      100 |     100 |     100 |
 modules/comment                   |     100 |    88.88 |     100 |     100 |
 modules/projects                  |   96.78 |    72.16 |   89.65 |     100 |
 modules/states                    |     100 |       88 |     100 |     100 |
 modules/todo                      |     100 |      100 |     100 |     100 |
 utils                             |     100 |      100 |     100 |     100 |
 utils/tools                       |     100 |      100 |     100 |     100 |
-----------------------------------|---------|----------|---------|---------|
Test Suites: 33 passed, 33 total
Tests:       517 passed, 517 total

Configuração Jest (Implementada)

// backend/jest.config.js
/** @type {import('jest').Config} */
module.exports = {
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: [
    '**/__tests__/**/*.test.ts',
    '**/*.test.ts'
  ],
  transform: {
    '^.+\\.ts$': ['ts-jest', {
      tsconfig: {
        experimentalDecorators: true,
        emitDecoratorMetadata: true
      }
    }]
  },
  moduleFileExtensions: ['ts', 'js', 'json', 'node'],
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.test.ts',
    '!src/**/*.spec.ts',
    '!src/server.ts',
    '!src/migrations/**',
    '!src/__tests__/**'
  ],
  coverageThreshold: {
    global: {
      branches: 70,
      functions: 70,
      lines: 70,
      statements: 70
    }
  }
};

Setup de Teste (Implementado)

// backend/jest.setup.js
// Jest setup file
require('reflect-metadata');

// Mock console.error para testes mais limpos
const originalError = console.error;
beforeAll(() => {
  console.error = (...args) => {
    if (
      typeof args[0] === 'string' &&
      args[0].includes('Warning') &&
      (args[0].includes('deprecated') || args[0].includes('experimental'))
    ) {
      return;
    }
    originalError.call(console, ...args);
  };
});

afterAll(() => {
  console.error = originalError;
});

Utilitários de Teste (Implementados)

// backend/src/__tests__/setup/database.ts
import { DataSource } from 'typeorm';
import { Express } from 'express';
import { Project } from '../../modules/projects/project.entity';
import { State } from '../../modules/states/state.entity';
import { Comment } from '../../modules/comment/comment.entity';
import { TodoItem } from '../../modules/todo/todo.entity';
import { ProjectStatusHistory } from '../../modules/projects/project-status-history.entity';
import { App } from '../../config/app';

export const createTestDataSource = async (): Promise<DataSource> => {
  const dataSource = new DataSource({
    type: 'sqlite',
    database: ':memory:',
    entities: [Project, State, Comment, TodoItem, ProjectStatusHistory],
    synchronize: true,
    logging: false,
  });

  return await dataSource.initialize();
};

export const setupTestDatabase = async (): Promise<Express> => {
  // Configuração do banco de teste e retorno da aplicação
  const testDataSource = await createTestDataSource();

  // Mock do AppDataSource para usar o banco de teste
  jest.mock('../../config/data-source', () => ({
    AppDataSource: testDataSource
  }));

  return App.getApp();
};

export const cleanupTestDatabase = async (dataSource: DataSource) => {
  if (!dataSource.isInitialized) return;

  const entities = dataSource.entityMetadatas;
  for (const entity of entities) {
    const repository = dataSource.getRepository(entity.name);
    await repository.clear();
  }
};

Exemplo de Teste de Integração (Implementado)

// backend/src/__tests__/integration/states.integration.test.ts
import request from 'supertest';
import { Application } from 'express';
import { DataSource } from 'typeorm';
import { setupTestDatabase, teardownTestDatabase, cleanupTestDatabase } from '../setup/database';
import { State } from '../../modules/states/state.entity';

describe('States Integration Tests', () => {
  let app: Application;
  let dataSource: DataSource;

  beforeAll(async () => {
    app = await setupTestDatabase();
    dataSource = require('../../config/data-source').AppDataSource;
  });

  afterAll(async () => {
    await teardownTestDatabase();
  });

  beforeEach(async () => {
    await cleanupTestDatabase(dataSource);
  });

  describe('POST /states', () => {
    it('should create a new state successfully', async () => {
      // Arrange
      const stateData = {
        name: 'In Development',
        color: '#3498db',
        order: 1
      };

      // Act
      const response = await request(app)
        .post('/states')
        .send(stateData)
        .expect(201);

      // Assert
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('id');
      expect(response.body.data.name).toBe(stateData.name);
      expect(response.body.data.color).toBe(stateData.color);

      // Verificar se foi salvo no banco
      const stateRepo = dataSource.getRepository(State);
      const savedState = await stateRepo.findOne({ where: { id: response.body.data.id } });
      expect(savedState).toBeTruthy();
      expect(savedState?.name).toBe(stateData.name);
    });

    it('should return 409 for duplicate state name', async () => {
      // Arrange
      const stateData = {
        name: 'Duplicate State',
        color: '#FF0000',
        order: 1
      };

      // Criar o primeiro estado
      await request(app)
        .post('/states')
        .send(stateData)
        .expect(201);

      // Act - Tentar criar estado com mesmo nome
      const response = await request(app)
        .post('/states')
        .send(stateData)
        .expect(409);

      // Assert
      expect(response.body.success).toBe(false);
      expect(response.body.error).toContain('Nome de estado já existe');
    });
  });
});

Exemplo de Teste Unitário com Mocks (Implementado)

// backend/src/__tests__/examples/unit.example.test.ts
import { ProjectService } from '../../modules/projects/project.service';
import { AppDataSource } from '../../config/data-source';
import { Project } from '../../modules/projects/project.entity';

// Mock AppDataSource
jest.mock('../../config/data-source', () => ({
  AppDataSource: {
    getRepository: jest.fn(),
  },
}));

describe('Unit Test Example - ProjectService', () => {
  let mockRepository: jest.Mocked<any>;

  beforeEach(() => {
    mockRepository = {
      create: jest.fn(),
      save: jest.fn(),
      findOne: jest.fn(),
      find: jest.fn(),
      delete: jest.fn(),
      update: jest.fn(),
      clear: jest.fn(),
    };

    (AppDataSource.getRepository as jest.Mock).mockReturnValue(mockRepository);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('createProject', () => {
    it('should create project successfully', async () => {
      // Arrange
      const projectData = {
        title: 'Test Project',
        description: 'Test Description',
        stateId: 1
      };

      const savedProject = {
        id: '1',
        ...projectData,
        code: 'PRJ-ABC123',
        createdAt: new Date(),
        updatedAt: new Date(),
        comments: [],
        todo: [],
        statusHistory: []
      } as Project;

      mockRepository.create.mockReturnValue(savedProject);
      mockRepository.save.mockResolvedValue(savedProject);

      // Act
      const result = await ProjectService.createProject(projectData);

      // Assert
      expect(mockRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          title: projectData.title,
          description: projectData.description,
          stateId: projectData.stateId,
          code: expect.any(String)
        })
      );
      expect(mockRepository.save).toHaveBeenCalled();
      expect(result).toEqual(savedProject);
    });

    it('should handle database error', async () => {
      // Arrange
      const projectData = {
        title: 'Test Project',
        description: 'Test Description',
        stateId: 1
      };

      mockRepository.create.mockReturnValue({});
      mockRepository.save.mockRejectedValue(new Error('Database error'));

      // Act & Assert
      await expect(ProjectService.createProject(projectData))
        .rejects
        .toThrow('Database error');
    });
  });
});

⚛️ Frontend Testing

Estrutura de Testes

frontend/
├── src/
│   ├── __tests__/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── utils/
│   │   └── pages/
│   ├── __mocks__/
│   └── test-utils.tsx
├── e2e/
│   ├── tests/
│   ├── fixtures/
│   └── playwright.config.ts
├── jest.config.js
└── jest.setup.js

Configuração Jest Frontend

// frontend/jest.config.js
const nextJest = require('next/jest');

const createJestConfig = nextJest({
  dir: './',
});

const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  testEnvironment: 'jest-environment-jsdom',
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/pages/api/**',
  ],
  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
};

module.exports = createJestConfig(customJestConfig);

Setup Frontend

// frontend/jest.setup.js
import '@testing-library/jest-dom';
import { server } from './src/__mocks__/server';

// Estabelecer API mocking antes de todos os testes
beforeAll(() => server.listen());

// Resetar handlers entre testes
afterEach(() => server.resetHandlers());

// Limpar após todos os testes
afterAll(() => server.close());

// Mock next/router
jest.mock('next/router', () => ({
  useRouter() {
    return {
      route: '/',
      pathname: '/',
      query: '',
      asPath: '',
      push: jest.fn(),
      pop: jest.fn(),
      reload: jest.fn(),
      back: jest.fn(),
      prefetch: jest.fn().mockResolvedValue(undefined),
      beforePopState: jest.fn(),
      events: {
        on: jest.fn(),
        off: jest.fn(),
        emit: jest.fn(),
      },
    };
  },
}));

Test Utils

// frontend/src/test-utils.tsx
import React, { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
};

const customRender = (
  ui: ReactElement,
  options?: Omit<RenderOptions, 'wrapper'>,
) => render(ui, { wrapper: AllTheProviders, ...options });

export * from '@testing-library/react';
export { customRender as render };

Mocks MSW

// frontend/src/__mocks__/handlers.ts
import { rest } from 'msw';

export const handlers = [
  rest.get('/api/projects', (req, res, ctx) => {
    return res(
      ctx.json({
        success: true,
        data: [
          {
            id: '1',
            name: 'Test Project 1',
            description: 'Test Description 1',
            status: 'active',
          },
          {
            id: '2',
            name: 'Test Project 2',
            description: 'Test Description 2',
            status: 'completed',
          },
        ],
      })
    );
  }),

  rest.post('/api/projects', (req, res, ctx) => {
    return res(
      ctx.status(201),
      ctx.json({
        success: true,
        data: {
          id: '3',
          name: 'New Project',
          description: 'New Description',
          status: 'active',
        },
      })
    );
  }),
];

Testes de Componente

// frontend/src/__tests__/components/ProjectForm.test.tsx
import { render, screen, fireEvent, waitFor } from '../test-utils';
import { ProjectForm } from '../../components/ProjectForm';

describe('ProjectForm', () => {
  const mockOnSubmit = jest.fn();

  beforeEach(() => {
    mockOnSubmit.mockClear();
  });

  it('renders form fields correctly', () => {
    render(<ProjectForm onSubmit={mockOnSubmit} />);

    expect(screen.getByLabelText(/nome do projeto/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/descrição/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /criar projeto/i })).toBeInTheDocument();
  });

  it('submits form with valid data', async () => {
    render(<ProjectForm onSubmit={mockOnSubmit} />);

    const nameInput = screen.getByLabelText(/nome do projeto/i);
    const descriptionInput = screen.getByLabelText(/descrição/i);
    const submitButton = screen.getByRole('button', { name: /criar projeto/i });

    fireEvent.change(nameInput, { target: { value: 'Test Project' } });
    fireEvent.change(descriptionInput, { target: { value: 'Test Description' } });
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith({
        name: 'Test Project',
        description: 'Test Description',
      });
    });
  });

  it('shows validation error for empty name', async () => {
    render(<ProjectForm onSubmit={mockOnSubmit} />);

    const submitButton = screen.getByRole('button', { name: /criar projeto/i });
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/nome é obrigatório/i)).toBeInTheDocument();
    });

    expect(mockOnSubmit).not.toHaveBeenCalled();
  });

  it('disables submit button while loading', () => {
    render(<ProjectForm onSubmit={mockOnSubmit} isLoading={true} />);

    const submitButton = screen.getByRole('button', { name: /criando.../i });
    expect(submitButton).toBeDisabled();
  });
});

Testes de Hook

// frontend/src/__tests__/hooks/useProjects.test.tsx
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useProjects } from '../../hooks/useProjects';

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
    },
  });

  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
};

describe('useProjects', () => {
  it('fetches projects successfully', async () => {
    const { result } = renderHook(() => useProjects(), {
      wrapper: createWrapper(),
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    expect(result.current.data).toHaveLength(2);
    expect(result.current.data?.[0].name).toBe('Test Project 1');
  });

  it('handles error state', async () => {
    // Mock error response
    const { result } = renderHook(() => useProjects(), {
      wrapper: createWrapper(),
    });

    await waitFor(() => {
      expect(result.current.isError).toBe(true);
    });
  });
});

🎭 E2E Testing com Playwright

Configuração Playwright

// frontend/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
  ],

  webServer: {
    command: 'pnpm dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Testes E2E

// frontend/e2e/tests/project-management.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Project Management', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('should create a new project', async ({ page }) => {
    // Navigate to new project page
    await page.click('[data-testid="new-project-button"]');
    await expect(page).toHaveURL('/projects/new');

    // Fill project form
    await page.fill('[data-testid="project-name"]', 'E2E Test Project');
    await page.fill('[data-testid="project-description"]', 'Created by E2E test');

    // Submit form
    await page.click('[data-testid="submit-button"]');

    // Verify success
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
    await expect(page).toHaveURL('/projects');
    await expect(page.locator('text=E2E Test Project')).toBeVisible();
  });

  test('should edit existing project', async ({ page }) => {
    // Create project first
    await page.goto('/projects/new');
    await page.fill('[data-testid="project-name"]', 'Project to Edit');
    await page.click('[data-testid="submit-button"]');

    // Navigate to edit
    await page.click('[data-testid="edit-project-button"]');

    // Edit project
    await page.fill('[data-testid="project-name"]', 'Edited Project');
    await page.click('[data-testid="submit-button"]');

    // Verify changes
    await expect(page.locator('text=Edited Project')).toBeVisible();
  });

  test('should delete project', async ({ page }) => {
    // Create project first
    await page.goto('/projects/new');
    await page.fill('[data-testid="project-name"]', 'Project to Delete');
    await page.click('[data-testid="submit-button"]');

    // Delete project
    await page.click('[data-testid="delete-project-button"]');
    await page.click('[data-testid="confirm-delete"]');

    // Verify deletion
    await expect(page.locator('text=Project to Delete')).not.toBeVisible();
  });
});

⚡ Performance Testing

Configuração Artillery

# performance/load-test.yml
config:
  target: 'http://localhost:3001'
  phases:
    - duration: 60
      arrivalRate: 1
      name: "Warm up"
    - duration: 300
      arrivalRate: 5
      rampTo: 50
      name: "Ramp up load"
    - duration: 600
      arrivalRate: 50
      name: "Sustained load"
  processor: "./processor.js"

scenarios:
  - name: "Project CRUD Operations"
    weight: 70
    flow:
      - get:
          url: "/api/projects"
      - post:
          url: "/api/projects"
          json:
            name: "Load Test Project {{ $randomString() }}"
            description: "Created during load test"
      - get:
          url: "/api/projects/{{ id }}"
          capture:
            - json: "$.data.id"
              as: "projectId"

  - name: "State Management"
    weight: 30
    flow:
      - get:
          url: "/api/states"
      - post:
          url: "/api/states"
          json:
            name: "Test State {{ $randomString() }}"
            color: "#{{ $randomHex() }}"

Scripts de Performance

# scripts/performance-test.sh
#!/bin/bash

echo "🚀 Iniciando testes de performance..."

# Subir aplicação em modo de produção
docker-compose -f docker-compose.prod.yml up -d

# Aguardar inicialização
sleep 30

# Executar testes de carga
cd performance
artillery run load-test.yml --output load-test-report.json

# Gerar relatório HTML
artillery report load-test-report.json

# Teste de performance frontend
cd ../frontend
pnpm lighthouse http://localhost:3000 --output html --output-path ./lighthouse-report.html

echo "✅ Testes de performance concluídos!"
echo "📊 Relatórios disponíveis em:"
echo "   - performance/load-test-report.json.html"
echo "   - frontend/lighthouse-report.html"

🔧 Scripts de Teste

Scripts de Teste (Implementados)

// backend/package.json - Scripts disponíveis
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:verbose": "jest --coverage --verbose"
  }
}

Como Executar os Testes

# Executar todos os testes
cd backend && pnpm test

# Executar com cobertura (atual: 98.58%)
cd backend && pnpm test:coverage

# Executar em modo watch (desenvolvimento)
cd backend && pnpm test:watch

# Executar com informações detalhadas
cd backend && pnpm test:verbose

# Executar testes específicos
cd backend && pnpm test states.integration
cd backend && pnpm test project.service
cd backend && pnpm test basic.test

Resultados Atuais (Junho 2025)

✅ Test Suites: 33 passed, 33 total
✅ Tests: 517 passed, 517 total
✅ Coverage: 98.58% statements, 83.06% branches
✅ Time: ~4.1s para execução completa
✅ Snapshots: 0 total (não utilizados)

CI/CD Pipeline

# .github/workflows/test.yml
name: Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Run backend tests
        run: |
          cd backend
          pnpm test:ci

      - name: Run frontend tests
        run: |
          cd frontend
          pnpm test:ci

      - name: Upload coverage
        uses: codecov/codecov-action@v3

  e2e-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Install Playwright
        run: npx playwright install --with-deps

      - name: Start application
        run: |
          docker-compose up -d
          sleep 30

      - name: Run E2E tests
        run: |
          cd frontend
          pnpm test:e2e

      - name: Upload test results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: frontend/playwright-report/

📊 Métricas e Cobertura (Atualizadas)

Targets de Cobertura

  • Backend: ✅ 98.58% (Target atingido: > 80%)
  • Frontend: 🟡 Não implementado (Target: > 75%)
  • E2E: 🟡 Não implementado (Target: > 90% dos fluxos críticos)

Detalhamento da Cobertura Backend

Módulo                  | Statements | Branches | Functions | Lines |
------------------------|------------|----------|-----------|-------|
config/                 |    100%    |   100%   |   100%    | 100%  |
modules/comment/        |    100%    |  88.88%  |   100%    | 100%  |
modules/projects/       |   96.78%   |  72.16%  |  89.65%   | 100%  |
modules/states/         |    100%    |    88%   |   100%    | 100%  |
modules/todo/           |    100%    |   100%   |   100%    | 100%  |
utils/                  |    100%    |   100%   |   100%    | 100%  |
utils/tools/            |    100%    |   100%   |   100%    | 100%  |
------------------------|------------|----------|-----------|-------|
TOTAL                   |   98.58%   |  83.06%  |  96.62%   | 100%  |

Relatórios Disponíveis

# Visualizar cobertura em HTML
open backend/coverage/lcov-report/index.html

# Cobertura em formato texto
cat backend/coverage/lcov.info

# Relatório JSON para CI/CD
cat backend/coverage/coverage-final.json

Áreas com Menor Cobertura (Para Melhoria)

  1. modules/projects/ - 72.16% branches
    • Faltam testes para alguns cenários de erro
    • Alguns métodos condicionais não cobertos
  2. modules/comment/ - 88.88% branches
    • Casos edge de validação
  3. modules/states/ - 88% branches
    • Validações específicas de estado

🚨 Troubleshooting

Problemas Comuns

  1. Testes flaky: Usar waitFor e timeouts adequados
  2. Mocks não funcionam: Verificar ordem de imports
  3. E2E falham: Verificar se aplicação está rodando
  4. Performance ruim: Verificar recursos do sistema

Debug de Testes

# Debug jest
node --inspect-brk node_modules/.bin/jest --runInBand

# Debug playwright
npx playwright test --debug

# Logs detalhados
DEBUG=* pnpm test

📋 Checklist de Testes

Antes do Commit

  • Testes unitários passando
  • Cobertura mínima atingida
  • Linting sem erros
  • TypeScript sem erros

Antes do Deploy

  • Todos os testes passando
  • Testes E2E executados
  • Performance validada
  • Smoke tests em produção