Pengujian unit adalah metode verifikasi perangkat lunak di mana kebenaran kerja modul atau fungsi kode individu diuji secara terisolasi dari seluruh sistem. Menurut Martin Fowler, 2026, pengujian unit adalah fondasi CI/CD dan refactoring, memberikan umpan balik cepat tentang kelayakan kode. Pengujian modular membantu menemukan kesalahan pada tahap awal pengembangan, mengurangi biaya perbaikannya hingga puluhan kali lipat.
Poin Utama
Pengujian unit adalah proses pemeriksaan unit individu kode sumber — fungsi, metode, kelas — secara terisolasi dari seluruh program. Setiap pengujian menjalankan skenario penggunaan modul tertentu dan memeriksa apakah hasilnya sesuai dengan yang diharapkan. Pengujian unit ditulis dalam bahasa pemrograman yang sama dengan kode utama dan dijalankan secara otomatis di lingkungan pengembangan atau di pipeline CI/CD. Berbeda dengan pengujian integrasi, pengujian unit tidak berinteraksi dengan database nyata, sistem file, atau layanan jaringan.
Tujuan utamanya adalah umpan balik cepat tentang kebenaran kode setelah perubahan. Jika pengembang melakukan refactoring pada suatu metode, kumpulan pengujian unit memastikan bahwa perilaku tidak rusak. Menurut Google Testing Blog (2025), proyek dengan cakupan pengujian unit di atas 60% memiliki 2,5 kali lebih sedikit insiden produksi. Keuntungan tambahan: dokumentasi kode (pengujian menunjukkan cara menggunakan API), penyederhanaan refactoring (implementasi dapat diubah sambil mempertahankan perilaku), dan diagnosis regresi yang cepat.
Tidak semua pengujian otomatis adalah pengujian unit. Kriteria: satu modul (kelas atau fungsi) diuji, ketergantungan eksternal diganti dengan mock atau stub, pengujian dijalankan dalam milidetik, tidak memerlukan menjalankan server atau database. Pengujian yang mengakses database nyata adalah pengujian integrasi. Pengujian yang membuka browser adalah pengujian E2E. Memahami batas antara jenis pengujian penting untuk distribusi upaya yang tepat dalam piramida pengujian.
Pengujian unit berkualitas mengikuti prinsip FIRST yang dirumuskan oleh Robert C. Martin. Setiap pengujian harus Fast (cepat — milidetik), Isolated (terisolasi — tidak bergantung pada pengujian lain), Repeatable (dapat diulang — hasil sama di mesin mana pun), Self-validating (memvalidasi sendiri — hasilnya “passed” atau “failed”, tanpa pemeriksaan manual), dan Timely (tepat waktu — ditulis sebelum atau bersamaan dengan kode). Pelanggaran prinsip mana pun mengurangi nilai pengujian.
Pola standar untuk menulis pengujian unit. Arrange — persiapan data dan ketergantungan: pembuatan objek, konfigurasi mock, pengaturan parameter input. Act — pelaksanaan tindakan yang diuji: pemanggilan metode atau fungsi. Assert — pemeriksaan hasil: perbandingan nilai aktual dengan yang diharapkan. Pembagian menjadi tiga blok membuat pengujian mudah dibaca dan dipahami. Jika blok Assert memerlukan logika kompleks, kemungkinan pengujian memeriksa terlalu banyak hal sekaligus.
// Contoh pengujian unit dengan pola AAA di Kotlin dengan JUnit 5
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
// ARRANGE — kita buat objek yang diuji
calculator = Calculator()
}
@Test
fun addition_shouldReturnCorrectSum() {
// ACT — kita lakukan tindakan
val result = calculator.add(2, 3)
// ASSERT — kita periksa hasilnya
Assertions.assertEquals(5, result)
}
}Nama pengujian harus menjelaskan apa yang diperiksa dan hasil apa yang diharapkan. Format: [methodName]_[scenario]_[expectedResult]. Contoh: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Nama pengujian yang baik menggantikan komentar dan saat gagal langsung menunjukkan fungsionalitas mana yang rusak. Hindari nama seperti test1, checkSomething, atau verify — mereka tidak membawa informasi dan mempersulit diagnosis.
Untuk mengisolasi modul yang diuji dari ketergantungan eksternal, digunakan pengganti pengujian (test doubles). Jenis utama: mock — memeriksa apakah metode tertentu dipanggil dengan parameter yang benar; stub — mengembalikan nilai yang ditentukan saat metode dipanggil; fake — implementasi sederhana dari komponen nyata (misalnya, InMemoryUserRepository sebagai ganti UserRepository yang bekerja dengan database). Pilihan tergantung pada apa yang perlu diperiksa: kondisi (stub) atau interaksi (mock).
| Pengganti | Apa yang diperiksa | Contoh |
|---|---|---|
| Mock | Panggilan metode dengan parameter benar | userRepository.save(user) dipanggil tepat 1 kali |
| Stub | Nilai yang dikembalikan | repository.findById(1) mengembalikan User(id=1, name=“Test”) |
| Fake | Logika melalui implementasi sederhana | InMemoryMapUserRepository dengan HashMap sebagai ganti database |
| Spy | Mocking parsial objek nyata | spy(repo).when(findById).thenReturn(user) |
Mockito adalah framework mocking paling populer di Java dan Kotlin. Memungkinkan pembuatan mock melalui mock(), konfigurasi nilai kembali melalui when().thenReturn(), dan pemeriksaan panggilan melalui verify(). Versi modern Mockito (5.x) mendukung mock statis (mockStatic) dan sintaksis yang disederhanakan melalui BDDMockito (given-willReturn). Aturan penting: jangan mock sesuatu yang bukan milik Anda — jangan buat mock untuk objek nilai dan pustaka standar.
// Contoh pengujian unit dengan Mockito di Kotlin
class OrderServiceTest {
@Mock
private lateinit var paymentGateway: PaymentGateway
@Mock
private lateinit var userRepository: UserRepository
private lateinit var orderService: OrderService
@BeforeEach
fun init() {
MockitoAnnotations.openMocks(this)
orderService = OrderService(paymentGateway, userRepository)
}
@Test
fun processOrder_whenPaymentFails_shouldThrowException() {
// given
val user = User(id = 1, balance = 100.0)
val order = Order(amount = 200.0)
Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
Mockito.`when`(userRepository.findById(1)).thenReturn(user)
// when & then
assert Throws<PaymentException> {
orderService.processOrder(user.id, order)
}
// verify
Mockito.verify(paymentGateway).charge(any())
}
}TDD (Test-Driven Development) — metodologi di mana pengujian ditulis sebelum implementasi kode. Siklus “Red-Green-Refactor”: tulis pengujian yang gagal (Red), tulis kode minimal untuk lulus pengujian (Green), tingkatkan kode tanpa mengubah perilaku (Refactor). TDD memastikan bahwa semua kode tercakup oleh pengujian (cakupan = 100% untuk fungsionalitas yang ditulis) dan bahwa kode dapat diuji — jika kode sulit diuji, berarti arsitektur perlu perbaikan.
Menurut penelitian IBM (2006-2026, studi longitudinal), tim yang menggunakan TDD membuat 40-80% lebih sedikit cacat dalam produksi dibandingkan dengan tim yang menulis pengujian setelah kode. TDD juga meningkatkan arsitektur: pengembang harus memikirkan desain API sebelum implementasi, yang menghasilkan kopling longgar (loose coupling) dan kohesi tinggi (high cohesion). Efek tambahan — dokumentasi dengan kode hidup: pengujian berfungsi sebagai spesifikasi perilaku modul yang selalu terkini.
TDD tidak selalu optimal. Komponen UI sulit diuji secara terisolasi — untuk mereka, pengujian snapshot atau pengujian regresi visual (Percy, Chromatic) lebih efektif. Prototipe dan penelitian (spike solutions) tidak memerlukan pengujian. Kode legacy tanpa pengujian sulit dicakup melalui TDD — di sini pertama-tama diperlukan characterization tests (pengujian yang merekam perilaku saat ini sebelum refactoring). Dalam kasus ini, TDD tidak sepenuhnya dihapuskan, tetapi disesuaikan — pengujian ditulis untuk fungsionalitas yang diubah, bukan untuk seluruh kode legacy.
Pengembangan mobile memiliki spesifikasinya: logika bisnis sering tercampur dengan kode UI (Activity, ViewController, ViewModel), yang mempersulit pengujian unit. Praktik terbaik — tampilan tipis, ViewModel tebal: pindahkan semua logika dari komponen UI ke kelas terpisah (UseCase, Repository, ViewModel) yang mudah diuji tanpa emulator. Untuk Android dan iOS, ada framework pengujian unit native yang berjalan di JVM/Native tanpa menjalankan perangkat.
Pengujian unit Android dijalankan di JVM lokal tanpa emulator, yang memastikan kecepatan eksekusi — pengujian tipikal membutuhkan waktu kurang dari 100ms. JUnit 5 adalah runner utama. Untuk pengujian ViewModel, gunakan kotlinx-coroutines-test untuk menguji coroutine dan Turbine untuk menguji StateFlow. Robolectric memungkinkan pengujian komponen dependen Android (Context, Resources) tanpa emulator dengan memuat shadow class. Untuk pengujian Compose, gunakan Compose UI Test — tetapi ini sudah merupakan pengujian UI, bukan unit.
Pengujian unit iOS ditulis dalam Swift dengan XCTest (terintegrasi di Xcode). Quick + Nimble — framework BDD untuk pengujian yang lebih mudah dibaca (describe/context/it). Untuk mocking, gunakan Cuckoo (generasi mock) atau SwiftyMocky. Swift mendukung protokol dan dependency injection, yang memudahkan penggantian ketergantungan. Poin penting: pengujian unit iOS dijalankan di simulator macOS, bukan di perangkat nyata. Pengujian yang memerlukan fungsi perangkat keras (kamera, Bluetooth) adalah pengujian integrasi.
Pengujian unit Flutter menggunakan paket flutter_test dan dijalankan di Dart VM tanpa emulator. Untuk mocking — paket mockito dengan generator kode (build_runner). Pengujian widget (dalam paket yang sama) menguji widget individu, tetapi memerlukan rendering dan berjalan lebih lambat — gunakan hanya untuk memeriksa logika UI. Logika Dart murni (model, repositori, blobs) diuji seperti pengujian Dart biasa tanpa mengimpor flutter_test.
// Contoh pengujian unit di Flutter dengan mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';
void main() {
late MockApiClient mockApi;
late UserRepository repository;
setUp(() {
mockApi = MockApiClient();
repository = UserRepository(mockApi);
});
test('fetchUser returns user when API succeeds', () async {
// Arrange
final expectedUser = User(id: 1, name: 'Test');
when(mockApi.getUser(1))
.thenAnswer((_) async => expectedUser);
// Act
final result = await repository.fetchUser(1);
// Assert
expect(result, expectedUser);
verify(mockApi.getUser(1)).called(1);
});
}Pengujian unit yang efektif memerlukan disiplin. Aturan utama: uji perilaku, bukan implementasi. Pengujian tidak boleh tahu bagaimana modul diimplementasikan secara internal (metode privat apa yang dipanggil, dalam urutan apa). Jika pengujian terikat pada implementasi, ia rusak pada setiap refactoring dan kehilangan nilai. Pengujian memeriksa kontrak: dengan input X harus ada output Y. Pengecualian — pengujian untuk algoritma dengan kinerja kritis, di mana urutan panggilan penting.
Cakupan 100% adalah tujuan yang tidak dapat dicapai dan tidak diperlukan. Menurut Google Testing Blog (2025), tingkat cakupan optimal untuk pengujian unit adalah 70-80% baris kode. Cakupan 100% sering dicapai dengan menguji getter, setter, dan konstruktor, yang tidak memberikan nilai. Fokus pada logika bisnis kritis: perhitungan kompleks, validasi, penanganan kesalahan, kasus tepi. Gunakan JaCoCo (Java), Coverage.py (Python), Istanbul (JS) untuk pengukuran dan atur ambang batas di CI — kegagalan build pada cakupan di bawah 60%.
Pengujian unit adalah tahap pertama dari setiap pipeline CI/CD. Mereka dijalankan pada setiap push ke repositori, sebelum build dan deploy. Waktu eksekusi rata-rata pengujian unit dalam proyek tidak boleh melebihi 5 menit — jika lebih lama, pengujian tidak lagi “epat” dan pengembang berhenti menjalankannya secara lokal. Bagilah pengujian menjadi cepat (unit) dan lambat (integrasi) dan jalankan di berbagai tahap pipeline. Gunakan eksekusi paralel dan fail-fast untuk mempercepat.
Pertanyaan yang Sering Diajukan
Pengujian unit memeriksa satu modul secara terisolasi, mengganti ketergantungan eksternal dengan mock. Pengujian integrasi memeriksa interaksi antara beberapa komponen nyata (database, API, sistem file). Pengujian unit dijalankan dalam milidetik, pengujian integrasi dalam detik. Dalam piramida pengujian, pengujian unit menempati 70%.
Pilihan tergantung pada platform: JUnit 5 untuk Java/Kotlin, XCTest untuk iOS/Swift, pytest untuk Python, Jest/Vitest untuk JavaScript/TypeScript, flutter_test untuk Flutter. Untuk mocking, gunakan Mockito (Java), Cuckoo (iOS), unittest.mock (Python), atau vitest.mock (JS). Semua framework modern mendukung pengujian berparameter, assert bawaan, dan eksekusi paralel.
Fast — pengujian dijalankan dalam milidetik. Isolated — tidak bergantung pada pengujian lain dan sistem eksternal. Repeatable — memberikan hasil yang sama di mesin mana pun. Self-validating — memeriksa hasil secara otomatis. Timely — ditulis sebelum atau sinkron dengan kode. Pelanggaran salah satu prinsip mengurangi efektivitas pengujian.
Ya, wajib. ViewModel berisi logika bisnis — penanganan peristiwa, transformasi data, manajemen status. Di Android, gunakan kotlinx-coroutines-test untuk coroutine dan Turbine untuk menguji StateFlow. Di iOS, uji Combine Publishers atau async/await di ViewModel. Pengujian ViewModel adalah pengujian unit murni yang berjalan di JVM/macOS tanpa emulator.
Permintaan jaringan dalam pengujian unit tidak dijalankan — mereka diganti dengan mock klien HTTP. Di Android, gunakan MockWebServer (OkHttp) — ini menjalankan server HTTP lokal, yang lebih disukai daripada mock karena mereproduksi interaksi jaringan nyata. MockWebServer memberikan isolasi tanpa kehilangan realisme. Untuk iOS — OHHTTPStubs atau URLProtocol untuk mencegat dan mengganti respons.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga