science 19. Testes Automatizados - PHPUnit e CI/CD

Testes automatizados garantem que seu código funciona corretamente e evitam bugs ao fazer mudanças. Com testes, você pode refatorar sem medo, sabendo que se algo quebrar, os testes vão alertar.

info
ROI de Testes: 10 minutos escrevendo testes hoje economiza horas debugando no futuro. Empresas sérias exigem >80% de code coverage.

code Backend PHP - PHPUnit

warning
Instalação: composer require --dev phpunit/phpunit
// tests/UserServiceTest.php
db = new PDO('sqlite::memory:');
        
        // Criar tabela
        $this->db->exec("
            CREATE TABLE users (
                id INTEGER PRIMARY KEY,
                name TEXT,
                email TEXT UNIQUE,
                password TEXT
            )
        ");
        
        $this->userService = new UserService($this->db);
    }
    
    public function testCreateUser() {
        $userData = [
            'name' => 'João Silva',
            'email' => 'joao@email.com',
            'password' => 'senha123'
        ];
        
        $user = $this->userService->create($userData);
        
        $this->assertNotNull($user);
        $this->assertEquals('João Silva', $user['name']);
        $this->assertEquals('joao@email.com', $user['email']);
        $this->assertTrue(password_verify('senha123', $user['password']));
    }
    
    public function testCreateUserDuplicateEmail() {
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Email já cadastrado');
        
        $userData = [
            'name' => 'João',
            'email' => 'joao@email.com',
            'password' => 'senha123'
        ];
        
        $this->userService->create($userData);
        $this->userService->create($userData); // Deve falhar
    }
    
    public function testAuthenticateSuccess() {
        // Criar usuário
        $this->userService->create([
            'name' => 'Maria',
            'email' => 'maria@email.com',
            'password' => 'senha456'
        ]);
        
        // Tentar autenticar
        $user = $this->userService->authenticate('maria@email.com', 'senha456');
        
        $this->assertNotNull($user);
        $this->assertEquals('Maria', $user['name']);
    }
    
    public function testAuthenticateWrongPassword() {
        $this->userService->create([
            'name' => 'Pedro',
            'email' => 'pedro@email.com',
            'password' => 'senha789'
        ]);
        
        $user = $this->userService->authenticate('pedro@email.com', 'senhaErrada');
        
        $this->assertNull($user);
    }
}

// Rodar testes:
// ./vendor/bin/phpunit tests/

integration_instructions Frontend React - Jest + Testing Library

// src/components/__tests__/CourseCard.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { CourseCard } from '../CourseCard';

describe('CourseCard', () => {
    const mockCourse = {
        id: 1,
        title: 'React Avançado',
        price: 4990,
        thumbnail_url: 'https://example.com/thumb.jpg'
    };

    test('renderiza informações do curso', () => {
        render();
        
        expect(screen.getByText('React Avançado')).toBeInTheDocument();
        expect(screen.getByText('R$ 49,90')).toBeInTheDocument();
    });

    test('chama onBuy ao clicar no botão', () => {
        const onBuy = jest.fn();
        render();
        
        const button = screen.getByRole('button', { name: /comprar/i });
        fireEvent.click(button);
        
        expect(onBuy).toHaveBeenCalledWith(mockCourse.id);
    });
});

// src/hooks/__tests__/useAuth.test.js
import { renderHook, act } from '@testing-library/react';
import { useAuth } from '../useAuth';
import axios from 'axios';

jest.mock('axios');

describe('useAuth', () => {
    test('login com sucesso', async () => {
        const mockUser = { id: 1, name: 'João' };
        axios.post.mockResolvedValue({ data: { user: mockUser } });
        
        const { result } = renderHook(() => useAuth());
        
        await act(async () => {
            await result.current.login('joao@email.com', 'senha123');
        });
        
        expect(result.current.user).toEqual(mockUser);
        expect(result.current.isAuthenticated).toBe(true);
    });
    
    test('login com credenciais inválidas', async () => {
        axios.post.mockRejectedValue({ response: { status: 401 } });
        
        const { result } = renderHook(() => useAuth());
        
        await act(async () => {
            try {
                await result.current.login('joao@email.com', 'senhaErrada');
            } catch (e) {
                // Esperado
            }
        });
        
        expect(result.current.user).toBeNull();
        expect(result.current.isAuthenticated).toBe(false);
    });
});

// Rodar testes:
// npm test

sync CI/CD com GitHub Actions

# .github/workflows/ci.yml
name: CI/CD Pipeline

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

jobs:
  backend-tests:
    runs-on: ubuntu-latest
    
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: testdb
        ports:
          - 3306:3306
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: mbstring, pdo_mysql
      
      - name: Install Dependencies
        run: composer install --no-interaction
      
      - name: Run PHPUnit Tests
        env:
          DB_HOST: 127.0.0.1
          DB_DATABASE: testdb
          DB_USERNAME: root
          DB_PASSWORD: root
        run: ./vendor/bin/phpunit tests/
      
      - name: Code Coverage
        run: ./vendor/bin/phpunit --coverage-clover coverage.xml
      
      - name: Upload Coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml

  frontend-tests:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install Dependencies
        run: npm ci
      
      - name: Run Tests
        run: npm test -- --coverage
      
      - name: Build
        run: npm run build

  deploy:
    needs: [backend-tests, frontend-tests]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to Production
        run: |
          echo "Deploying to production..."
          # Comandos de deploy (SSH, FTP, etc)

check_circle Pirâmide de Testes

┌─────────────────────────────────────────────┐
│         PIRÂMIDE DE TESTES                  │
└─────────────────────────────────────────────┘

                 ▲
                / \
               /E2E\      ← 10% E2E (Cypress, Playwright)
              /Tests\       Testa fluxo completo do usuário
             /_______\
            /         \
           /Integration\  ← 30% Integration Tests
          /    Tests    \   Testa integração entre módulos
         /______________\
        /                \
       /   Unit Tests     \ ← 60% Unit Tests
      /____________________\  Testa funções isoladas

Unit Tests: Rápidos, isolados, testam função/classe
Integration: Testam interação entre componentes
E2E: Lentos, testam fluxo completo (simulam usuário real)
rocket_launch
Próximo Passo: Testes implementados! Agora a última seção: Deploy e Produção para colocar sua aplicação no ar!