
في SparkPost، نخصص الكثير من الوقت والجهد لاختبار كودنا. منصتنا مكتوبة بلغة C، ومؤخرًا قمت بالبحث في دمجها مع إطار عمل اختبار وحدة يسمى "CppUTest"، الذي يوفر اختبارًا على نمط xUnit لـ C/C++. هذا الإطار قوي وغني بالميزات وتحت التطوير النشط، مما يجعله اختيارًا رائعًا. كما يوفر طبقة تكامل مع C مما سهل استخدامه مع كود منصتنا المكتوب بلغة C رغم أن معظم الإطار مكتوب بلغة C++. يغطي هذا الشرح كيفية البدء مع CppUTest في مشاريعك الخاصة.
في SparkPost، نبذل الكثير من الوقت والجهد في اختبار الشفرة. منصتنا مكتوبة بلغة C، ومؤخرًا بحثت في دمجها مع إطار اختبار وحدة يسمى “CppUTest”، الذي يقدم اختبارات بأسلوب xUnit لـ C/C++. هذا الإطار قوي، وغني بالميزات، ويخضع لتطوير نشط، مما يجعله اختيارًا ممتازًا. كما يوفر طبقة تكامل C مما جعله سهل الاستخدام مع شيفرة منصتنا C على الرغم من أن معظم الإطار مكتوب بـ C++. يغطي هذا البرنامج التعليمي كيفية البدء باستخدام CppUTest في مشاريعك الخاصة.
تحميل CppUTest
صفحة مشروع CppUTest متاحة هنا، والمستودع موجود على github. يتم تضمينه أيضًا في مستودعات إدارة الحزم للعديد من توزيعات لينكس، وكذلك homebrew على نظام Mac OS. الأمثلة التالية تم تنفيذها على نظام Mac OS X، لكنها مشتقة من كود مكتوب لـRed Hat، وهو نظام التشغيل الذي يعمل عليه نظامنا الأساسي.
الأساسيات موثقة جيدًا على الصفحة الرئيسية لـ CppUTest. سنقوم بالمرور بسرعة على ذلك والوصول إلى بعض الميزات الأكثر إثارة.
وضع الأساس
مشروع Makefile
سيكون ملف makefile للمشروع على نفس المستوى مثل أدلة 'src' و't' في جذر المشروع. يجب أن يبدو كالتالي:
# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=example TEST_DIR=t test: make -C $(TEST_DIR) test_clean: make -C $(TEST_DIR) clean code.o: gcc -c -I$(CODE_DIR) $(CODE_DIR)/code.cpp -o $(CODE_DIR)/code.o main: code.o gcc -I$(CODE_DIR) $(CODE_DIR)/code.o $(SRC_DIR)/main.cpp -o $(OUT) all: test main clean: test_clean rm $(SRC_DIR)/*.o $(CODE_DIR)/*.o $(OUT)
لاحظ أن هذا يستخدم 'make -C' للأهداف التجريبية – يعني أنه سيستخدم 'make' مرة أخرى باستخدام ملف makefile في دليل الاختبار.
في هذه المرحلة يمكننا تجميع الكود الموجود في 'src' باستخدام ملف makefile ورؤية أنه يعمل:
[]$ make main gcc -c -I./src/code ./src/code/code.cpp -o ./src/code/code.o gcc -I./src/code ./src/code/code.o ./src/main.cpp -o example []$ ./example hello world!
اختبارات Makefile
بالنسبة للاختبارات، تكون الأمور أكثر تعقيدًا بعض الشيء لأنه علينا تحميل وتكامل بشكل صحيح مع مكتبة CppUTest.
يوفر مستودع CppUTest ملفًا يسمى "MakefileWorker.mk". ويوفر الكثير من الوظائف التي تجعل البناء مع CppUTest سهلًا. يوجد الملف تحت دليل "build" في مستودع git. في هذا الدليل سوف نفترض أنه تم نسخه إلى الدليل 't/'. يمكن استخدامه كما يلي:
# نحن لا نريد استخدام المسارات النسبية، لذا نضبط هذه المتغيرات PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # تحديد مكان وجود الشيفرة المصدرية والمتضمنات INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # تحديد مكان وجود شيفرة الاختبار TEST_SRC_DIRS = $(TEST_DIR) # ماذا نسمي الملفات الثنائية للاختبار TEST_TARGET=example # أين توجد مكتبة cpputest CPPUTEST_HOME=/usr/local # تنفيذ MakefileWorker.mk بالمتغيرات المعرفة هنا include MakefileWorker.mk
لاحظ أن CPPUTEST_HOME يجب أن يتم ضبطه إلى أينما تم تثبيت CppUTest. إذا كنت قد قمت بتثبيت حزمة توزيعة، فعادة ما يكون تحت /usr/local في نظام linux/mac. إذا كنت قد قمت بعمل تفريغ للمستودع بنفسك، فهو أينما كان ذلك التفريغ.
تم توثيق جميع هذه الخيارات في MakefileWorker.mk.
كما يضيف MakefileWorker.mk عددًا قليلاً من أهداف makefile، بما في ذلك ما يلي:
all - يبني الاختبارات المشار إليها في makefile
clean - يزيل جميع ملفات الكائن وgcov الناتجة عن الاختبارات
realclean - يزيل أي ملفات كائن أو gcov في شجرة الدليل بأكملها
flags - يسرد جميع الأعلام المفعلة المستخدمة لتجميع الاختبارات
debug - يسرد جميع ملفات المصدر والكائنات والتبعيات و'الأشياء التي تحتاج إلى تنظيف'
تغطية الشفرة
لن تكون اختبارات الوحدة كاملة بدون تقرير تغطية. الأداة الشائعة لهذا للمشاريع التي تستخدم gcc هي gcov، المتاحة كجزء من مجموعة أدوات gcc القياسية. يندمج Cpputest بسهولة مع gcov، كل ما عليك فعله هو إضافة هذا السطر إلى ملف makefile:
CPPUTEST_USE_GCOV=Y
بعد ذلك، نحتاج إلى التأكد من أن سكريبت filterGcov.sh من هذا المستودع في '/scripts/filterGcov.sh' نسبة إلى مكان تعيين 'CPPUTEST_HOME' ليكون. كما يحتاج إلى أن يكون لديه أذونات تنفيذ.
في ملف Makefile المثال، سيتم نشره في '/usr/local/scripts/filterGcov.sh'. إذا كنت تقوم بتشغيل CppUTest من تحقق من المستودع، يجب أن يعمل كل شيء دون تعديل.
مع ذلك، يمكنك ببساطة تشغيل 'make gcov' وسيتم إنشاء التحليل لك. في حالتنا، سنحتاج إلى 'make -B' لإعادة بناء ملفات الكائن مع تفعيل gcov:
[]$ make -B gcov < مخرجات الترجمة > for d in /Users/ykuperman/code/blogpost/qa/src/code ; do \ FILES=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $FILES >> gcov_output.txt 2>>gcov_error.txt ; \ done for f in ; do \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ done /usr/local/scripts/filterGcov.sh gcov_output.txt gcov_error.txt gcov_report.txt example.txt cat gcov_report.txt 100.00% /Users/ykuperman/code/blogpost/qa/src/code/code.cpp mkdir -p gcov mv *.gcov gcov mv gcov_* gcov See gcov directory for details
سيؤدي هذا إلى إخراج عدد من الملفات إلى مجلد 'gcov' جديد. هذه الملفات هي:
code.cpp.gcov – الملف الفعلي لـ 'gcov' للكود الذي يتم اختباره
gcov_error.txt – تقرير خطأ (في حالتنا، ينبغي أن يكون فارغ)
gcov_output.txt – المخرج الفعلي لأمر gcov الذي تم تشغيله
gcov_report.txt – ملخص للتغطية لكل ملف يتم اختباره
gcov_report.txt.html – نسخة بتنسيق HTML من gcov_report
اكتشاف تسرب الذاكرة Cpputest
يسمح لك Cpputest باكتشاف تسرب الذاكرة تلقائيًا عن طريق إعادة تعريف وظائف "malloc/free" القياسية لاستخدام الطبقات الوظيفية الخاصة به بدلاً من ذلك. يتيح ذلك له اكتشاف التسربات بسرعة والإبلاغ عنها لكل تنفيذ اختبار. يتم تمكين هذا بشكل افتراضي في MakefileWorker.mk، لذلك فهو مفعّل بالفعل مع الخطوات الموضحة حتى الآن.
لتوضيح ذلك، دعونا نشهد بعض التسربات في test_func() !
بالعودة إلى code.c، نضيف malloc() إلى الدالة، كالتالي:
int test_func() { malloc(1); return 1; }
الآن، بعد إعادة التجميع، يتم إنتاج الخطأ التالي:
test.cpp:9: خطأ: فشل في TEST(AwesomeExamples, FirstExample) تم العثور على تسرب للذاكرة. عدد المخصصات (4) حجم التسرب: 1 تم تخصيصه في: ./code.c والخط: 6. النوع: "malloc" ذاكرة: <0x7fc9e94056d0> المحتوى: 0000: 00 |.| إجمالي عدد التسربات: 1
يوضح هذا أي اختبار تسبب في التسرب، وأين حدث التسرب في الشيفرة المصدرية، وما كان في الذاكرة المتسربة. مفيد جدًا!
هناك بعض الملاحظات مع هذه الميزة:
يستخدم Cpputest تعريفات مسبقة لإعادة تعريف ديناميكيًا جميع الاستدعاءات لوظائف إدارة الذاكرة القياسية. يعني ذلك أنه سيعمل فقط للاستدعاءات في الشيفرة المصدرية تحت الاختبار نظرًا لأن هذا هو ما يتم تجميعه مع تجاوزات CppUTest. لن يتم اكتشاف التسريبات في المكتبات المرتبطة.
أحيانًا الذاكرة التي تم تخصيصها لمدة حياة العملية بالكامل ليست مخصصة ليتم تحريرها. هذا يمكن أن يتسبب في حدوث الكثير من الأخطاء المزعجة إذا كنت تختبر وحدة بها هذا السلوك. لتعطيل كشف التسرب، يمكنك القيام بذلك:
CPPUTEST_USE_MEM_LEAK_DETECTION=N
مهتم بمعرفة المزيد؟
هذه مجرد قمة جبل الجليد عندما يتعلق الأمر بجميع الميزات الموجودة في هذه الأداة. بالإضافة إلى الأساسيات التي نوقشت هنا، تحتوي أيضًا على إطار عمل محاكاة، وطبقة تكامل مباشرة مع C، وإطار عمل إضافات، لذكر بعض الميزات البارزة. يحتوي المستودع أيضًا على دليل كامل للبرامج المساعدة التي يمكن أن تساعد في أتمتة بعض الأجزاء الروتينية من العمل مع الإطار.
آمل أن تساعدك المعلومات هنا في تحسين جودة كود C/C++ باستخدام هذه الأداة الرائعة!