หน้าแรก: https://github.com/mity/acutest
Acutest คือเครื่องมือทดสอบหน่วย C/C++ ที่มีเป้าหมายเพื่อให้ง่ายที่สุดเท่าที่จะเป็นไปได้ โดยไม่ขัดขวางนักพัฒนา และเพื่อลดการพึ่งพาจากภายนอกให้เหลือน้อยที่สุด
เพื่อให้บรรลุเป้าหมายนั้น การใช้งานโดยสมบูรณ์จะอยู่ในไฟล์ส่วนหัว C ไฟล์เดียว และแกนกลางของไฟล์นั้นขึ้นอยู่กับฟังก์ชันไลบรารี C มาตรฐานเพียงไม่กี่ฟังก์ชันเท่านั้น
คุณสมบัติหลัก:
acutest.h
main()
)--tap
)--xml-output=FILE
)คุณสมบัติเฉพาะของ C ++:
std::exception
what()
จะถูกเขียนออกมาในข้อความแสดงข้อผิดพลาดคุณสมบัติเฉพาะของ Unix/Posix:
clock_gettime()
) ผู้ใช้สามารถวัดเวลาดำเนินการทดสอบด้วย --time=real
(เหมือนกับ --time
) และ --time=cpu
คุณสมบัติเฉพาะของลินุกซ์:
คุณสมบัติเฉพาะของ Windows:
--time
คุณสมบัติเฉพาะของ macOS:
โมดูล C/C++ ใดๆ ที่ใช้การทดสอบหน่วยอย่างน้อยหนึ่งรายการและรวมถึง acutest.h
สามารถสร้างเป็นโปรแกรมสแตนด์อโลนได้ เราเรียกไบนารีที่ได้ผลลัพธ์ว่า "ชุดทดสอบ" ตามวัตถุประสงค์ของเอกสารนี้ จากนั้นชุดโปรแกรมจะถูกดำเนินการเพื่อรันการทดสอบ ตามที่ระบุไว้ในตัวเลือกบรรทัดคำสั่ง
เราบอกว่าการทดสอบหน่วยใด ๆ จะประสบความสำเร็จก็ต่อเมื่อ:
หากต้องการใช้ Acutest เพียงรวมไฟล์ส่วนหัว acutest.h
ไว้ที่จุดเริ่มต้นของไฟล์ต้นฉบับ C/C++ ที่ใช้การทดสอบหน่วยตั้งแต่หนึ่งรายการขึ้นไป โปรดทราบว่าส่วนหัวจัดให้มีการใช้งานฟังก์ชัน main()
#include "acutest.h"
การทดสอบทุกครั้งควรนำไปใช้เป็นฟังก์ชันโดยมีต้นแบบดังต่อไปนี้:
void test_example ( void );
การทดสอบสามารถใช้มาโครตัวประมวลผลล่วงหน้าบางตัวเพื่อตรวจสอบเงื่อนไขการทดสอบ สามารถใช้งานได้หลายครั้ง และหากเงื่อนไขใดๆ เหล่านั้นล้มเหลว การทดสอบเฉพาะจะถือว่าล้มเหลว
TEST_CHECK
เป็นมาโครการทดสอบที่ใช้กันมากที่สุดซึ่งเพียงทดสอบเงื่อนไขบูลีนและล้มเหลวหากเงื่อนไขประเมินเป็นเท็จ (หรือศูนย์)
ตัวอย่างเช่น:
void test_example ( void )
{
void * mem ;
int a , b ;
mem = malloc ( 10 );
TEST_CHECK ( mem != NULL );
mem = realloc ( mem , 20 );
TEST_CHECK ( mem != NULL );
}
โปรดทราบว่าการทดสอบควรเป็นอิสระจากกันโดยสมบูรณ์ เมื่อใดก็ตามที่ชุดการทดสอบถูกเรียกใช้ ผู้ใช้สามารถทำการทดสอบในชุดทดสอบจำนวนเท่าใดก็ได้ ตามลำดับใดก็ได้ นอกจากนี้ ตามค่าเริ่มต้น บนแพลตฟอร์มที่รองรับ การทดสอบแต่ละหน่วยจะดำเนินการเป็นกระบวนการแบบสแตนด์อโลน (ย่อย)
สุดท้าย ไฟล์ต้นฉบับของชุดการทดสอบจะต้องแสดงรายการการทดสอบหน่วย โดยใช้มาโคร TEST_LIST
รายการระบุชื่อของการทดสอบแต่ละรายการ (ต้องไม่ซ้ำกัน) และตัวชี้ไปยังฟังก์ชันที่ใช้การทดสอบ ฉันขอแนะนำชื่อที่ใช้งานง่ายบนบรรทัดคำสั่ง: โดยเฉพาะการหลีกเลี่ยงช่องว่างและอักขระพิเศษอื่นๆ ที่อาจต้องมีการ Escape ในเชลล์ นอกจากนี้ หลีกเลี่ยงเครื่องหมายขีดกลาง ( -
) เป็นอักขระตัวแรกของชื่อ เนื่องจากสามารถตีความได้ว่าเป็นตัวเลือกบรรทัดคำสั่ง ไม่ใช่ชื่อทดสอบ
TEST_LIST = {
{ "example" , test_example },
...
{ NULL, NULL } /* zeroed record marking the end of the list */
};
โปรดทราบว่ารายการทดสอบจะต้องลงท้ายด้วยบันทึกที่เป็นศูนย์
สำหรับชุดการทดสอบขั้นพื้นฐาน นี่คือทั้งหมดที่คุณต้องรู้ไม่มากก็น้อย อย่างไรก็ตาม Acutest มีมาโครเพิ่มเติมซึ่งมีประโยชน์ในบางสถานการณ์ เรากล่าวถึงในส่วนย่อยต่อไปนี้
มีมาโคร TEST_ASSERT
ซึ่งคล้ายกับ TEST_CHECK
มาก แต่หากล้มเหลว จะยกเลิกการดำเนินการของการทดสอบหน่วยปัจจุบันทันที
ตัวอย่างเช่น:
void test_example ( void )
{
void * mem ;
int a , b ;
mem = malloc ( 10 );
TEST_ASSERT ( mem != NULL );
mem = realloc ( mem , 20 );
TEST_ASSERT ( mem != NULL );
}
การทำแท้งในกรณีของความล้มเหลวจะดำเนินการโดยการเรียก abort()
(หากการทดสอบดำเนินการเป็นกระบวนการย่อย) หรือผ่านทาง longjmp()
(หากไม่ใช่)
ดังนั้นจึงควรใช้เฉพาะในกรณีที่คุณเข้าใจถึงค่าใช้จ่ายที่เกี่ยวข้องกับการทดสอบการทำแท้งที่โหดร้ายเช่นนี้ ขึ้นอยู่กับการทดสอบหน่วยของคุณ ซึ่งอาจรวมถึงตัวอธิบายไฟล์ที่ไม่ได้ล้าง หน่วยความจำรั่ว อ็อบเจ็กต์ C++ ที่ถูกทำลายโดยไม่ได้เรียกตัวทำลาย และอื่นๆ
โดยทั่วไป ควรเลือกใช้ TEST_CHECK
มากกว่า TEST_ASSERT
เว้นแต่คุณจะรู้แน่ชัดว่าคุณทำอะไรและทำไมคุณถึงเลือก TEST_ASSERT
ในบางสถานการณ์
สำหรับ C++ จะมีมาโครเพิ่มเติม TEST_EXCEPTION
สำหรับการตรวจสอบโค้ดที่กำหนด (โดยทั่วไปเป็นเพียงฟังก์ชันหรือการเรียกใช้เมธอด) จะส่งข้อยกเว้นประเภทที่คาดไว้
การตรวจสอบล้มเหลวหากฟังก์ชันไม่เกิดข้อยกเว้นใดๆ หรือหากมีสิ่งใดที่เข้ากันไม่ได้
ตัวอย่างเช่น:
void test_example ( void )
{
TEST_EXCEPTION ( CallSomeFunction (), std:: exception );
}
หากการตรวจสอบเงื่อนไขล้มเหลว การให้ข้อมูลเพิ่มเติมเกี่ยวกับสถานการณ์มักจะเป็นประโยชน์ เพื่อให้สามารถแก้ไขปัญหาได้ง่ายขึ้น Acutest จัดเตรียมมาโคร TEST_MSG
และ TEST_DUMP
เพื่อจุดประสงค์นี้
อันแรกส่งออกข้อความที่เหมือน printf
ใด ๆ ส่วนอีกอันหนึ่งส่งออกการถ่ายโอนข้อมูลเลขฐานสิบหกของบล็อกหน่วยความจำที่ให้มา
ตัวอย่างเช่น:
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 ));
}
โปรดทราบว่ามาโครทั้งสองจะแสดงผลลัพธ์เฉพาะเมื่อมาโครการตรวจสอบล่าสุดล้มเหลวเท่านั้น กล่าวอีกนัยหนึ่งทั้งสองนี้เทียบเท่ากัน:
if (! TEST_CHECK ( some_condition != 0 ))
TEST_MSG ( "some message" );
TEST_CHECK ( some_condition != 0 );
TEST_MSG ( "some message" );
(โปรดทราบว่า TEST_MSG
ต้องการคอมไพเลอร์ที่รองรับมาโครแบบแปรผัน)
บางครั้ง การออกแบบฟังก์ชันการทดสอบของคุณเป็นการวนซ้ำข้อมูลที่รวบรวมชุดของเวกเตอร์ทดสอบและผลลัพธ์ที่คาดหวังไว้จะเป็นประโยชน์ ตัวอย่างเช่น ลองนึกภาพการทดสอบหน่วยของเราควรจะตรวจสอบการใช้งานฟังก์ชันแฮชบางประเภท และเรามีคอลเลกชันของเวกเตอร์ทดสอบสำหรับมันในข้อกำหนดเฉพาะของแฮช
ในกรณีเช่นนี้ จะมีประโยชน์มากในการเรียกชื่อที่เกี่ยวข้องกับเวกเตอร์ทดสอบทุกตัวและส่งออกชื่อในบันทึกเอาต์พุต เพื่อว่าหากการตรวจสอบใดล้มเหลว ง่ายต่อการระบุเวกเตอร์ทดสอบที่มีความผิด อย่างไรก็ตาม เนื้อหาของลูปอาจดำเนินการตรวจสอบแมโครหลายสิบรายการ ดังนั้นจึงอาจไม่เหมาะสมที่จะเพิ่มชื่อดังกล่าวเพื่อกำหนดข้อความตรวจสอบทุกข้อความในลูปเอง
เพื่อแก้ปัญหานี้ Acutest จัดเตรียมมาโคร TEST_CASE
มาโครระบุสตริงที่ทำหน้าที่เป็นชื่อเวกเตอร์ทดสอบ เมื่อใช้ Acutest จะตรวจสอบให้แน่ใจว่าในบันทึกผลลัพธ์ ชื่อที่ให้ไว้นำหน้าข้อความใด ๆ จากการตรวจสอบเงื่อนไขในภายหลัง
ตัวอย่างเช่น สมมติว่าเรากำลังทดสอบ SomeFunction()
ซึ่งควรจะส่งคืนอาร์เรย์ไบต์อื่นในบัฟเฟอร์ malloc
-ed ใหม่สำหรับอาร์เรย์ไบต์ที่กำหนดซึ่งมีขนาดบางขนาด จากนั้นเราสามารถทำสิ่งนี้ได้:
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 );
}
}
}
ชื่อที่ระบุใช้กับเช็คทั้งหมดที่ดำเนินการหลังการใช้ TEST_CASE
TEST_CASE
อีกครั้งเพื่อระบุชื่ออื่น หรือTEST_CASE
โดยมี NULL
เป็นอาร์กิวเมนต์มาโครจำนวนมากที่กล่าวถึงในส่วนก่อนหน้านี้มีคู่กันที่อนุญาตให้ส่งออกข้อความที่กำหนดเองแทนข้อความเริ่มต้นบางรายการ
ทั้งหมดนี้ชื่อเดียวกับมาโครที่กล่าวมาข้างต้น เพียงแต่เพิ่มส่วนต่อท้ายด้วยขีดล่างเท่านั้น ด้วยคำต่อท้าย พวกเขาคาดหวังรูปแบบสตริงที่เหมือน printf
และอาร์กิวเมนต์เพิ่มเติมที่เกี่ยวข้อง
ตัวอย่างเช่น แทนที่จะเป็นมาโครการตรวจสอบแบบธรรมดา
TEST_CHECK (a == b);
TEST_ASSERT (x < y);
TEST_EXCEPTION (SomeFunction(), std::exception);
เราสามารถใช้คู่ของตนกับข้อความที่กำหนดเองได้:
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");
คุณควรใช้ถ้อยคำที่เป็นกลางสำหรับข้อความเหล่านั้น เนื่องจากด้วยตัวเลือกบรรทัดคำสั่ง --verbose
ข้อความจะถูกออกจากระบบแม้ว่าการตรวจสอบที่เกี่ยวข้องจะผ่านสำเร็จก็ตาม
(หากคุณต้องการส่งข้อมูลการวินิจฉัยในกรณีที่การตรวจสอบล้มเหลว ให้ใช้มาโคร TEST_MSG
นั่นคือจุดประสงค์ของมัน)
ในทำนองเดียวกันแทน
TEST_CASE ( "name" );
เราสามารถใช้ความสมบูรณ์ยิ่งขึ้นได้
TEST_CASE_ ( "iteration #%d" , 42 );
อย่างไรก็ตาม โปรดทราบว่าสิ่งเหล่านี้สามารถใช้ได้ก็ต่อเมื่อคอมไพเลอร์ของคุณรองรับมาโครตัวประมวลผลล่วงหน้าแบบแปรผันเท่านั้น มาโคร Variadic กลายเป็นส่วนมาตรฐานของภาษา C ด้วย C99
เมื่อเราทำการทดสอบเสร็จแล้ว เราก็สามารถคอมไพล์มันเป็นโปรแกรม C/C++ ง่ายๆ ได้ ตัวอย่างเช่น สมมติว่า cc
คือคอมไพเลอร์ C ของคุณ:
$ cc test_example.c -o test_example
เมื่อชุดการทดสอบถูกคอมไพล์ ไบนารีการทดสอบผลลัพธ์ที่สามารถใช้เพื่อรันการทดสอบได้
รหัสทางออกของชุดการทดสอบคือ 0 หากการทดสอบหน่วยที่ดำเนินการทั้งหมดผ่าน, 1 หากการทดสอบใดล้มเหลว หรือหมายเลขอื่นใดหากเกิดข้อผิดพลาดภายใน
ตามค่าเริ่มต้น (โดยไม่มีตัวเลือกบรรทัดคำสั่ง) ระบบจะรันการทดสอบหน่วยที่นำไปใช้งานทั้งหมด นอกจากนี้ยังสามารถรันเฉพาะชุดย่อยของการทดสอบหน่วยตามที่ระบุไว้ในบรรทัดคำสั่ง:
$ ./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
โปรดทราบว่าอาร์กิวเมนต์บรรทัดคำสั่งเดียวสามารถเลือกหน่วยทดสอบทั้งกลุ่มได้เนื่องจาก Acutest ใช้การเลือกการทดสอบหน่วยหลายระดับ (หน่วยที่ 1 ตรงกับหน่วยทดสอบอย่างน้อยหนึ่งหน่วย):
การจับคู่แบบตรงทั้งหมด : เมื่ออาร์กิวเมนต์ตรงกับชื่อเต็มของการทดสอบหน่วย ระบบจะเลือกเฉพาะการทดสอบที่กำหนด
การจับคู่คำ : เมื่ออาร์กิวเมนต์ไม่ตรงกับชื่อการทดสอบที่สมบูรณ์ใดๆ แต่ตรงกับทั้งคำในชื่อการทดสอบตั้งแต่ 1 ชื่อขึ้นไป การทดสอบดังกล่าวทั้งหมดจะถูกเลือก
อักขระต่อไปนี้ถือเป็นตัวคั่นคำ: ช่องว่าง
, tabulator t
, ขีด -
, ขีดเส้นใต้ _
, เครื่องหมายทับ /
, .
, ลูกน้ำ ,
, ทวิภาค :
, อัฒภาค ;
-
การจับคู่สตริงย่อย : หากแม้แต่การจับคู่คำล้มเหลวในการเลือกการทดสอบใดๆ การทดสอบทั้งหมดที่มีชื่อซึ่งมีอาร์กิวเมนต์เป็นสตริงย่อยจะถูกเลือก
ด้วยการนำกลยุทธ์การตั้งชื่อการทดสอบที่เหมาะสมมาใช้ จะทำให้ผู้ใช้สามารถรัน (หรือข้ามหากใช้ --exclude
) การทดสอบที่เกี่ยวข้องทั้งตระกูลได้อย่างง่ายดายด้วยอาร์กิวเมนต์บรรทัดคำสั่งเดียว
ตัวอย่างเช่นพิจารณาชุดทดสอบ test_example
ซึ่งใช้การทดสอบ foo-1
, foo-2
, foomatic
, bar-1
และ 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)
คุณสามารถใช้ --list
หรือ -l
เพื่อแสดงรายการการทดสอบหน่วยทั้งหมดที่ดำเนินการโดยชุดการทดสอบที่กำหนด:
$ ./test_example --list
หากต้องการดูคำอธิบายสำหรับตัวเลือกบรรทัดคำสั่งที่รองรับทั้งหมด ให้รันไบนารี่ด้วยตัวเลือก --help
:
$ ./test_example --help
ถาม: โครงการนี้เรียกว่า "CUTest" ไม่ใช่หรือ
ก . ใช่. ได้รับการเปลี่ยนชื่อแล้วเนื่องจากพบว่าชื่อเดิมมีมากเกินไป
ถาม: ฉันจำเป็นต้องแจกจ่ายไฟล์ README.md
และ/หรือ LICENSE.md
หรือไม่
ตอบ: ไม่ ส่วนหัว acutest.h
มี URL ไปยัง repo ของเรา บันทึกเกี่ยวกับลิขสิทธิ์ และข้อกำหนดสิทธิ์การใช้งาน MIT อยู่ภายใน ตราบใดที่คุณปล่อยสิ่งเหล่านั้นไว้เหมือนเดิม เราก็ไม่เป็นไรหากคุณเพิ่มส่วนหัวลงในโปรเจ็กต์ของคุณเท่านั้น ท้ายที่สุดแล้ว การใช้งานที่เรียบง่ายและมีลักษณะส่วนหัวแบบ all-in-one คือเป้าหมายหลักของเรา
Acutest ได้รับการคุ้มครองด้วยใบอนุญาต MIT โปรดดูไฟล์ LICENSE.md
หรือจุดเริ่มต้นของ acutest.h
สำหรับข้อความฉบับเต็ม
โครงการอยู่บน GitHub:
คุณสามารถค้นหา Acutest เวอร์ชันล่าสุดได้ที่นั่น สนับสนุนการปรับปรุงหรือรายงานข้อบกพร่อง