Skip to content

Testy

Projekt používá Pest PHP pro testování. Testy jsou organizovány do čtyř kategorií: Unit, Integration, Feature a Architecture. Všechny jsou uloženy v adresáři /tests.

Spuštění testů

Všechny testy

./pest

Konkrétní kategorii testů

./pest tests/Unit
./pest tests/Integration
./pest tests/Feature
./pest tests/Architecture

Filtrování podle názvu

./pest --filter CacheTest
./pest --filter "save item"
./pest --filter Cache tests/Unit

Konkrétní soubor

./pest tests/Unit/Core/Cache/CacheTest.php

Testy se skupinami

./pest --group architecture
./pest --group database
./pest --group cache

Paralelní běh (zrychlení)

./pest --parallel

Struktura testů

Testy jsou organizovány podle skutečné vrstvy kódu, ne podle funkce:

tests/
├── Unit/                                    # Jednotlivé komponenty bez závislostí
│   ├── Core/                                # core/ kód
│   │   ├── Array/
│   │   ├── Cache/
│   │   │   └── CacheTest.php
│   │   ├── FileSystem/
│   │   ├── Http/
│   │   ├── Validation/
│   │   └── ... (35+ test souborů)
│   │
│   ├── App/                                 # app/* mimo Modules
│   ├── Console/                             # Parser a CLI helpery
│   │
│   └── Modules/                             # app/Modules/ bez DB (zatím prázdné)
│
├── Integration/                             # Skutečné závislosti (DB, soubory)
│   ├── Core/                                # core/ s databází
│   │   ├── Cache/
│   │   │   └── CacheFileTest.php
│   │   ├── Database/
│   │   │   ├── DBTest.php
│   │   │   ├── DBSetTest.php
│   │   │   └── DBSpecialCasesTest.php
│   │   └── Session/
│   │       └── SessionTest.php
│   │
│   ├── App/                                 # app/* s DB (TBD)
│   │
│   └── Modules/                             # app/Modules/ s DB (business logic)
│       └── I18n/
│           └── I18nFrontendServiceTest.php
│
├── Feature/                                  # End-to-end nebo route-level scénáře
│
├── Architecture/                             # Globální architekturní pravidla
│   ├── CubeSystemArchTest.php
│   ├── MiddlewareAndRoutingArchTest.php
│   ├── TemplateLayerArchTest.php
│   └── README.md
│
├── Support/                                  # Test helpery a fixtures
│   ├── Architecture/
│   │   └── ArchitectureHelper.php
│   └── DatabaseSetup.php
│
└── Pest.php                                  # Globální Pest konfigurace

Vrstvení — Co kam patří

  • Unit/Core/...core/ kód bez DB, bez souboru
  • Unit/App/...app/* mimo modules, bez DB
  • Unit/Console/... — parser a CLI chování
  • Unit/Modules/...app/Modules/* bez DB přístupu
  • Integration/Core/...core/ s DB, Session, File cache
  • Integration/Modules/...app/Modules/* s DB přístupem (Service vrstva)
  • Feature/... — scénáře přes route, request/response nebo vyšší vrstvu aplikace

Psaní testů

Jednoduchý Unit test

<?php

declare(strict_types=1);

use Core\Cache\Cache;

test('save item with valid data', function () {
    $itemName = 'test_item';
    $data = ['key' => 'value'];

    Cache::set($itemName, $data, 60);

    expect(Cache::get($itemName))->toEqual($data);
});

test('save item with empty array', function () {
    Cache::set('test', [], 60);
    expect(Cache::get('test'))->toEqual([]);
});

Test se skupinou a tagy

<?php

test('SQL is parameterized', function () {
    // test code
})
    ->group('architecture', 'database');

Integration test s databází

<?php

declare(strict_types=1);

use Core\Database\DB;

// Databáze se automaticky inicializuje (viz tests/Pest.php)
// Transakce se vrací po testu (rollback)

test('insert user', function () {
    DB::beginTransaction();

    DB::query("INSERT INTO temp_users (name) VALUES (?)", ['Alice']);
    $user = DB::row("SELECT * FROM temp_users WHERE name = ?", ['Alice']);

    expect($user)->not->toBeNull();
    expect($user->name)->toEqual('Alice');

    DB::rollback();
});

Test s Before/After hooks

<?php

beforeAll(function () {
    // Spustí se jednou na začátku všech testů v souboru
    Cache::clear();
});

afterAll(function () {
    // Spustí se jednou na konci všech testů v souboru
    Cache::clear();
});

test('uses clean cache', function () {
    // Cache je čistá
});

Expect Assertions (Pest)

Pest používá fluent syntaxi přes expect():

// Základní kontroly
expect($result)->toBe(true);
expect($result)->toBeTrue();
expect($result)->toBeFalse();
expect($result)->toBeNull();

// Porovnání
expect($value)->toEqual(5);
expect($value)->toBe(5);                        // Striktnější (===)
expect($value)->toNotEqual(5);

// Typy
expect($value)->toBeArray();
expect($value)->toBeString();
expect($value)->toBeInt();
expect($value)->toBeInstanceOf(stdClass::class);

// Stringy
expect($text)->toContain('substring');
expect($text)->toMatch('/pattern/');
expect($text)->toStartWith('prefix');
expect($text)->toEndWith('suffix');

// Kolekce
expect($array)->toHaveCount(3);
expect($array)->toHaveKey('key');
expect($array)->toContainEqual('value');

// Řetězování
expect($result)
    ->toBeArray()
    ->toHaveCount(3)
    ->toHaveKey('status')
    ->and($result['status'])->toEqual('ok');

Chyby a výjimky

// Kontrola vyhozené výjimky
expect(function () {
    throw new Exception('Error message');
})->toThrow(Exception::class);

// S konkrétní zprávou
expect(function () {
    throw new Exception('Custom error');
})->toThrow(Exception::class, 'Custom error');

// V PHPUnit stylu (legacy)
$this->expectException(Exception::class);
$this->expectExceptionMessage('message');

Fixture Data & Faker

Projektuje k dispozici knihovna Faker pro generování testovacích dat:

<?php

use Faker\Factory;

beforeEach(function () {
    $faker = Factory::create('cs_CZ');

    $this->user = [
        'name' => $faker->name(),
        'email' => $faker->email(),
        'phone' => $faker->phoneNumber(),
        'city' => $faker->city(),
    ];
});

test('creates user with faker data', function () {
    expect($this->user['name'])->toBeString();
});

Pest Konfigurace

Konfigurace je v tests/Pest.php:

use Tests\Support\DatabaseSetup;
require_once __DIR__ . '/Support/Architecture/ArchitectureHelper.php';

// Automatická inicializace databáze pro Integration testy
uses()
    ->beforeEach(fn() => DatabaseSetup::init())
    ->in('Integration/Core/Database', 'Integration/Core/I18n', 'Integration/Modules');

Helpery jsou v tests/Support/: - DatabaseSetup.php — Inicializace dočasných DB tabulek pro testy - Architecture/ArchitectureHelper.php — Utility pro Architecture testy

Běžné Příklady

Test Cache komponenty

./pest --filter CacheTest
./pest tests/Unit/Core/Cache/CacheTest.php

Jen Unit testy

./pest tests/Unit

Jen Architecture testy

./pest tests/Architecture

S detailním výstupem

./pest -vv tests/Unit/Core/Cache/CacheTest.php

Best Practices

  1. Jeden test = jednu věc: Každý test() by měl testovat jednu funkcionalitu
  2. Jasné názvy: test('save item with valid data') je lepší než test('save')
  3. Arrange-Act-Assert: Příprava → Spuštění → Kontrola
  4. Čisté testy: Database/Cache se automaticky čistí mezi testy
  5. Soubory podle třídy: I18nFrontendServiceTest.php testuje I18nFrontendService, ne ServiceTest.php
  6. Umístění podle vrstvy:
  7. core/Foo/Bar.phptests/Unit/Core/Foo/BarTest.php (nebo Integration)
  8. app/Modules/News/Service.phptests/Unit/Modules/News/ServiceTest.php (nebo Integration)
  9. app/Console/Parser.phptests/Unit/App/Console/ParserTest.php
  10. Žádný suffix v názvu: CacheFileTest.php (ne CacheFileIntegrationTest.php — typ je v cestě)

Continuous Integration

Při commitu spustit testy:

./pest

Jen testy bez lintů:

./pest