مرحبا يا هبر! اسمي إيجور دانلينكو. أقوم بتطوير منصة رقمية للخدمات المصرفية عبر الإنترنت للشركات في Sberbank Business Online ، واليوم أريد أن أخبركم عن إجراء تطوير CI الذي اعتمدناه.
كيف تأتي تغييرات المطور إلى ضخ في فرع الإصدار؟ يقوم المطور بإجراء تغييرات محليًا ويدفع إلى نظام التحكم في الإصدار الخاص بنا. نستخدم Bitbucket مع مكون إضافي للمؤلف (كتبنا عن هذا المكون الإضافي سابقًا
هنا ). في هذه التغييرات ، يتم إطلاق التجميع ومطاردة الاختبارات (الوحدة ، التكامل ، الوظيفية). إذا لم يفشل التجميع وتم اجتياز جميع الاختبارات بنجاح ، وكذلك بعد مراجعة ناجحة ، يتم صب طلب السحب في الفرع الرئيسي.
ولكن مع مرور الوقت ، ازداد عدد الفرق. ازداد عدد الاختبارات بشكل متناسب. لقد أدركنا أن مثل هذا العدد من الفرق سيسرع من ظهور مشكلة "سحب الطلب البطيء" ، وسيصبح من المستحيل تطوير منتج. حاليا ، لدينا حوالي 40 فريق. إلى جانب الميزات الجديدة ، يجلبون اختبارات جديدة ، والتي يجب أيضًا تشغيلها على طلبات السحب.
كنا نظن أنه سيكون رائعًا إذا عرفنا الاختبارات التي يجب إجراؤها لتغيير جزء معين من التعليمات البرمجية.
وهكذا تمكنا من حل هذه المشكلة.
بيان المشكلة
هناك مشروع به اختبارات ، ونريد تحديد الاختبارات التي يجب تشغيلها عند "لمس" ملف معين.
كلنا نعرف عن مكتبة تغطية كود EclEmma JaCoCo. أخذنا ذلك كأساس.
قليلا عن JaCoCo
JaCoCo هي مكتبة لقياس تغطية الكود بالاختبارات. يعتمد العمل على تحليل بايتات التعليمات البرمجية. يقوم الوكيل بجمع معلومات التنفيذ وتحميلها بناءً على طلب أو إيقاف تشغيل JVM.
هناك ثلاث طرق لجمع البيانات:
- نظام الملفات: بعد إيقاف JVM ، ستتم كتابة البيانات في ملف.
- خادم TCP Socket: يمكنك توصيل أدوات خارجية بـ JVM واستقبال البيانات من خلال المقبس.
- عميل TCP Socket: عند تشغيله ، يتصل عامل JaCoCo بنقطة نهاية TCP محددة.
لقد اخترنا الخيار الثاني.
الحل
من الضروري إدراك القدرة على تشغيل التطبيقات والاختبارات نفسها مع وكيل JaCoCo.
بادئ ذي بدء ، نضيف إلى تدريج القدرة على إجراء الاختبارات مع وكيل JaCoCo.
يمكن تشغيل وكيل Java:
-javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]
أضف تبعية لمشروعنا:
dependencies { compile 'org.jacoco:org.jacoco.agent:0.8.0' }
نحتاج فقط أن نبدأ بالوكيل لجمع الإحصائيات ، لذلك نضيف علامة withJacoco مع القيمة الافتراضية false إلى gradle.properties. كما نحدد الدليل حيث سيتم جمع الإحصائيات والعنوان والمنفذ.
أضف وسيطة jvm مع الوكيل إلى مهمة بدء الاختبار:
if (withJacoco.toBoolean()) { … jvmArgs "-javaagent:${tempPath}=${jacocoArgs.join(',')}".toString() }
الآن ، بعد كل إتمام ناجح للاختبار ، نحتاج إلى جمع الإحصائيات باستخدام JaCoCo. للقيام بذلك ، اكتب مستمع TestNG.
public class JacocoCoverageTestNGListener implements ITestListener { private static final IntegrationTestsCoverageReporter reporter = new IntegrationTestsCoverageReporter(); private static final String TEST_NAME_PATTERN = "%s.%s"; @Override public void onTestStart(ITestResult result) { reporter.resetCoverageDumpers(String.format(TEST_NAME_PATTERN, result.getInstanceName(), result.getMethod().getMethodName())); } @Override public void onTestSuccess(ITestResult result) { reporter.report(String.format(TEST_NAME_PATTERN, result.getInstanceName(), result.getMethod().getMethodName())); } }
أضف مستمعًا إلى testng.xml وعلق عليه ، حيث أننا لسنا بحاجة إليه في اختبار تشغيل عادي.
الآن لدينا الفرصة لإجراء الاختبارات مع وكيل JaCoCo ، مع كل إحصائيات اختبار ناجحة سيتم جمعها.
المزيد حول كيفية تنفيذ المراسل لجمع الإحصائيات.
أثناء تهيئة المراسل ، يتم الاتصال بالوكلاء ، ويتم إنشاء دليل حيث يتم تخزين الإحصائيات ، وسيتم جمع الإحصائيات.
أضف طريقة التقرير:
public void report(String test) { reportClassFiles(test); reportResources(test); }
تقوم طريقة reportClassFile بإنشاء مجلد jvm في دليل الإحصائيات ، حيث يتم تخزين الإحصائيات التي تم جمعها بواسطة ملفات الفئة.
تقوم طريقة reportResources بإنشاء مجلد الموارد ، الذي يقوم بتخزين الإحصائيات التي تم جمعها عن الموارد (لجميع الملفات غير التابعة للفئة).
يحتوي التقرير على كل منطق الاتصال بوكيل ، وقراءة البيانات من مقبس والكتابة إلى ملف. يتم تنفيذها بواسطة الأدوات التي توفرها JaCoCo ، مثل org.jacoco.core.runtime.RemoteControlReader / RemoteControlWriter.
تستخدم الدالتان reportClassFiles و reportResources وظيفة dumpToFile العامة.
public void dumpToFile(File file) { try (Writer fileWriter = new BufferedWriter(new FileWriter(file))) { for (RemoteControlReader remoteControlReader : remoteControlReaders) { remoteControleReader.setExecutionDataVisitor(new IExecutionDataVisitor() { @Override public void visitClassExecution(ExecutionData data) { if (data.hasHits()) { String name = data.getName(); try { fileWriter.write(name); fileWriter.write('\n'); } catch (IOException e) { throw new RuntimeException(e); } } } }); } } }
ستكون نتيجة الوظيفة ملفًا يحتوي على مجموعة من الفئات / الموارد التي يؤثر عليها هذا الاختبار.
وهكذا ، بعد تشغيل جميع الاختبارات ، لدينا دليل يحتوي على إحصائيات حول ملفات الصف والموارد.
يبقى كتابة خط أنابيب لجمع الإحصائيات اليومية والإضافة إلى إطلاق خط أنابيب لفحص طلبات السحب.
نحن لسنا مهتمين بمراحل تجميع المشروع ، ولكننا سننظر في مرحلة نشر الإحصائيات بمزيد من التفصيل.
stage('Agregate and parse result') { def inverterInJenkins = downloadMavenDependency( url: NEXUS_RELEASE_REPOSITORY, group: '', name: 'coverage-inverter', version: '0', type: 'jar', mavenHome: wsp ) dir('coverage-mapping') { gitFullCheckoutRef '', '', 'coverage-mapping', "refs/heads/${params.targetBranch}-integration-tests" sh 'rm -rf *' } sh "ls -lRa ..//out/coverage/" def inverter = wsp + inverterInJenkins.substring(wsp.length()) sh "java -jar ${inverter} " + "-d ..//out/coverage/jvm " + "-o coverage-mapping//jvm " + "-i coverage-config/jvm-include " + "-e coverage-config/jvm-exclude" sh "java -jar ${inverter} " + "-d ..//out/coverage/resources " + "-o coverage-mapping//resources " + "-i coverage-config/resources-include " + "-e coverage-config/resources-exclude" gitPush '', '', 'coverage-mapping', "${params.targetBranch}-integration-tests" }
في تخطيط التغطية ، نحتاج إلى تخزين اسم الملف وداخله قائمة الاختبارات التي يجب تشغيلها. نظرًا لأن نتيجة جمع الإحصائيات هي اسم الاختبار الذي يخزن مجموعة الفئات والموارد ، فنحن بحاجة إلى عكس كل شيء واستبعاد البيانات غير الضرورية (الفئات من مكتبات الجهات الخارجية).
نقوم بعكس إحصائياتنا وندفع إلى مستودعنا.
يتم جمع الإحصاءات كل ليلة. يتم تخزينها في مستودع منفصل لكل فرع الإصدار.
البنغو!
الآن ، عند تشغيل الاختبارات ، علينا فقط العثور على الملف المعدل وتحديد الاختبارات التي يجب تشغيلها.
المشاكل التي واجهناها:
- نظرًا لأن JaCoCo يعمل فقط مع البايت كود ، فمن المستحيل جمع إحصائيات حول ملفات مثل .xml و .gradle و .sql من الصندوق. لذلك ، كان علينا "ربط" قراراتنا.
- المراقبة المستمرة لأهمية الإحصاءات وتكرار التجميع ، إذا فشل التجميع الليلي لسبب ما ، فسيتم استخدام إحصائيات "الأمس" للتحقق في طلبات السحب.