Beranda: https://github.com/mity/acutest
Acutest adalah fasilitas pengujian unit C/C++ yang bertujuan sesederhana mungkin, tidak menghalangi pengembang dan meminimalkan ketergantungan eksternal.
Untuk mencapai hal tersebut, implementasi lengkap berada dalam satu file header C, dan intinya hanya bergantung pada beberapa fungsi pustaka C standar.
Fitur utama:
acutest.h
.main()
).--tap
).--xml-output=FILE
).Fitur khusus C++:
std::exception
, what()
ditulis dalam pesan kesalahan.Fitur khusus Unix/Posix:
clock_gettime()
), pengguna dapat mengukur waktu eksekusi pengujian dengan --time=real
(sama dengan --time
) dan --time=cpu
.Fitur khusus Linux:
Fitur khusus Windows:
--time
.fitur khusus MacOS:
Modul C/C++ apa pun yang mengimplementasikan satu atau lebih pengujian unit dan termasuk acutest.h
, dapat dibuat sebagai program mandiri. Kami menyebut biner yang dihasilkan sebagai "test suite" untuk tujuan dokumen ini. Suite tersebut kemudian dijalankan untuk menjalankan pengujian, sebagaimana ditentukan dengan opsi baris perintahnya.
Kami mengatakan pengujian unit apa pun berhasil jika dan hanya jika:
Untuk menggunakan Acutest, cukup sertakan file header acutest.h
di awal file sumber C/C++ yang mengimplementasikan satu atau lebih pengujian unit. Perhatikan bahwa header menyediakan implementasi fungsi main()
.
#include "acutest.h"
Setiap pengujian seharusnya diimplementasikan sebagai fungsi dengan prototipe berikut:
void test_example ( void );
Pengujian dapat menggunakan beberapa makro praprosesor untuk memvalidasi kondisi pengujian. Tes tersebut dapat digunakan berkali-kali, dan jika salah satu kondisi tersebut gagal, pengujian tertentu dianggap gagal.
TEST_CHECK
adalah makro pengujian yang paling umum digunakan yang hanya menguji kondisi boolean dan gagal jika kondisi bernilai salah (atau nol).
Misalnya:
void test_example ( void )
{
void * mem ;
int a , b ;
mem = malloc ( 10 );
TEST_CHECK ( mem != NULL );
mem = realloc ( mem , 20 );
TEST_CHECK ( mem != NULL );
}
Perhatikan bahwa pengujian harus benar-benar independen satu sama lain. Setiap kali rangkaian pengujian dipanggil, pengguna dapat menjalankan sejumlah pengujian dalam rangkaian pengujian tersebut, dalam urutan apa pun. Selanjutnya secara default, pada platform yang didukung, setiap pengujian unit dijalankan sebagai (sub) proses yang berdiri sendiri.
Terakhir, file sumber rangkaian pengujian harus mencantumkan pengujian unit, menggunakan makro TEST_LIST
. Daftar tersebut menentukan nama setiap pengujian (harus unik) dan penunjuk ke fungsi yang mengimplementasikan pengujian. Saya merekomendasikan nama yang mudah digunakan pada baris perintah: terutama menghindari spasi dan karakter khusus lainnya yang mungkin memerlukan pelolosan di shell; hindari juga tanda hubung ( -
) sebagai karakter pertama nama, karena dapat diartikan sebagai opsi baris perintah dan bukan sebagai nama pengujian.
TEST_LIST = {
{ "example" , test_example },
...
{ NULL, NULL } /* zeroed record marking the end of the list */
};
Perhatikan bahwa daftar tes harus diakhiri dengan catatan nol.
Untuk rangkaian pengujian dasar, kurang lebih hanya ini yang perlu Anda ketahui. Namun Acutest menyediakan lebih banyak makro yang dapat berguna dalam beberapa situasi tertentu. Kami membahasnya di sub-bagian berikut.
Ada makro TEST_ASSERT
yang sangat mirip dengan TEST_CHECK
tetapi, jika gagal, eksekusi pengujian unit saat ini akan langsung dibatalkan.
Misalnya:
void test_example ( void )
{
void * mem ;
int a , b ;
mem = malloc ( 10 );
TEST_ASSERT ( mem != NULL );
mem = realloc ( mem , 20 );
TEST_ASSERT ( mem != NULL );
}
Aborsi jika terjadi kegagalan dilakukan dengan memanggil abort()
(jika pengujian dijalankan sebagai proses anak) atau melalui longjmp()
(jika tidak).
Oleh karena itu, tes ini sebaiknya digunakan hanya jika Anda memahami biaya yang terkait dengan aborsi brutal terhadap tes tersebut. Bergantung pada apa yang dilakukan pengujian unit Anda, ini mungkin mencakup deskriptor file yang tidak di-flush, kebocoran memori, objek C++ yang dirusak tanpa pemanggilan destruktornya, dan masih banyak lagi.
Secara umum, TEST_CHECK
sebaiknya lebih disukai daripada TEST_ASSERT
, kecuali Anda tahu persis apa yang Anda lakukan dan mengapa Anda memilih TEST_ASSERT
dalam situasi tertentu.
Untuk C++, ada makro tambahan TEST_EXCEPTION
untuk memverifikasi kode yang diberikan (biasanya hanya fungsi atau pemanggilan metode) yang memunculkan jenis pengecualian yang diharapkan.
Pemeriksaan gagal jika fungsi tidak memunculkan pengecualian apa pun atau jika fungsi menampilkan sesuatu yang tidak kompatibel.
Misalnya:
void test_example ( void )
{
TEST_EXCEPTION ( CallSomeFunction (), std:: exception );
}
Jika pemeriksaan kondisi gagal, sering kali berguna untuk memberikan beberapa informasi tambahan tentang situasi tersebut sehingga masalah lebih mudah untuk di-debug. Acutest menyediakan makro TEST_MSG
dan TEST_DUMP
untuk tujuan ini.
Yang pertama mengeluarkan pesan seperti printf
, yang lain mengeluarkan dump heksadesimal dari blok memori yang disediakan.
Jadi misalnya:
void test_example ( void )
{
char produced [ 100 ];
char expected [] = "Hello World!" ;
SomeSprintfLikeFunction ( produced , "Hello %s!" , "world" );
TEST_CHECK ( strcmp ( produced , expected ) == 0 );
TEST_MSG ( "Expected: %s" , expected );
TEST_MSG ( "Produced: %s" , produced );
/* Or, if the function could provide some binary stuff, we might rather
* use TEST_DUMP instead in order to output a hexadecimal dump of the data.
*/
TEST_DUMP ( "Expected:" , expected , strlen ( expected ));
TEST_DUMP ( "Produced:" , produced , strlen ( produced ));
}
Perhatikan bahwa kedua makro mengeluarkan apa pun hanya ketika makro pemeriksaan terbaru gagal. Dengan kata lain, keduanya setara:
if (! TEST_CHECK ( some_condition != 0 ))
TEST_MSG ( "some message" );
TEST_CHECK ( some_condition != 0 );
TEST_MSG ( "some message" );
(Perhatikan bahwa TEST_MSG
memerlukan kompiler dengan dukungan makro variadik.)
Terkadang, berguna untuk mendesain fungsi pengujian Anda sebagai perulangan data yang menyediakan kumpulan vektor pengujian dan keluaran yang diharapkan masing-masing. Misalnya bayangkan pengujian unit kita seharusnya memverifikasi semacam implementasi fungsi hashing dan kita memiliki kumpulan vektor pengujian untuk itu dalam spesifikasi hash.
Dalam kasus seperti ini, sangat berguna untuk mengasosiasikan beberapa nama dengan setiap vektor pengujian dan menampilkan nama tersebut di log keluaran sehingga jika ada pemeriksaan yang gagal, mudah untuk mengidentifikasi vektor pengujian yang bersalah. Namun, badan perulangan dapat menjalankan lusinan makro pengecekan sehingga mungkin tidak praktis untuk menambahkan nama tersebut untuk menyesuaikan setiap pesan pemeriksaan dalam perulangan.
Untuk mengatasi ini, Acutest menyediakan makro TEST_CASE
. Makro menentukan string yang berfungsi sebagai nama vektor pengujian. Saat digunakan, Acutest memastikan bahwa dalam log keluaran, nama yang diberikan mendahului pesan apa pun dari pemeriksaan kondisi berikutnya.
Sebagai contoh, mari kita asumsikan kita sedang menguji SomeFunction()
yang seharusnya, untuk array byte tertentu dengan ukuran tertentu, mengembalikan array byte lain dalam buffer malloc
-ed yang baru. Lalu kita bisa melakukan sesuatu seperti ini:
struct TestVector {
const char * name ;
const uint8_t * input ;
size_t input_size ;
const uint8_t * expected_output ;
size_t expected_output_size ;
};
struct TestVector test_vectors [] = {
/* some data */
};
void test_example ( void )
{
int i ;
const uint8_t * output ;
size_t output_size ;
for ( i = 0 ; i < sizeof ( test_vectors ) / sizeof ( test_vectors [ 0 ]); i ++ ) {
struct TestVector * vec = & test_vectors [ i ];
/* Output the name of the tested test vector. */
TEST_CASE ( vec . name );
/* Now, we can check the function produces what it should for the
* current test vector. If any of the following checking macros
* produces any output (either because the check fails, or because
* high `--verbose` level is used), Acutest also outputs the currently
* tested vector's name. */
output = SomeFunction ( vec -> input , vec -> input_size , & output_size );
if ( TEST_CHECK ( output != NULL )) {
TEST_CHECK ( output_size == vec -> expected_output_size );
TEST_CHECK ( memcmp ( output , vec -> expected_output , output_size ) == 0 );
free ( output );
}
}
}
Nama yang ditentukan berlaku untuk semua pemeriksaan yang dijalankan setelah penggunaan TEST_CASE
TEST_CASE
digunakan lagi untuk menentukan nama lain; atauTEST_CASE
dengan NULL
sebagai argumennya.Banyak makro yang disebutkan di bagian sebelumnya memiliki mitra yang memungkinkan untuk mengeluarkan pesan khusus, bukan beberapa pesan default.
Semua ini memiliki nama yang sama dengan makro yang disebutkan di atas, hanya dengan tambahan akhiran garis bawah. Dengan akhiran, mereka kemudian mengharapkan format string seperti printf
dan argumen tambahan yang sesuai.
Jadi, misalnya, alih-alih memeriksa makro secara sederhana
TEST_CHECK (a == b);
TEST_ASSERT (x < y);
TEST_EXCEPTION (SomeFunction(), std::exception);
kita dapat menggunakan rekannya masing-masing dengan pesan khusus:
TEST_CHECK_ (a == b, " %d is equal to %d " , a, b);
TEST_ASSERT_ (x < y, " %d is lower than %d " , x, y);
TEST_EXCEPTION_ (SomeFunction(), std::exception, "SomeFunction() throws std::exception");
Anda harus menggunakan kata-kata netral untuk pesan tersebut karena, dengan opsi baris perintah --verbose
, pesan akan dikeluarkan meskipun pemeriksaan tersebut berhasil lolos.
(Jika Anda perlu mengeluarkan beberapa informasi diagnostik jika pemeriksaan gagal, gunakan makro TEST_MSG
. Itulah tujuannya.)
Demikian pula, bukannya
TEST_CASE ( "name" );
kita bisa menggunakan lebih kaya
TEST_CASE_ ( "iteration #%d" , 42 );
Namun perhatikan semua ini hanya dapat digunakan jika kompiler Anda mendukung makro praprosesor variadik. Makro variadik menjadi bagian standar bahasa C dengan C99.
Ketika kita selesai mengimplementasikan pengujian, kita cukup mengkompilasinya sebagai program C/C++ sederhana. Misalnya, dengan asumsi cc
adalah kompiler C Anda:
$ cc test_example.c -o test_example
Saat rangkaian pengujian dikompilasi, biner pengujian yang dihasilkan dapat digunakan untuk menjalankan pengujian.
Kode keluar dari rangkaian pengujian adalah 0 jika semua pengujian unit yang dijalankan lulus, 1 jika ada yang gagal, atau angka lainnya jika terjadi kesalahan internal.
Secara default (tanpa opsi baris perintah apa pun), ini menjalankan semua pengujian unit yang diterapkan. Itu juga hanya dapat menjalankan sebagian dari pengujian unit seperti yang ditentukan pada baris perintah:
$ ./test_example # Runs all tests in the suite
$ ./test_example test1 test2 # Runs only tests specified
$ ./test_example --exclude test3 # Runs all tests but those specified
Perhatikan bahwa argumen baris perintah tunggal dapat memilih seluruh kelompok unit pengujian karena Acutest mengimplementasikan beberapa tingkat pemilihan pengujian unit (yang pertama digunakan yang cocok dengan setidaknya satu unit pengujian):
Pencocokan tepat : Jika argumen sama persis dengan seluruh nama pengujian unit, maka pengujian tertentu saja yang dipilih.
Pencocokan kata : Jika argumen tidak cocok dengan nama pengujian yang lengkap, namun cocok dengan seluruh kata dalam satu atau lebih nama pengujian, maka semua pengujian tersebut akan dipilih.
Karakter berikut dikenali sebagai pembatas kata: spasi
, tabulator t
, tanda hubung -
, garis bawah _
, garis miring /
, titik .
, koma ,
, titik dua :
, titik koma ;
.
Pencocokan substring : Bahkan jika pencocokan kata gagal memilih pengujian apa pun, maka semua pengujian dengan nama yang berisi argumen sebagai substringnya akan dipilih.
Dengan mengadopsi strategi penamaan pengujian yang sesuai, hal ini memungkinkan pengguna untuk menjalankan (atau melewati jika --exclude
digunakan) dengan mudah seluruh rangkaian pengujian terkait dengan satu argumen baris perintah.
Misalnya pertimbangkan test suite test_example
yang mengimplementasikan tes foo-1
, foo-2
, foomatic
, bar-1
dan bar-10
:
$ ./test_example bar-1 # Runs only the test 'bar-1' (exact match)
$ ./test_example foo # Runs 'foo-1' and 'foo-2' (word match)
$ ./test_example oo # Runs 'foo-1', 'foo-2' and 'foomatic' (substring match)
$ ./test_example 1 # Runs 'foo-1' and 'bar-1' (word match)
Anda dapat menggunakan --list
atau -l
untuk mencantumkan semua pengujian unit yang diterapkan oleh rangkaian pengujian yang diberikan:
$ ./test_example --list
Untuk melihat deskripsi semua opsi baris perintah yang didukung, jalankan biner dengan opsi --help
:
$ ./test_example --help
T: Bukankah proyek ini dikenal sebagai "CUTest"?
J: Ya. Ini telah diganti namanya karena nama aslinya ternyata terlalu kelebihan beban.
Q: Apakah saya perlu mendistribusikan file README.md
dan/atau LICENSE.md
?
J: Tidak. Header acutest.h
menyertakan URL ke repo kami, catatan hak cipta, dan persyaratan lisensi MIT di dalamnya. Selama Anda membiarkannya utuh, kami baik-baik saja jika Anda hanya menambahkan header ke dalam proyek Anda. Bagaimanapun, penggunaan sederhana dan sifat header lengkap adalah tujuan utama kami.
Acutest dilindungi dengan lisensi MIT, lihat file LICENSE.md
atau awal acutest.h
untuk teks lengkapnya.
Proyek ini berada di github:
Anda dapat menemukan versi terbaru Acutest di sana, berkontribusi dengan penyempurnaan, atau melaporkan bug.