الصفحة الرئيسية: https://github.com/mity/acute
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
.ميزات محددة لنظام التشغيل Linux:
ميزات محددة لنظام التشغيل 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
. تحدد القائمة اسم كل اختبار (يجب أن يكون فريدًا) ومؤشرًا إلى وظيفة تنفذ الاختبار. أوصي باستخدام أسماء سهلة الاستخدام في سطر الأوامر: تجنب بشكل خاص المسافات والأحرف الخاصة الأخرى التي قد تتطلب الهروب في الصدفة؛ تجنب أيضًا استخدام الشرطة ( -
) كحرف أول من الاسم، حيث يمكن تفسيرها بعد ذلك على أنها خيار سطر أوامر وليس كاسم اختبار.
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 );
ومع ذلك، لاحظ أنه لا يمكن استخدام كل هذه العناصر إلا إذا كان المترجم الخاص بك يدعم وحدات ماكرو المعالج الأولي المتنوع. أصبحت وحدات الماكرو المتغيرة جزءًا قياسيًا من لغة 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 ينفذ عدة مستويات من اختيار اختبار الوحدة (يتم استخدام المستوى الأول المطابق لوحدة اختبار واحدة على الأقل):
التطابق التام : عندما تتطابق الوسيطة تمامًا مع الاسم الكامل لاختبار الوحدة، يتم تحديد الاختبار المحدد فقط.
مطابقة الكلمات : عندما لا تتطابق الوسيطة مع أي اسم اختبار كامل، ولكنها تتطابق مع الكلمة بأكملها في اسم اختبار واحد أو أكثر، فسيتم تحديد جميع هذه الاختبارات.
يتم التعرف على الأحرف التالية كمحددات للكلمات: المسافة
, 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 الخاص بمستودع الريبو الخاص بنا ومذكرة حقوق الطبع والنشر وشروط ترخيص MIT بداخله. طالما تركت هذه العناصر سليمة، فلا بأس إذا قمت فقط بإضافة الرأس إلى مشروعك. بعد كل شيء، الاستخدام البسيط وطبيعة الكل في واحد هو هدفنا الأساسي.
Acutest مغطى بترخيص MIT، راجع الملف LICENSE.md
أو بداية acutest.h
للحصول على نصه الكامل.
المشروع موجود على جيثب:
يمكنك العثور على أحدث إصدار من Acutest هناك، أو المساهمة في التحسينات أو الإبلاغ عن الأخطاء.