Unit Test vs Integration Test

TDD or not TDD
that is the question: Whether 'tis nobler in the mind to suffer,
Or to take arms against a sea of troubles

Dalam artikel sebelumnya, sudah dibahas mengenai TDD, whitebox dan blackbox testing. Kesimpulannya adalah kedua jenis testing tersebut penting untuk dilakukan. Bagi kalian yang sudah menjalankan whitebox testing, mungkin muncul pertanyaan yang lebih mendalam: apa bedanya unit test & integration test?

Unit Test

Unit Test adalah metode pengujian perangkat lunak yang berfokus pada pengujian unit terkecil dari kode, biasanya berupa fungsi atau metode, secara terpisah. Konsep ini telah ada sejak awal pengembangan perangkat lunak dan menjadi semakin populer dengan munculnya metodologi seperti Test Driven Development (TDD). Tujuan utama dari Unit Test adalah untuk memastikan bahwa setiap unit kode berfungsi sesuai dengan spesifikasi yang telah ditetapkan, sehingga dapat mendeteksi bug pada tahap awal pengembangan.  Unit Test yang efektif bersifat deterministik, artinya memberikan hasil yang sama setiap kali dijalankan, dan terisolasi, sehingga tidak bergantung pada bagian lain dari sistem atau lingkungan eksternal.

Untuk membuat Unit Test yang baik, dapat memperhatikan konsep F.I.R.S.T.

  • Fast
    Unit Test harus cepat dijalankan. Tes yang lambat dapat menghambat produktivitas dan membuat pengembang enggan menjalankannya secara sering. Pastikan tes hanya berfokus pada unit yang diuji dan hindari interaksi dengan komponen eksternal seperti database atau jaringan.
  • Isolated/Independent
    Pastikan setiap unit test menguji satu unit kode saja, seperti satu fungsi atau metode. Tes harus terisolasi, artinya tidak boleh bergantung pada tes lain atau bagian lain dari sistem. Ini memastikan bahwa jika satu tes gagal, itu menunjukkan masalah dalam unit kode yang diuji, bukan di tempat lain.
  • Repeatable
    Unit test harus memberikan hasil yang sama setiap kali dijalankan (deterministik), terlepas dari lingkungan atau urutan eksekusi. Ini berarti menghindari penggunaan data eksternal atau variabel yang dapat berubah, seperti waktu sistem atau data acak, kecuali jika diperlukan dan dikontrol.
  • Self-Validating
    Dalam pembuatan Unit Test tidak boleh ada pengecekan secara manual. Setiap test harus dapat memberikan hasil berupa sukses/gagal secara otomatis.
  • Thorough
    Unit Test harus mampu mencakup banyak skenario, jangan hanya mengincar 100% test coverage dengan skenario yang terlalu sedikit. Perhatikan corner case, keamanan sistem, lakukan tes dengan data yang besar/banyak/invalid agar kesalahan/bug dapat dideteksi sedari awal.

Integration Test

Integration Test adalah tahap pengujian perangkat lunak yang fokus pada pengujian interaksi antara unit atau komponen yang berbeda dalam sebuah sistem. Setelah unit-unit individu diuji secara terpisah melalui unit testing, integration testing memastikan bahwa unit-unit tersebut bekerja bersama dengan benar. Pengujian ini penting untuk mengidentifikasi masalah yang mungkin muncul ketika unit-unit tersebut digabungkan, seperti kesalahan dalam antarmuka, kesalahan komunikasi data, atau ketidaksesuaian antar modul.

Ada beberapa tipe Integration Test yang dapat dicoba sewaktu pengembangan perangkat lunak anda, seperti:

  • Big Bang Integration Testing: Semua unit atau komponen diintegrasikan sekaligus dan kemudian diuji secara keseluruhan. Meskipun sederhana, pendekatan ini bisa membuat pelacakan sumber kesalahan menjadi sulit jika tes gagal, karena banyaknya komponen yang diuji secara bersamaan.
  • Incremental Integration Testing: Komponen diintegrasikan dan diuji secara bertahap. Metode ini bisa dilakukan secara top-down, bottom-up, atau kombinasi keduanya (sandwich/hybrid). Pendekatan ini lebih terstruktur dan memungkinkan pengidentifikasian masalah lebih mudah dibandingkan dengan Big Bang.
  • Top-Down Integration Testing: Pengujian dimulai dari modul tingkat atas dan bergerak ke bawah. Modul tingkat bawah yang belum siap digantikan oleh stubs, yang mensimulasikan fungsi dari modul tersebut.
# Modul Controller - Tingkat atas
class Controller:
    def __init__(self, database):
        self.database = database

    def save_data(self, key, value):
        return self.database.save(key, value)

    def get_data(self, key):
        return self.database.load(key)

# Stub untuk modul Database - Tingkat bawah
class DatabaseStub:
    def save(self, key, value):
        print(f"Stub: Pretending to save {key} -> {value}")
        return True

    def load(self, key):
        print(f"Stub: Pretending to load value for {key}")
        return "stub_value"

# Uji modul Controller dengan menggunakan DatabaseStub
def test_controller_with_stub():
    print("Testing Controller Module with Database Stub")
    db_stub = DatabaseStub()
    controller = Controller(db_stub)
    
    assert controller.save_data('key1', 'value1') == True
    assert controller.get_data('key1') == "stub_value"
    print("All tests passed for Controller Module with Database Stub")

# Jalankan pengujian
test_controller_with_stub()
  • Bottom-Up Integration Testing: Pengujian dimulai dari modul tingkat bawah dan bergerak ke atas. Modul tingkat atas yang belum siap digantikan oleh drivers, yang mensimulasikan fungsi dari modul tersebut.
# Modul Database - Tingkat bawah
class Database:
    def __init__(self):
        self.data = {}

    def save(self, key, value):
        self.data[key] = value
        return True

    def load(self, key):
        return self.data.get(key, None)

# Modul Service - Tingkat atas (belum siap)
# class Service:
#     def __init__(self, database):
#         self.database = database

#     def save_data(self, key, value):
#         return self.database.save(key, value)

#     def get_data(self, key):
#         return self.database.load(key)

# Driver untuk menguji modul Database
def test_database():
    print("Testing Database Module")
    db = Database()
    assert db.save('key1', 'value1') == True
    assert db.load('key1') == 'value1'
    assert db.load('key2') == None
    print("All tests passed for Database Module")

# Jalankan driver
test_database()

Unit Test vs Integration Test?

Kedua test tersebut mempunyai perbedaan fungsi dan tujuan.

Gunakan Unit Test ketika
☑️ Kode yang diuji merupakan elemen terkecil pada sistem (fungsi/metode)
☑️ Pengujian dilakukan secara otomatis menggunakan CI/CD
☑️ Terdapat modul yang hendak dilakukan refactor - Unit Test berguna untuk memastikan bahwa fungsi modul tersebut sama sebelum & sesudah refactor.

Gunakan Integration Test ketika
☑️ Terdapat banyak hubungan antar sistem/komponen yang perlu diuji kestabilannya
☑️ Dibutuhkan pengujian interaksi UI dengan skenario penggunaan mirip dengan kondisi dunia nyata