Baru-baru ini, zerocost menulis artikel yang menarik, "Tes dalam C ++ tanpa makro dan memori dinamis" , yang membahas kerangka kerja minimalis untuk menguji kode C ++. Penulis (hampir) berhasil menghindari penggunaan makro untuk mendaftar tes, tetapi alih-alih, template "ajaib" muncul dalam kode, yang menurut saya pribadi, maaf, jelek jeleknya. Setelah membaca artikel itu, saya merasa tidak puas, karena saya tahu apa yang bisa dilakukan dengan lebih baik. Saya tidak dapat segera mengingat di mana, tetapi saya pasti melihat kode tes, yang tidak mengandung satu karakter tambahan untuk mendaftarkan mereka:
void test_object_addition() { ensure_equals("2 + 2 = ?", 2 + 2, 4); }
Akhirnya, saya ingat bahwa kerangka ini disebut Cutter dan menggunakan cara jenius untuk mengidentifikasi fungsi pengujian dengan caranya sendiri.
(KDPV diambil dari situs web Cutter di bawah CC BY-SA.)
Apa masalahnya?
Kode uji dirakit di perpustakaan bersama yang terpisah. Fungsi tes diekstraksi dari simbol perpustakaan yang diekspor dan diidentifikasi dengan nama. Pengujian dilakukan oleh utilitas eksternal khusus. Sapienti duduk.
$ cat test_addition.c #include <cutter.h> void test_addition() { cut_assert_equal_int(2 + 2, 5); }
$ cc -shared -o test_addition.so \ -I/usr/include/cutter -lcutter \ test_addition.c
$ cutter . F ========================================================================= Failure: test_addition <2 + 2 == 5> expected: <4> actual: <5> test_addition.c:5: void test_addition(): cut_assert_equal_int(2 + 2, 5, ) ========================================================================= Finished in 0.000943 seconds (total: 0.000615 seconds) 1 test(s), 0 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s) 0% passed
Ini adalah contoh dari dokumentasi Cutter . Anda dapat dengan aman menggulir semua yang berhubungan dengan Autotools, dan hanya melihat kodenya. Kerangka kerja ini agak aneh, ya, seperti semuanya Jepang.
Saya tidak akan membahas terlalu detail tentang fitur implementasi. Saya juga tidak memiliki kode lengkap (dan bahkan setidaknya konsep), karena secara pribadi saya tidak benar-benar membutuhkannya (di Rust semuanya sudah di luar kotak). Namun, bagi orang yang tertarik, ini bisa menjadi latihan yang bagus.
Detail dan opsi implementasi
Pertimbangkan beberapa tugas yang perlu Anda selesaikan saat menulis kerangka kerja untuk pengujian menggunakan pendekatan Cutter.
Mendapatkan Fungsi yang Diekspor
Pertama, Anda harus pergi ke fungsi tes entah bagaimana. Standar C ++, tentu saja, tidak menggambarkan pustaka bersama sama sekali. Windows baru-baru ini mengakuisisi subsistem Linux, yang memungkinkan ketiga sistem operasi utama direduksi menjadi POSIX. Seperti yang Anda ketahui, sistem POSIX menyediakan fungsi dlopen()
, dlsym()
, dlclose()
, dengan bantuan yang Anda bisa mendapatkan alamat fungsi, mengetahui nama simbolnya, dan ... itu saja. Daftar fungsi yang terkandung dalam pustaka yang dimuat tidak diungkapkan oleh POSIX.
Sayangnya (walaupun agak untungnya), tidak ada standar, cara portabel untuk menemukan semua fungsi yang diekspor dari perpustakaan. Mungkin, fakta bahwa konsep perpustakaan tidak ada di semua platform (baca: tertanam) entah bagaimana terlibat di sini. Tapi bukan itu intinya. Yang utama adalah Anda harus menggunakan fitur khusus platform.
Sebagai perkiraan awal, Anda cukup memanggil utilitas nm :
$ cat test.cpp void test_object_addition() { }
$ clang -shared test.cpp
$ nm -gj ./a.out __Z20test_object_additionv dyld_stub_binder
parse outputnya dan gunakan dlsym()
.
Untuk introspeksi yang lebih dalam, pustaka seperti libelf , libMachO , pe-parse berguna, memungkinkan Anda mem-parsing file yang dapat dieksekusi secara terprogram dan pustaka platform yang menarik bagi Anda. Bahkan, nm dan perusahaan hanya menggunakannya.
Pemfilteran fungsi tes
Seperti yang mungkin Anda perhatikan, perpustakaan berisi beberapa karakter aneh:
__Z20test_object_additionv dyld_stub_binder
Ini adalah __Z20test_object_additionv
, ketika kita memanggil fungsi hanya test_object_addition
? Dan apa ini dyld_stub_binder
kiri?
Karakter " __Z20...
" __Z20...
adalah nama yang disebut dekorasi (nama mangling). Fitur kompilasi C ++, tidak ada yang bisa dilakukan, hidup dengannya. Inilah yang disebut fungsi dari sudut pandang sistem (dan dlsym()
). Untuk menunjukkannya kepada seseorang dalam bentuk normal, Anda dapat menggunakan perpustakaan seperti libdemangle . Tentu saja, perpustakaan yang Anda butuhkan tergantung pada kompiler yang Anda gunakan, tetapi format dekorasi biasanya sama dalam kerangka platform.
Adapun fungsi-fungsi aneh seperti dyld_stub_binder
, ini juga fitur platform yang harus diperhitungkan. Anda tidak perlu memanggil fungsi apa pun saat memulai tes, karena tidak ada ikan di sana.
Kelanjutan logis dari ide ini adalah memfilter fungsi berdasarkan nama. Misalnya, Anda hanya dapat menjalankan fungsi dengan test
di namanya. Atau hanya fungsi dari namespace tests
. Dan juga menggunakan ruang nama bersarang untuk tes grup. Tidak ada batasan imajinasi Anda.
Lulus konteks tes yang dapat dieksekusi
File objek dengan tes dikumpulkan di perpustakaan bersama, pelaksanaan kode yang sepenuhnya dikontrol oleh driver utilitas eksternal - cutter
untuk Cutter. Dengan demikian, fungsi tes internal dapat menggunakan ini.
Misalnya, konteks tes yang dapat dieksekusi ( IRuntime
dalam artikel asli) dapat dengan aman melewati variabel global (lokal-thread). Pengemudi bertanggung jawab untuk mengelola dan melewati konteks.
Dalam kasus ini, fungsi pengujian tidak memerlukan argumen, tetapi mempertahankan semua fitur canggih, seperti penamaan sewenang-wenang dari kasus yang diuji:
void test_vector_add_element() { testing::description("vector size grows after push_back()"); }
Fungsi description()
mengakses IRuntime
bersyarat melalui variabel global dan dengan demikian dapat memberikan komentar ke kerangka kerja untuk seseorang. Keamanan menggunakan konteks global dijamin oleh kerangka kerja dan bukan tanggung jawab penulis ujian.
Dengan pendekatan ini, akan ada lebih sedikit noise dalam kode dengan transfer konteks ke pernyataan perbandingan dan fungsi tes internal yang mungkin perlu dipanggil dari yang utama.
Konstruktor dan destruktor
Karena pelaksanaan tes sepenuhnya dikendalikan oleh pengemudi, ia dapat mengeksekusi kode tambahan di sekitar tes.
Pustaka Cutter menggunakan fungsi berikut untuk ini:
cut_setup()
- sebelum setiap tes individucut_teardown()
- setelah setiap tes individucut_startup()
- sebelum menjalankan semua tescut_shutdown()
- setelah menyelesaikan semua tes
Fungsi-fungsi ini dipanggil hanya jika didefinisikan dalam file uji. Anda dapat memasukkannya ke dalam persiapan dan pembersihan lingkungan pengujian (fixture): membuat file sementara yang diperlukan, pengaturan kompleks dari objek yang diuji, dan antipatterns lainnya dari pengujian.
Untuk C ++, dimungkinkan untuk membuat antarmuka yang lebih idiomatis:
- lebih berorientasi objek dan tipe aman
- dengan dukungan konsep RAII yang lebih baik
- menggunakan lambdas untuk eksekusi yang ditangguhkan
- melibatkan konteks pelaksanaan pengujian
Tetapi untuk sekarang saya memikirkan hal ini lagi secara rinci sekarang.
Eksekusi uji mandiri
Cutter menggunakan pendekatan perpustakaan bersama untuk kenyamanan. Berbagai tes dikompilasi ke dalam kumpulan perpustakaan yang ditemukan dan dijalankan oleh utilitas tes terpisah. Secara alami, jika diinginkan, seluruh kode driver tes dapat disematkan langsung ke file yang dapat dieksekusi, mendapatkan file terpisah yang biasa. Namun, ini akan membutuhkan kolaborasi dengan sistem build untuk mengatur tata letak file yang dapat dieksekusi ini dengan cara yang benar: tanpa memotong fungsi "tidak terpakai", dengan dependensi yang benar, dll.
Lainnya
Pemotong dan kerangka kerja lainnya juga memiliki banyak hal berguna lainnya yang dapat membuat hidup lebih mudah saat menulis tes:
- pernyataan pengujian yang fleksibel dan dapat diperluas
- membangun dan mendapatkan data uji dari file
- tumpukan studi jejak, pengecualian dan penanganan drop
- βlevel rincianβ yang dapat disesuaikan dari pengujian
- menjalankan tes dalam berbagai proses
Layak untuk melihat kembali kerangka yang ada saat menulis sepeda Anda. UX adalah topik yang jauh lebih dalam.
Kesimpulan
Pendekatan yang digunakan oleh kerangka kerja Cutter memungkinkan identifikasi fungsi tes dengan beban kognitif minimal pada programmer: cukup tulis fungsi tes dan hanya itu. Kode tidak memerlukan penggunaan templat atau makro khusus, yang meningkatkan keterbacaannya.
Fitur perakitan dan pengujian yang berjalan dapat disembunyikan dalam modul yang dapat digunakan kembali untuk sistem perakitan seperti Makefile, CMake, dll. Pertanyaan tentang serangkaian pengujian terpisah masih harus ditanyakan dengan satu atau lain cara.
Kerugian dari pendekatan ini termasuk kesulitan menempatkan tes dalam file yang sama (unit terjemahan yang sama) sebagai kode utama. Sayangnya, dalam hal ini, tanpa petunjuk tambahan, tidak mungkin lagi mencari tahu fungsi mana yang perlu diluncurkan dan mana yang tidak. Untungnya, dalam C ++ biasanya merupakan kebiasaan untuk mendistribusikan tes dan implementasi ke file yang berbeda.
Adapun pembuangan makro terakhir, menurut saya pada prinsipnya mereka tidak boleh ditinggalkan. Macro memungkinkan, misalnya, untuk menulis pernyataan perbandingan yang lebih pendek, menghindari duplikasi kode:
void test_object_addition() { ensure_equals(2 + 2, 5); }
tetapi pada saat yang sama menjaga konten informasi yang sama dari masalah jika terjadi kesalahan:
Failure: test_object_addition <ensure_equals(2 + 2, 5)> expected: <5> actual: <4> test.c:5: test_object_addition()
Nama fungsi yang diuji, nama file, dan nomor baris dari awal fungsi dalam teori dapat diekstraksi dari informasi debug yang terkandung di perpustakaan yang dikumpulkan. Nilai yang diharapkan dan aktual dari ekspresi yang dibandingkan diketahui dengan fungsi ensure_equals()
. Makro memungkinkan Anda untuk "mengembalikan" ejaan asli dari pernyataan pengujian, yang darinya lebih jelas mengapa nilai 4
diharapkan.
Namun, ini bukan untuk semua orang. Apakah manfaat makro untuk kode tes berakhir di sana? Saya belum benar-benar memikirkan momen ini, yang mungkin berubah menjadi bidang yang baik untuk selanjutnya penyimpangan penelitian. Pertanyaan yang jauh lebih menarik: apakah mungkin membuat kerangka tiruan untuk C ++ tanpa makro?
Pembaca yang penuh perhatian juga mencatat bahwa benar-benar tidak ada SMS dan asbes dalam pelaksanaannya, yang merupakan nilai tambah yang tidak diragukan untuk ekologi dan ekonomi Bumi.