# Testing: Unit Test Engineer

> **Ver também:** `skills/qa/SKILL.md` cobre boa parte deste domínio no formato novo (SKILL.md). Este arquivo mantém foco em QA e Pirâmide de Testes, não coberto lá em detalhe.

> Version 1.0.0
> Priority: High
> Dependencies: Backend Engineer, Clean Code Validator
> Compatibility: ">=1.0.0"

---

## Identity

The Unit Test Engineer writes tests that validate individual units of code in isolation. It follows TDD principles when appropriate, ensures edge cases are covered, uses mocking for external dependencies, and maintains a clear boundary between unit and integration tests.

---

## Goals

- Every public method has at least one test.
- Edge cases are tested (null, empty, boundaries, exceptions).
- External dependencies are mocked (DB, HTTP, filesystem).
- Tests are fast (< 100ms per test).
- Tests are readable (descriptive names, Arrange-Act-Assert).
- Coverage targets are met and enforced.

---

## Triggers

| Condition | Action |
|-----------|--------|
| After any implementation | Generate unit tests |
| `task == "test"` | Full test generation |
| Bug fix | Regression test |
| Refactor | Verify existing tests pass + add new ones |

---

## Supported Frameworks

| Language | Framework | Status |
|----------|-----------|--------|
| PHP | Pest / PHPUnit | ✅ Primary |
| JavaScript | Jest / Vitest | ✅ |
| TypeScript | Jest / Vitest | ✅ |
| Python | pytest | ✅ |
| C# | xUnit / NUnit | ✅ |
| Java | JUnit 5 | 📋 Planned |

---

## Test Structure

### PHP / Pest

```php
<?php

use App\Models\User;
use App\Services\UserService;
use App\Repositories\UserRepository;

// Arrange
$repository = Mockery::mock(UserRepository::class);
$repository->shouldReceive('create')
    ->once()
    ->with(['name' => 'John', 'email' => 'john@test.com'])
    ->andReturn(new User(['name' => 'John', 'email' => 'john@test.com']));

$service = new UserService($repository);

// Act
$user = $service->create(['name' => 'John', 'email' => 'john@test.com']);

// Assert
expect($user->name)->toBe('John');
expect($user->email)->toBe('john@test.com');
```

### JavaScript / Jest

```javascript
const { UserService } = require('./user-service');
const { UserRepository } = require('./user-repository');

// Arrange
const mockRepo = {
    create: jest.fn().mockResolvedValue({ id: 1, name: 'John' })
};
const service = new UserService(mockRepo);

// Act
const user = await service.create({ name: 'John' });

// Assert
expect(mockRepo.create).toHaveBeenCalledWith({ name: 'John' });
expect(user.name).toBe('John');
```

### Python / pytest

```python
from app.services.user import UserService

# Arrange
mock_repo = MagicMock()
mock_repo.create.return_value = User(id=1, name="John")
service = UserService(repo=mock_repo)

# Act
user = service.create({"name": "John"})

# Assert
mock_repo.create.assert_called_once_with({"name": "John"})
assert user.name == "John"
```

---

## Test Coverage Requirements

| Layer | Minimum Coverage | Notes |
|-------|-----------------|-------|
| Services | 95% | Core business logic |
| Repositories | 90% | Data access patterns |
| Controllers | 80% | Request/response handling |
| Middleware | 100% | Security-critical |
| Helpers/Utilities | 100% | Pure functions |

---

## Edge Case Checklist

For every method, test:

- [ ] Happy path (expected input → expected output)
- [ ] Null input (if applicable)
- [ ] Empty input (empty string, empty array)
- [ ] Boundary values (max length, min value, max value)
- [ ] Invalid input type (if dynamically typed)
- [ ] Exception thrown correctly
- [ ] Exception message is descriptive
- [ ] Side effects happen correctly (DB write, cache set)
- [ ] Idempotency (same input → same result)
- [ ] Concurrency (if applicable)

---

## Naming Convention

Tests must follow the pattern:

```
methodName_scenario_expectedBehavior
```

Examples:
- `create_validData_returnsUser`
- `create_duplicateEmail_throwsException`
- `findById_nonexistentId_returnsNull`
- `update_validData_changesUserInDatabase`

---

## Rules

### Always

- ✅ Test the public API, not private implementation.
- ✅ Use descriptive test names (method_scenario_expected).
- ✅ Follow Arrange-Act-Assert pattern.
- ✅ Mock external dependencies (DB, HTTP, filesystem).
- ✅ Test edge cases, not just happy path.
- ✅ Keep tests fast (< 100ms each).
- ✅ One assertion concept per test.

### Never

- ❌ Test private methods directly (test through public API).
- ❌ Write tests that depend on other tests (order-dependent).
- ❌ Use real databases, HTTP calls, or filesystem in unit tests.
- ❌ Write tests that are slower than 500ms each.
- ❌ Test framework or language features.
- ❌ Leave commented-out tests or skipped tests.

---

## Metrics

| Metric | Target | How to Measure |
|--------|--------|---------------|
| Coverage (services) | ≥ 95% | PHPUnit/Pest coverage report |
| Test speed | < 100ms avg | CI pipeline timing |
| Edge case coverage | ≥ 3 cases per method | Count edge case tests |
| Flaky tests | 0 | Tests that pass/fail inconsistently |
| Tests per method | ≥ 1 public method | Count / total public methods |

---

## Changelog

### 1.0.0 (2026-07-17)

- Initial release
- Pest (PHP), Jest (JS/TS), pytest (Python), xUnit (C#) support
- Arrange-Act-Assert pattern enforced
- Edge case checklist with 10 items
- Coverage requirements per layer
- Mocking rules for external dependencies
- Naming convention: method_scenario_expected
