Lewati ke konten utama

BAB 57. TESTING STRATEGY

Tujuan

Menjamin kualitas sistem dan mencegah regresi sebelum kode masuk ke production melalui pendekatan Test Pyramid.


Piramida Testing

flowchart TD
E2E["🔺 E2E Test\n(Sedikit, Lambat, Mahal)"]
FEAT["🔷 Feature / Integration Test"]
UNIT["🔹 Unit Test\n(Banyak, Cepat, Murah)"]

E2E --> FEAT --> UNIT

Semakin bawah piramida = semakin banyak, cepat, dan murah.


Backend Testing — Laravel

Unit Test

Menguji logika terisolasi tanpa database.

TargetContoh
AI Scoring Enginetest_skill_similarity_returns_100_for_exact_match
SIS Calculationtest_sis_score_increases_after_successful_session
XP Calculationtest_xp_awarded_based_on_duration_and_rating
Repository Mocktest_create_request_calls_repository_once
// tests/Unit/AI/ScoringEngineTest.php

class ScoringEngineTest extends TestCase
{
public function test_skill_similarity_exact_match_returns_100(): void
{
$engine = new ScoringEngine();
$score = $engine->calculateSkillSimilarity(
tutorSkillId: 5,
requestSubjectId: 5
);

$this->assertEquals(100, $score);
}
}

Feature Test

Menguji endpoint API secara end-to-end dengan database test.

EndpointTest Case
POST /auth/loginLogin berhasil, password salah, akun nonaktif
POST /requestsRequest dibuat, validasi gagal, unauthorized
POST /bookings/{id}/acceptAccept berhasil, sudah accepted, not found
POST /meetings/check-inQR valid, QR expired, diluar radius
POST /ratingsRating disimpan, duplicate rating dicegah
// tests/Feature/Api/RequestTest.php

class RequestTest extends TestCase
{
use RefreshDatabase;

public function test_student_can_create_request(): void
{
$student = Student::factory()->create();

$response = $this->actingAs($student->user, 'sanctum')
->postJson('/api/v1/requests', [
'subject_id' => 1,
'description' => 'Butuh bantuan integral',
'preferred_location' => 'Perpustakaan',
'preferred_time' => now()->addDay(),
'duration_minutes' => 60,
]);

$response->assertCreated()
->assertJsonPath('data.status', 'searching');
}
}

Flutter Testing

Widget Test

Menguji komponen UI secara terisolasi.

// test/widget/login_form_test.dart

void main() {
testWidgets('Login form shows error when fields empty', (tester) async {
await tester.pumpWidget(const MaterialApp(home: LoginPage()));

await tester.tap(find.byKey(const Key('btn_login')));
await tester.pump();

expect(find.text('Username wajib diisi'), findsOneWidget);
});
}

Integration Test

Menguji alur pengguna dari login hingga request.

// integration_test/request_flow_test.dart

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Student can login and create request', (tester) async {
app.main();
await tester.pumpAndSettle();

// Login
await tester.enterText(find.byKey(const Key('field_username')), '1234567890');
await tester.enterText(find.byKey(const Key('field_password')), 'password');
await tester.tap(find.byKey(const Key('btn_login')));
await tester.pumpAndSettle();

expect(find.text('Dashboard'), findsOneWidget);
});
}

Coverage Target

LayerTarget Coverage
Domain / Use Cases90%
Service Layer85%
Repository80%
Controller75%
Widget70%
Overall Minimum80%

Perintah Testing

# Backend — semua test
php artisan test

# Backend — dengan coverage
php artisan test --coverage --min=80

# Backend — parallel
php artisan test --parallel

# Flutter — unit & widget
flutter test

# Flutter — integration
flutter test integration_test/