Beispielarbeiten · Code & Fehler
Laravel: Aufgabenverwaltung mit Migration, Eingabeprüfung und Tests
Eine kleine Laravel-Anwendung mit Datenbanktabelle, Controller, Formularprüfung und Ansicht. Geprüft mit den Laravel-Tests und mit echten HTTP-Aufrufen, dazu eine Gegenprobe mit kaputten Fassungen.
Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.
Was gebaut wurde
- Migration, Modell, Controller und Routen im üblichen Laravel-Aufbau
- Eingaben über eine eigene Request-Klasse geprüft, Fehler erscheinen im Formular
- Feature-Tests mit artisan test und Aufrufe gegen den laufenden Server
- Gegenprobe: jede absichtlich kaputte Fassung wird erkannt
laravel-anwendung-quellcode.zip herunterladen
Aktuelles Laravel auf PHP 8.3; ältere Laravel-Versionen sind nicht geprüft. Datenbank nur SQLite, MySQL und PostgreSQL liefen nicht. Eingebauter Server statt Apache oder nginx, ohne Queues, Scheduler und Redis. Anmeldung, Livewire, Inertia und der Frontend-Build sind nicht geprüft. Das Archiv enthält die eigenen Dateien, nicht den vendor-Ordner.
Wie geprüft wurde
Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):
- artisan test 11 gruen, 0 rot
- HTTP 29/29 gruen
- Gegenprobe 4/4 kaputte Fassungen erkannt
Grenzen
Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:
- Laravel 13 (aktuelles create-project) auf PHP 8.3 - aeltere Laravel-Versionen (5-10) mit eigenem Aufbau/Paketen nicht geprueft
- Datenbank nur SQLite; MySQL/PostgreSQL nur ueber Konfiguration, nicht gelaufen
- eingebauter Server (artisan serve), kein Apache/nginx/FPM; keine Queues/Scheduler/Broadcasting, kein Redis
- nicht geprueft: Authentifizierung (Breeze/Jetstream/Sanctum), Livewire/Inertia/Vue-Frontend, npm/Vite-Build (Node nicht eingerichtet)
- Pakete kommen aus Packagist (Netz noetig beim ersten Lauf); Kundenprojekte mit privaten Paketen brauchen Zugang
- keine Arbeit auf dem Server des Kunden ohne Zugang; Ergebnis ist Quellcode + Testnachweis
Der Controller
<?php
namespace App\Http\Controllers;
use App\Http\Requests\AufgabeRequest;
use App\Models\Aufgabe;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class AufgabeController extends Controller
{
public function index(): View
{
return view('aufgaben.index', ['aufgaben' => Aufgabe::orderBy('id')->get()]);
}
public function store(AufgabeRequest $request): RedirectResponse
{
Aufgabe::create($request->validated());
return redirect()->route('aufgaben.index')->with('status', 'Gespeichert');
}
public function update(AufgabeRequest $request, Aufgabe $aufgabe): RedirectResponse
{
$aufgabe->update($request->validated());
return redirect()->route('aufgaben.index')->with('status', 'Geaendert');
}
public function destroy(Aufgabe $aufgabe): RedirectResponse
{
$aufgabe->delete();
return redirect()->route('aufgaben.index')->with('status', 'Geloescht');
}
}
Die Tests
<?php
namespace Tests\Feature;
use App\Models\Aufgabe;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AufgabenTest extends TestCase
{
use RefreshDatabase;
public function test_startseite_leitet_auf_liste_und_zeigt_formular_mit_token(): void
{
$this->get('/')->assertRedirect('/aufgaben');
$r = $this->get('/aufgaben')->assertOk();
$r->assertSee('name="_token"', false);
}
public function test_anlegen_speichert_und_zeigt_in_liste(): void
{
$this->post('/aufgaben', ['titel' => 'Jörg zurückrufen', 'prioritaet' => 2])
->assertRedirect('/aufgaben');
$this->assertDatabaseHas('aufgaben', ['titel' => 'Jörg zurückrufen', 'prioritaet' => 2, 'erledigt' => false]);
$this->get('/aufgaben')->assertSee('Jörg zurückrufen');
}
public static function ungueltige(): array
{
return [
'Titel leer' => [['titel' => '', 'prioritaet' => 3], 'titel'],
'Titel 101 Zeichen' => [['titel' => str_repeat('x', 101), 'prioritaet' => 3], 'titel'],
'Prioritaet 0' => [['titel' => 'ok', 'prioritaet' => '0'], 'prioritaet'],
'Prioritaet 6' => [['titel' => 'ok', 'prioritaet' => '6'], 'prioritaet'],
'Prioritaet Text' => [['titel' => 'ok', 'prioritaet' => 'viel'], 'prioritaet'],
];
}
#[DataProvider('ungueltige')]
public function test_validierung_lehnt_ab_und_speichert_nichts(array $daten, string $feld): void
{
$this->post('/aufgaben', $daten)->assertSessionHasErrors($feld);
$this->assertDatabaseCount('aufgaben', 0);
}
public function test_titel_wird_escaped(): void
{
Aufgabe::create(['titel' => '<script>alert(1)</script>', 'prioritaet' => 3]);
$this->get('/aufgaben')->assertDontSee('<script>alert(1)</script>', false)
->assertSee('<script>alert(1)</script>', false);
}
public function test_aendern_und_loeschen(): void
{
$a = Aufgabe::create(['titel' => 'alt', 'prioritaet' => 3]);
$this->put("/aufgaben/{$a->id}", ['titel' => 'neu', 'prioritaet' => 1, 'erledigt' => 1])->assertRedirect('/aufgaben');
$this->assertDatabaseHas('aufgaben', ['id' => $a->id, 'titel' => 'neu', 'erledigt' => true]);
$this->delete("/aufgaben/{$a->id}")->assertRedirect('/aufgaben');
$this->assertDatabaseMissing('aufgaben', ['id' => $a->id]);
}
public function test_unbekannte_id_ist_404(): void
{
$this->put('/aufgaben/999', ['titel' => 'x', 'prioritaet' => 3])->assertNotFound();
$this->delete('/aufgaben/999')->assertNotFound();
}
}
So etwas für deinen Betrieb?
Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.