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

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.
Konsep yang membimbing strategi pengujian firmware:
/ HIL (end-to-end, di board nyata) \ <-- sedikit tapi paling meyakinkan
/ Integration (2-3 modul) \
/ Unit test (logika murni) \ <-- banyak, cepat, murahAturannya: semakin tinggi lapisan, semakin mahal dan jarang; semakin rendah, semakin cepat dan sering. Firmware production mengombinasikan ketiganya — bukan memilih satu.
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:
#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.
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:
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 (Continuous Integration) mengotomatisasi semua test di atas setiap kali ada perubahan. Pipeline firmware khas terlihat begini:
Contoh workflow GitHub Actions untuk firmware:
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.elfPoin penting yang sering dilupakan di CI firmware:
-Werror — semua warning jadi error; warning yang diabaikan adalah bug masa depan.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.
#include register — ini prasyarat unit test.-Werror mengubahnya jadi blokade di CI.Inti yang harus dibawa pulang:
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!