Bagaimana saya melakukan TDD pada perangkat yang disematkan?

17

Saya bukan orang baru dalam pemrograman dan saya bahkan telah bekerja dengan C dan ASM tingkat rendah pada AVR, tetapi saya benar-benar tidak dapat memahami proyek C yang tertanam dalam skala yang lebih besar.

Menjadi merosot oleh filosofi Ruby tentang TDD / BDD, saya tidak dapat memahami bagaimana orang menulis dan menguji kode seperti ini. Saya tidak mengatakan itu kode yang buruk, saya hanya tidak mengerti bagaimana ini bisa bekerja.

Saya ingin mendapatkan lebih banyak ke beberapa pemrograman tingkat rendah, tetapi saya benar-benar tidak tahu bagaimana mendekati ini, karena sepertinya pola pikir yang sama sekali berbeda yang saya gunakan. Saya tidak mengalami kesulitan memahami pointer aritmatika, atau bagaimana mengalokasikan memori bekerja, tetapi ketika saya melihat betapa rumitnya kode C / C ++ dibandingkan dengan Ruby, sepertinya sangat sulit.

Karena saya sudah memesan sendiri papan Arduino, saya ingin mendapatkan lebih banyak ke level C rendah dan benar-benar mengerti bagaimana melakukan sesuatu dengan benar, tetapi sepertinya tidak ada aturan bahasa tingkat tinggi yang berlaku.

Mungkinkah melakukan TDD pada perangkat yang disematkan atau ketika mengembangkan driver atau hal-hal seperti bootloader khusus, dll.?

Jakub Arnold
sumber
3
Halo Darth, kami benar-benar tidak dapat membantu Anda mengatasi rasa takut Anda akan C, tetapi pertanyaan tentang TDD pada perangkat yang disematkan adalah pada topik di sini: Saya telah merevisi pertanyaan Anda menjadi fitur.

Jawaban:

18

Pertama, Anda harus tahu bahwa mencoba memahami kode yang tidak Anda tulis adalah 5x lebih sulit daripada menulisnya sendiri. Anda dapat belajar C dengan membaca kode produksi, tetapi itu akan memakan waktu lebih lama daripada belajar sambil bekerja.

Menjadi merosot oleh filosofi Ruby tentang TDD / BDD, saya tidak dapat memahami bagaimana orang menulis dan menguji kode seperti ini. Saya tidak mengatakan itu kode yang buruk, saya hanya tidak mengerti bagaimana ini bisa bekerja.

Itu keterampilan; Anda menjadi lebih baik. Kebanyakan programmer C tidak mengerti bagaimana orang menggunakan Ruby, tetapi itu tidak berarti mereka tidak bisa.

Mungkinkah melakukan TDD pada perangkat yang disematkan atau ketika mengembangkan driver atau hal-hal seperti bootloader khusus, dll.?

Nah, ada buku tentang masalah ini:

masukkan deskripsi gambar di sini Jika lebah bisa melakukannya, Anda juga bisa!

Ingatlah bahwa menerapkan praktik dari bahasa lain biasanya tidak berhasil. TDD cukup universal.

Pubby
sumber
2
Setiap TDD yang saya lihat untuk embedded system saya hanya menemukan kesalahan pada sistem yang memiliki mudah untuk menyelesaikan kesalahan saya akan dengan mudah menemukan sendiri. Mereka tidak akan pernah menemukan apa yang saya butuhkan bantuan, interaksi tergantung waktu dengan chip lain dan interaksi interupsi.
Kortuk
3
Ini tergantung pada sistem apa yang sedang Anda kerjakan. Saya telah menemukan bahwa menggunakan TDD untuk menguji perangkat lunak, ditambah dengan abstraksi perangkat keras yang baik, sebenarnya memungkinkan saya untuk mengejek interaksi yang tergantung waktu itu dengan lebih mudah. Manfaat lain yang sering dilihat orang adalah bahwa pengujian, yang otomatis, dapat dijalankan kapan saja dan tidak mengharuskan seseorang untuk duduk di perangkat dengan penganalisa logika untuk memastikan perangkat lunak bekerja. TDD telah menyelamatkan saya dari debugging di proyek saya saat ini saja. Seringkali, kesalahan yang menurut kami mudah dikenali yang menyebabkan kesalahan yang tidak kami harapkan.
Nick Pascucci
Plus itu memungkinkan pengembangan dan pengujian di luar target.
cp.engr
Bisakah saya mengikuti buku ini untuk memahami TDD untuk C yang tidak tertanam? Untuk pemrograman ruang C pengguna apa pun?
overexchange
15

Berbagai macam jawaban di sini ... sebagian besar menangani masalah ini dengan berbagai cara.

Saya telah menulis perangkat lunak dan firmware tingkat rendah tertanam selama lebih dari 25 tahun dalam berbagai bahasa - kebanyakan C (tetapi dengan pengalihan ke Ada, Occam2, PL / M, dan berbagai perakit sepanjang jalan).

Setelah lama berpikir dan mencoba-coba, saya telah menetapkan metode yang mendapatkan hasil cukup cepat dan cukup mudah untuk membuat pembungkus dan pelindung uji (di mana mereka MENAMBAH NILAI!)

Metodenya seperti ini:

  1. Tulis unit kode abstraksi driver atau perangkat keras untuk setiap perangkat utama yang ingin Anda gunakan. Juga tulis satu untuk menginisialisasi prosesor dan mengatur semuanya (ini membuat lingkungan ramah). Biasanya pada prosesor tertanam kecil - AVR Anda menjadi contoh - mungkin ada 10 - 20 unit seperti itu, semuanya kecil. Ini mungkin unit untuk inisialisasi, konversi A / D ke buffer memori yang tidak dihitung, keluaran bitwise, input tombol tekan (tidak ada sampel yang hanya diambil sampelnya), driver modulasi lebar pulsa, UART / driver serial sederhana yang menggunakan interupsi dan buffer I / O kecil. Mungkin ada beberapa lagi - misalnya driver I2C atau SPI untuk EEPROM, EPROM, atau perangkat I2C / SPI lainnya.

  2. Untuk masing-masing unit abstraksi perangkat keras (HAL) / driver, saya kemudian menulis program uji. Ini bergantung pada port serial (UART) dan prosesor init - sehingga program pengujian pertama hanya menggunakan 2 unit tersebut dan hanya melakukan beberapa input dan output dasar. Ini memungkinkan saya menguji bahwa saya dapat memulai prosesor dan bahwa saya memiliki dukungan debug dasar seri I / O yang berfungsi. Setelah itu berhasil (dan hanya kemudian) saya kemudian mengembangkan program uji HAL lainnya, membangun ini di atas unit UART dan INIT yang dikenal baik. Jadi saya mungkin memiliki program pengujian untuk membaca input bitwise dan menampilkannya dalam bentuk yang bagus (hex, desimal, apa pun) pada terminal debug serial saya. Saya kemudian dapat pindah ke hal-hal yang lebih besar dan lebih kompleks seperti program uji EEPROM atau EPROM - Saya membuat sebagian besar menu ini digerakkan sehingga saya dapat memilih tes untuk menjalankan, menjalankannya, dan melihat hasilnya. Saya tidak bisa SCRIPT tapi biasanya saya tidak

  3. Setelah saya menjalankan semua HAL saya, saya kemudian menemukan cara untuk mendapatkan centang timer biasa. Ini biasanya pada tingkat di suatu tempat antara 4 dan 20 ms. Ini harus teratur, dihasilkan dalam interupsi. Rollover / overflow dari penghitung biasanya bagaimana hal ini dapat dilakukan. Penangan interrupt kemudian MENINGKATKAN ukuran byte "semaphore". Pada titik ini, Anda juga dapat bermain-main dengan manajemen daya jika perlu. Gagasan semaphore adalah bahwa jika nilainya> 0 Anda perlu menjalankan "loop utama".

  4. EXECUTIVE menjalankan loop utama. Cukup banyak hanya menunggu di semaphore itu untuk menjadi non-0 (saya abaikan detail ini). Pada titik ini, Anda dapat bermain-main dengan penghitung untuk menghitung kutu ini (karena Anda tahu tingkat kutu) dan sehingga Anda dapat mengatur bendera yang menunjukkan jika centang eksekutif saat ini adalah untuk interval 1 detik, 1 menit, dan interval umum lainnya yang Anda mungkin ingin digunakan. Setelah eksekutif mengetahui bahwa semaphore adalah> 0, ia menjalankan satu melewati setiap "aplikasi" proses "pembaruan" fungsi.

  5. Proses aplikasi secara efektif duduk berdampingan satu sama lain dan dijalankan secara teratur dengan tanda centang "pembaruan". Ini hanya fungsi yang disebut oleh eksekutif. Ini benar-benar orang miskin multi-tasking dengan RTOS yang ditanamkan di rumah yang sangat sederhana yang bergantung pada semua aplikasi yang masuk, melakukan sedikit pekerjaan dan keluar. Aplikasi perlu mempertahankan variabel keadaan mereka sendiri dan tidak dapat melakukan perhitungan berjalan lama karena tidak ada sistem operasi pre-emptive untuk memaksakan keadilan. TENTANG waktu aplikasi berjalan (secara kumulatif) harus lebih kecil dari periode centang utama.

Pendekatan di atas mudah diperpanjang sehingga Anda dapat memiliki hal-hal seperti tumpukan komunikasi yang ditambahkan yang berjalan secara tidak sinkron dan pesan koms kemudian dapat dikirim ke aplikasi (Anda menambahkan fungsi baru untuk masing-masing yang merupakan "rx_message_handler" dan Anda menulis dispatcher pesan yang angka-angka aplikasi yang akan dikirim).

Pendekatan ini bekerja untuk hampir semua sistem komunikasi yang ingin Anda beri nama - ia dapat (dan telah dilakukan) bekerja untuk banyak sistem berpemilik, sistem koms standar terbuka, bahkan bekerja untuk tumpukan TCP / IP.

Ini juga memiliki keuntungan dibangun di bagian modular dengan antarmuka yang terdefinisi dengan baik. Anda dapat menarik potongan masuk dan keluar kapan saja, gantikan potongan yang berbeda. Di setiap titik di sepanjang jalan Anda dapat menambahkan test harness atau handler yang dibangun di atas bagian lapisan bawah yang baik (hal-hal di bawah ini). Saya telah menemukan bahwa sekitar 30% hingga 50% dari suatu desain dapat mengambil manfaat dengan menambahkan tes unit khusus yang biasanya cukup mudah ditambahkan.

Saya telah mengambil semua ini selangkah lebih maju (ide yang saya dapatkan dari orang lain yang telah melakukan ini) dan mengganti layer HAL dengan yang setara untuk PC. Jadi misalnya Anda dapat menggunakan C / C ++ dan winforms atau serupa pada PC dan dengan menulis kode DENGAN SEKSAMA Anda dapat meniru setiap antarmuka (misalnya EEPROM = file disk dibaca ke dalam memori PC) dan kemudian jalankan seluruh aplikasi yang tertanam pada PC. Kemampuan untuk menggunakan lingkungan debug yang ramah dapat menghemat banyak waktu dan upaya. Hanya proyek-proyek besar yang biasanya dapat membenarkan upaya ini.

Deskripsi di atas adalah sesuatu yang tidak unik untuk bagaimana saya melakukan sesuatu pada platform yang tertanam - saya telah menemukan banyak organisasi komersial yang melakukan hal serupa. Cara pelaksanaannya biasanya sangat berbeda dalam implementasi tetapi prinsip-prinsipnya seringkali sama.

Saya berharap hal di atas memberi sedikit rasa ... pendekatan ini bekerja untuk sistem tertanam kecil yang berjalan dalam beberapa kB dengan manajemen baterai yang agresif hingga monster 100K atau lebih jalur sumber yang berjalan dengan tenaga permanen. Jika Anda menjalankan "embedded" pada OS besar seperti Windows CE atau lebih, maka semua hal di atas benar-benar tidak penting. Tapi itu bukan pemrograman tertanam NYATA, bagaimanapun.

dengan cepat_now
sumber
2
Sebagian besar perangkat keras yang tidak dapat Anda uji melalui UART, karena cukup sering Anda terutama tertarik dengan karakteristik pengaturan waktu. Jika Anda ingin memeriksa laju sampel ADC, siklus tugas PWM, perilaku beberapa perangkat serial lainnya (SPI, CAN dll), atau hanya waktu eksekusi beberapa bagian dari program Anda, maka Anda tidak dapat melakukan ini melalui UART. Pengujian firmware tertanam serius apa pun mencakup osiloskop - Anda tidak dapat memprogram sistem tertanam tanpa itu.
1
Oh ya, tentu saja. Saya hanya lupa menyebutkan yang itu. Tetapi begitu UART Anda aktif dan berjalan, sangat mudah untuk menyiapkan uji atau uji kasus (yang merupakan pertanyaannya), merangsang berbagai hal, memungkinkan input pengguna, mendapatkan hasil, dan menampilkannya dengan cara yang ramah. Ini + CRO Anda membuat hidup sangat mudah.
cepat,
2

Kode yang memiliki sejarah panjang pengembangan bertahap dan optimisasi untuk berbagai platform, seperti contoh yang Anda pilih, biasanya lebih sulit dibaca.

Hal tentang C adalah bahwa ia sebenarnya mampu menjangkau platform pada rentang kekayaan API dan kinerja perangkat keras yang sangat besar (dan kekurangannya). MacVim berjalan secara responsif pada mesin dengan lebih dari 1000X lebih sedikit memori dan kinerja prosesor daripada smartphone biasa saat ini. Bisakah kode Ruby Anda? Itulah salah satu alasan yang mungkin terlihat lebih sederhana daripada contoh C matang yang Anda pilih.

hotpaw2
sumber
2

Saya berada di posisi sebaliknya karena telah menghabiskan sebagian besar 9 tahun terakhir sebagai programmer C, dan baru-baru ini mengerjakan beberapa Ruby di Rails front-end.

Barang-barang yang saya kerjakan di C sebagian besar adalah sistem kustom berukuran sedang untuk mengendalikan gudang otomatis (biaya khas beberapa ratus ribu pound, hingga beberapa juta). Contoh fungsionalitas adalah basis data dalam memori khusus, yang terhubung ke permesinan dengan beberapa persyaratan waktu respons pendek dan manajemen aliran kerja gudang yang lebih tinggi.

Saya bisa katakan pertama-tama, kami tidak melakukan TDD. Saya sudah mencoba beberapa kali memperkenalkan unit test, tetapi di C lebih banyak masalah daripada nilainya - setidaknya ketika mengembangkan perangkat lunak khusus. Tapi saya akan mengatakan TDD jauh lebih dibutuhkan di C daripada Ruby. Terutama, itu hanya karena C dikompilasi, dan jika dikompilasi tanpa peringatan, Anda telah melakukan sejumlah pengujian yang serupa dengan tes perancah rspec yang dibuat secara otomatis di Rails. Ruby tanpa tes unit tidak layak.

Tetapi apa yang akan saya katakan adalah bahwa C tidak harus sekeras beberapa orang membuatnya. Banyak dari pustaka standar C merupakan kekacauan dari nama fungsi yang tidak dapat dipahami dan banyak program C mengikuti konvensi ini. Saya senang mengatakan bahwa kami tidak, dan sebenarnya memiliki banyak pembungkus untuk fungsionalitas perpustakaan standar (ST_Copy bukan strncpy, ST_PatternMatch alih-alih regcomp / regexec, CHARSET_Convert alih-alih iconv_open / iconv / iconv_close dan sebagainya). Kode C in-house kami lebih baik bagi saya daripada kebanyakan hal lain yang pernah saya baca.

Tetapi ketika Anda mengatakan aturan dari bahasa tingkat tinggi lainnya tampaknya tidak berlaku, saya tidak akan setuju. Banyak kode C baik 'terasa' berorientasi objek. Anda sering melihat pola inisialisasi pegangan ke sumber daya, memanggil beberapa fungsi melewati pegangan sebagai argumen, dan akhirnya melepaskan sumber daya. Memang, prinsip-prinsip desain pemrograman berorientasi objek sebagian besar berasal dari hal-hal baik yang dilakukan orang dalam bahasa prosedural.

Saat-saat ketika C menjadi sangat rumit sering kali ketika melakukan hal-hal seperti driver perangkat dan kernel OS yang pada dasarnya tingkat sangat rendah. Saat Anda menulis sistem level yang lebih tinggi, Anda juga dapat menggunakan fitur level C yang lebih tinggi dan menghindari kompleksitas level rendah.

Satu hal yang sangat menarik yang mungkin ingin Anda lihat adalah kode sumber C untuk Ruby. Dalam dokumen Ruby API (http://www.ruby-doc.org/core-1.9.3/), Anda dapat mengklik dan melihat kode sumber untuk berbagai metode. Yang menarik adalah kode ini terlihat cukup bagus dan elegan - tidak terlihat serumit yang Anda bayangkan.

asc99c
sumber
" ... kamu juga bisa menggunakan fitur level yang lebih tinggi dari C ... ", seperti yang ada? ;-)
alk
Maksud saya level lebih tinggi daripada manipulasi bit dan pointer ke pointer yang cenderung Anda lihat dalam kode tipe driver perangkat! Dan jika Anda tidak khawatir tentang overhead beberapa panggilan fungsi, Anda dapat membuat kode C yang benar-benar terlihat cukup tinggi.
asc99c
" ... kamu dapat membuat kode C yang benar-benar terlihat cukup tinggi. ", absolutly, aku sepenuhnya setuju untuk itu. Tetapi meskipun " ... fitur tingkat yang lebih tinggi ... " bukan dari C, tetapi di kepala Anda, bukan?
alk
2

Apa yang saya lakukan adalah memisahkan kode tergantung perangkat dari kode independen perangkat, lalu menguji kode independen perangkat. Dengan modularitas dan disiplin yang baik, Anda akan berakhir dengan basis kode yang sebagian besar telah teruji.

Paul Nathan
sumber
2

Tidak ada alasan mengapa Anda tidak bisa. Masalahnya adalah bahwa mungkin tidak ada kerangka kerja unit pengujian yang "bagus" seperti yang Anda miliki dalam jenis pengembangan lainnya. Tidak apa-apa. Ini hanya berarti bahwa Anda harus mengambil pendekatan "roll-your-own" untuk pengujian.

Misalnya, Anda mungkin harus memprogram instrumentasi untuk menghasilkan "input palsu" untuk konverter A / D Anda atau mungkin Anda harus menghasilkan aliran "data palsu" untuk ditanggapi oleh perangkat yang disematkan.

Jika Anda menemukan penolakan untuk menggunakan kata "TDD", sebut saja "DVT" (uji verifikasi desain) yang akan membuat EE lebih nyaman dengan gagasan itu.

Angelo
sumber
0

Mungkinkah melakukan TDD pada perangkat yang disematkan atau ketika mengembangkan driver atau hal-hal seperti bootloader khusus, dll.?

Beberapa waktu lalu saya perlu menulis bootloader tingkat pertama untuk CPU ARM. Sebenarnya ada satu dari orang-orang yang menjual CPU ini. Dan kami menggunakan skema tempat bootloader mereka mem-boot bootloader kami. Tapi ini lambat, karena kami perlu mem-flash dua file ke NOR flash, bukan satu, kami perlu membuat ukuran bootloader kami menjadi bootloader pertama, dan membangunnya kembali setiap kali kami mengganti bootloader kami dan seterusnya.

Jadi saya memutuskan untuk mengintegrasikan fungsi-fungsi bootloader mereka ke kita. Karena itu kode komersial, saya harus memastikan bahwa semua hal berjalan sesuai harapan. Jadi saya memodifikasi QEMU untuk meniru blok IP CPU itu (tidak semua, hanya yang menyentuh bootloader), dan menambahkan kode ke QEMU untuk "printf" semua membaca / menulis ke register yang mengontrol hal-hal seperti PLL, UART, pengontrol SRAM dan begitu seterusnya. Kemudian saya memutakhirkan bootloader kami untuk mendukung CPU ini, dan setelah itu membandingkan output yang memberikan bootloader kami dan emulatornya, ini membantu saya menangkap beberapa bug. Itu ditulis sebagian di assembler ARM, sebagian di C. Juga setelah itu dimodifikasi QEMU membantu saya menangkap satu bug, bahwa saya tidak bisa menangkap menggunakan JTAG dan CPU ARM nyata.

Begitu pun dengan C dan assembler Anda dapat menggunakan tes.

Evgeniy
sumber
-2

Ya, dimungkinkan untuk melakukan TDD pada perangkat lunak tertanam. Orang yang mengatakan itu tidak mungkin, tidak relevan, atau tidak berlaku tidak benar. Ada nilai serius yang dapat diperoleh dari TDD yang tertanam seperti pada perangkat lunak apa pun.

Mereka cara terbaik untuk melakukannya bukan dengan menjalankan tes Anda pada target tetapi untuk abstrak dependensi perangkat keras Anda dan kompilasi dan berjalan pada PC host Anda.

Saat Anda melakukan TDD, Anda akan membuat dan menjalankan banyak tes. Anda memerlukan perangkat lunak untuk membantu Anda melakukan ini. Anda menginginkan kerangka uji yang membuatnya cepat dan mudah untuk melakukan ini, dengan penemuan tes otomatis dan pembuatan tiruan.

Pilihan terbaik untuk C saat ini adalah Ceedling. Berikut adalah posting tentang saya menulis tentang itu:

http://www.electronvector.com/blog/try-embedded-test-driven-development-right-now-with-ceedling

Dan itu dibangun di Ruby! Anda tidak perlu tahu Ruby apa pun untuk menggunakannya.

cherno
sumber
jawaban diharapkan berdiri sendiri. Memaksa pembaca untuk pergi ke sumber daya eksternal untuk menemukan substansi disukai di Stack Exchange ("baca artikel atau lihat Ceedling"). Pertimbangkan pengeditan agar sesuai dengan norma kualitas situs
agas
Apakah Ceedling memiliki mekanisme untuk mendukung acara yang tidak sinkron? Salah satu aspek yang lebih menantang dari aplikasi embedded real-time adalah bahwa mereka berurusan dengan menerima input dari sistem yang sangat kompleks yang sulit untuk dimodelkan ...
Jay Elston
@ Jay Tidak memiliki sesuatu yang khusus untuk mendukungnya. Namun saya sudah berhasil menguji hal semacam itu dengan mengejek, dan dengan mengatur arsitektur untuk mendukungnya. Sebagai contoh, saya baru-baru ini bekerja pada sebuah proyek di mana peristiwa yang didorong oleh interupsi dimasukkan ke dalam antrian dan kemudian dikonsumsi di mesin status "pengendali event". Ini pada dasarnya hanya fungsi yang dipanggil setiap kali suatu peristiwa terjadi. Ketika menguji fungsi itu, saya bisa mengejek panggilan fungsi yang menarik peristiwa keluar dari antrian, dan bisa mensimulasikan setiap peristiwa yang terjadi dalam sistem. Tes mengemudi juga membantu di sini.
cherno