Belajar Embedded Engineer - Embedded Testing & CI
Episode 16 of 28

Belajar Embedded Engineer - Embedded Testing & CI

Mengamankan kualitas firmware dengan pengujian berlapis: unit test pada logika murni dengan Unity/CMock, hardware-in-the-loop untuk menguji di board nyata, dan pipeline CI yang menjalankan semuanya otomatis, ditutup praktik membangun pipeline CI firmware dari nol

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Semua kode yang kalian tulis sejauh ini — bare-metal, RTOS, driver, logika komunikasi — sekarang perlu dipastikan benar, dan tetap benar setelah berubah. Di dunia desktop, testing sudah jadi rutinitas; di embedded, testing justru lebih penting karena bug tidak muncul di layar monitor, melainkan di lapangan — pada perangkat yang tidak bisa di-update semudah aplikasi web.

Masalah klasiknya: firmware susah dites. Kode menempel pada hardware, hardware tidak selalu tersedia, dan CI tradisional (container) tidak punya MCU. Episode ini membangun strategi berlapis — unit test di host, test di target, dan pipeline CI yang mengotomatisasi keduanya — sehingga setiap perubahan ditebus dengan bukti, bukan doa.

Piramida Testing Firmware

Konsep yang membimbing strategi pengujian firmware:

Piramida testing firmware
        /  HIL (end-to-end, di board nyata)  \  <-- sedikit tapi paling meyakinkan
      /      Integration (2-3 modul)           \
    /          Unit test (logika murni)           \  <-- banyak, cepat, murah
  1. Unit test — menguji satu fungsi/modul tanpa hardware. Cepat, ribuan dalam detik, jalan di PC biasa.
  2. Integration test — menguji interaksi beberapa modul (misal parser + state machine).
  3. HIL (Hardware-in-the-Loop) — menjalankan firmware asli di MCU nyata dengan stimulasi input/output terkontrol.

Aturannya: semakin tinggi lapisan, semakin mahal dan jarang; semakin rendah, semakin cepat dan sering. Firmware production mengombinasikan ketiganya — bukan memilih satu.

Unit Test di Host: Tanpa Hardware

Rahasia utama unit test firmware: pisahkan logika dari hardware. Kode yang murni (parser, state machine, checksum, buffer) harus ditulis tanpa #include register — sehingga bisa di-compile dan dijalankan di PC. Test framework yang populer: Unity (C) dan Ceedling (manajer build). Contoh:

c
#include "unity.h"
#include "crc.h"
 
void setUp(void) {}
void tearDown(void) {}
 
void test_crc16_known_vector(void) {
    TEST_ASSERT_EQUAL_UINT16(0x4B37, crc16(0x1234));
}
 
void test_rb_push_pop_roundtrip(void) {
    uint8_t out;
    TEST_ASSERT_EQUAL_INT(0, rb_push(0xAB));
    TEST_ASSERT_EQUAL_INT(0, rb_pop(&out));
    TEST_ASSERT_EQUAL_UINT8(0xAB, out);
}
 
int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_crc16_known_vector);
    RUN_TEST(test_rb_push_pop_roundtrip);
    return UNITY_END();
}

Karena unit test berjalan di host, setiap pull request bisa menjalankan ribuan test dalam hitungan detik — tanpa board. Inilah garis pertahanan pertama yang menghentikan bug sebelum mencapai hardware.

Hardware-in-the-Loop: Uji di Board Nyata

Unit test tidak bisa membuktikan hardware berfungsi. Di sinilah HIL berperan: firmware asli di-flash ke MCU, dan "dunia luar" disimulasikan — sinyal GPIO diubah, data UART disuntikkan, tegangan diukur — lalu hasilnya diverifikasi otomatis.

Contoh setup HIL sederhana dan terjangkau:

  • Board target (MCU) + host pengontrol (PC/raspberry pi).
  • PC mengirim frame UART yang "meniru sensor", firmware merespons, PC memvalidasi respons.
  • Dengan logic analyzer / power monitor, HIL juga bisa memverifikasi timing (episode 14) dan konsumsi daya (episode 13).
Loop HIL sederhana
PC -------------UART------------> MCU   (kirim frame sensor palsu)
PC <-----------UART------------- MCU   (terima respons, validasi)
PC ----GPIO stimulasi-----------> MCU   (tekan tombol, ubah pin)
PC <---logic analyzer (timing)--- MCU   (ukur jitter & delay)

HIL adalah jembatan antara "logika benar di host" dan "hardware benar di lapangan". Ia biasanya dijalankan di CI khusus dengan runner yang punya board — atau dijalankan berkala sebagai nightly test.

CI untuk Firmware: Pipeline yang Menjaga

CI (Continuous Integration) mengotomatisasi semua test di atas setiap kali ada perubahan. Pipeline firmware khas terlihat begini:

100%

Contoh workflow GitHub Actions untuk firmware:

.github/workflows/firmware-ci.yml
name: firmware-ci
on: [push, pull_request]
jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install host test deps
        run: sudo apt install gcc make
      - name: Build & run unit tests
        run: |
          cd tests && make && ./run_tests
  build-target:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install ARM toolchain
        run: sudo apt install gcc-arm-none-eabi
      - name: Cross-compile firmware
        run: make -C firmware -j$(nproc)
      - name: Check binary size
        run: arm-none-eabi-size firmware/build/firmware.elf

Poin penting yang sering dilupakan di CI firmware:

  1. Ukuran binary dipantau — regression ukuran (flash/RAM) bisa dicek otomatis; jika binary membengkak, CI memberi sinyal lebih awal.
  2. Build dengan -Werror — semua warning jadi error; warning yang diabaikan adalah bug masa depan.
  3. Versi firmware + hash dipublikasikan — setiap binary punya identitas untuk audit OTA (episode 17).
  4. HIL sebagai job terpisah — tidak memperlambat feedback cepat unit test.

Tip

Mulai CI firmware dari yang paling sederhana: satu workflow yang menjalankan unit test + cross-compile + size check. Ini bisa berjalan hari ini tanpa hardware apa pun. Tambahkan HIL setelah tim punya board runner. Ingat — CI yang tidak ada tidak melindungi apa pun; CI yang sederhana dan berjalan jauh lebih berharga daripada CI besar yang sering merah.

Praktik: Bangun CI Firmware Kalian

  1. Pisahkan modul logika (CRC, ring buffer, state machine) agar tidak #include register — ini prasyarat unit test.
  2. Tulis 10-20 unit test dengan Unity dan jalankan di host.
  3. Buat workflow GitHub Actions: unit test + cross-compile + size check.
  4. Buat satu "bug" sengaja (misal salah offset parser) dan amati CI menolaknya — bukti pipeline bekerja.
  5. (Lanjutan) Jika punya board: tambahkan HIL job yang flash dan memvalidasi respons UART.

Common Pitfalls

  1. Kode menempel hardware — unit test tidak mungkin; pisahkan logika sejak awal.
  2. Test hanya di host, tidak pernah di target — endianness/timing berbeda; HIL mengisi celahnya.
  3. CI tidak memantau ukuran binary — flash/RAM membengkak tanpa terasa.
  4. Warning diabaikan-Werror mengubahnya jadi blokade di CI.
  5. Flaky test dibiarkan — test yang kadang merah membuat tim tidak percaya pipeline; buang sumber flakiness.

Penutup

Inti yang harus dibawa pulang:

  • Strategi berlapis: unit test (host) + integration + HIL (board).
  • Pisahkan logika dari hardware agar bisa dites tanpa board.
  • HIL menguji firmware asli di MCU dengan simulasi dunia luar.
  • CI firmware: lint → unit test → cross-compile → size check → HIL opsional.
  • Awali sederhana; CI yang berjalan selalu lebih baik daripada CI yang sempurna tapi mati.

Di episode 17 selanjutnya kita menutup siklus hidup firmware: bootloaders & OTA — merancang bootloader, secure boot, dan pipeline update over-the-air yang membuat perangkat bisa diperbaiki dan ditingkatkan dari jarak jauh. Inilah yang membedakan produk yang bisa bertumbuh dari produk yang mati di tangan pengguna. Sampai jumpa di episode 17!

Belajar Embedded Engineer - Embedded Testing & CI | Belajar Embedded Engineer