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!