على عكس العديد من الأنظمة الأساسية ، تعاني Java من نقص مكتبات كعب الاتصال. إذا كنت في هذا العالم لفترة طويلة ، فمن المحتمل أن تكون على دراية بـ WireMock أو Betamax أو حتى Spock. يستخدم العديد من المطورين في الاختبارات Mockito لوصف سلوك الكائنات ، DataJpaTest مع قاعدة بيانات محلية h2 ، اختبارات Cucumber. ستقابل اليوم بديلاً خفيف الوزن سيساعدك على التغلب على المشكلات المختلفة التي قد تواجهها باستخدام هذه الأساليب. على وجه الخصوص ، يحاول anyStub حل المشكلات التالية:
- تبسيط تكوين بيئة الاختبار
- أتمتة جمع البيانات للاختبارات
- البقاء في اختبار التطبيق الخاص بك وتجنب اختبار لشيء آخر
ما هو anyStub وكيف يعمل
يلتف AnyStub بمكالمات الوظائف ، في محاولة للعثور على المكالمات المتطابقة التي تم تسجيلها بالفعل. شيئين يمكن أن يحدث مع هذا:
- إذا كان هناك مكالمة مطابقة ، فسيقوم anyStub باستعادة النتيجة المسجلة المرتبطة بتلك المكالمة وإعادتها
- إذا لم يكن هناك مكالمة مطابقة وتم السماح بالوصول إلى النظام الخارجي ، فسيقوم anyStub بإجراء هذه المكالمة وتسجيل هذه النتيجة وإعادتها
من خارج الصندوق ، يوفر anyStub غلافًا لعميل http من Apache HttpClient لإنشاء بذرة لطلبات http والعديد من الواجهات من javax.sql. * لاتصالات DB. يتم تزويدك أيضًا بواجهة برمجة تطبيقات لإنشاء بذرة لاتصالات أخرى.
AnyStub هي مكتبة فئة بسيطة ولا تتطلب تكوينًا خاصًا لبيئتك. تهدف هذه المكتبة إلى العمل مع تطبيقات الإقلاع الربيعي وستحصل على أقصى فائدة من خلال اتباع هذا المسار. يمكنك استخدامه خارج Spring ، في تطبيقات Java العادية ، ولكن بالتأكيد سيكون عليك القيام بعمل إضافي. يركز الوصف التالي على اختبار تطبيقات الإقلاع.
دعونا ننظر في اختبار التكامل. هذه هي الطريقة الأكثر إثارة وشاملة لاختبار النظام الخاص بك. في الواقع ، يقوم كل من spring-boot و JUnit بعمل كل شيء تقريبًا لك عند كتابة التعليقات التوضيحية السحرية:
@RunWith(SpringRunner.class) @SpringBootTest
في الوقت الحالي ، يتم التقليل من أهمية اختبارات التكامل وتستخدم إلى حد محدود ، ويتجنبها بعض المطورين. ويرجع ذلك أساسًا إلى إعداد الاختبارات وصيانتها المستهلكة للوقت أو الحاجة إلى تهيئة خاصة للبيئة على خوادم البنية.
مع anyStub ، لن تضطر إلى شل حركة spring-contex. بدلاً من ذلك ، فإن إبقاء السياق بالقرب من تكوين الإنتاج أمر بسيط ومباشر.
في هذا المثال ، سوف نرى كيفية توصيل anyStub باستهلاك خدمة ويب مريحة من الدليل من Pivotal.
توصيل مكتبة من خلال pom.xml
<dependency> <groupId>org.anystub</groupId> <artifactId>anystub</artifactId> <version>0.2.27</version> <scope>test</scope> </dependency>
والخطوة التالية هي تعديل سياق الربيع.
package hello; import org.anystub.http.StubHttpClient; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @Configuration public class TestConfiguration { @Bean public RestTemplateBuilder builder() { RestTemplateCustomizer restTemplateCustomizer = new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { HttpClient real = HttpClientBuilder.create().build(); StubHttpClient stubHttpClient = new StubHttpClient(real); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(stubHttpClient); restTemplate.setRequestFactory(requestFactory); } }; return new RestTemplateBuilder(restTemplateCustomizer); } }
لا يؤدي هذا التعديل إلى تغيير علاقات المكونات في التطبيق ، ولكن يحل محل تنفيذ واجهة واحدة فقط. هذا يرسلنا إلى مبدأ استبدال باربرا ليسك . إذا لم ينتهك تصميم التطبيق الخاص بك ، فلن ينتهك هذا الاستبدال الوظيفة.
كل شيء جاهز. هذا المشروع يتضمن بالفعل اختبار.
@RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTest { @Autowired private RestTemplate restTemplate; @Test public void contextLoads() { assertThat(restTemplate).isNotNull(); } }
هذا الاختبار فارغ ، لكنه يعمل بالفعل على سياق التطبيق. المرح يبدأ من هنا . كما قلنا أعلاه ، يتزامن سياق التطبيق في الاختبار مع سياق العمل الذي يتم فيه إنشاء CommandLineRunner والذي يتم فيه تنفيذ طلب http إلى النظام الخارجي.
@SpringBootApplication public class Application { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String args[]) { SpringApplication.run(Application.class); } @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } @Bean public CommandLineRunner run(RestTemplate restTemplate) throws Exception { return args -> { Quote quote = restTemplate.getForObject( "https://gturnquist-quoters.cfapps.io/api/random", Quote.class); log.info(quote.toString()); }; } }
هذا يكفي لإثبات تشغيل المكتبة. بعد بدء الاختبارات للمرة الأولى ، ستجد ملف جديد complete/src/test/resources/anystub/stub.yml
.
request0: exception: [] keys: [GET, HTTP/1.1, 'https://gturnquist-quoters.cfapps.io/api/random'] values: [HTTP/1.1, '200', OK, 'Content-Type: application/json;charset=UTF-8', 'Date: Thu, 25 Apr 2019 23:04:49 GMT', 'X-Vcap-Request-Id: 5ffce9f3-d972-4e95-6b5c-f88f9b0ae29b', 'Content-Length: 177', 'Connection: keep-alive', '{"type":"success","value":{"id":3,"quote":"Spring has come quite a ways in addressing developer enjoyment and ease of use since the last time I built an application using it."}}']
ماذا حدث قام برنامج spring-boot بتصميم RestTemplateBuilder من تكوين الاختبار إلى التطبيق. أدى هذا التطبيق للعمل من خلال تنفيذ كعب الروتين من عميل HTTP. اعترض StubHttpClient الطلب ، ولم يعثر على ملف كعب الروتين ، وقم بتنفيذ الطلب ، وحفظ النتيجة في ملف ، وعاد النتيجة المستردة من الملف.
من الآن فصاعدًا ، يمكنك تشغيل هذا الاختبار دون اتصال بالإنترنت وسيكون هذا الطلب ناجحًا. restTemplate.getForObject()
النتيجة نفسها. يمكنك الاعتماد على هذه الحقيقة في اختباراتك المستقبلية.
يمكنك العثور على جميع التغييرات الموضحة على جيثب .
في الواقع ، ما زلنا لم ننشئ اختبارًا واحدًا. قبل كتابة الاختبارات ، دعونا نرى كيف تعمل مع قواعد البيانات.
في هذا المثال ، سنضيف اختبار تكامل إلى الوصول إلى البيانات العلائقية باستخدام JDBC مع Spring من البرنامج التعليمي المحوري.
يبدو تكوين الاختبار لهذه الحالة كما يلي:
package hello; import org.anystub.jdbc.StubDataSource; import org.h2.jdbcx.JdbcDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class TestConfiguration { @Bean public DataSource dataSource() { JdbcDataSource ds = new JdbcDataSource(); ds.setURL("jdbc:h2:./test"); return new StubDataSource(ds); } }
هنا ، يتم إنشاء مصدر بيانات منتظم لقاعدة بيانات خارجية ولفه مع تطبيق كعب روتين - فئة StubDataSource. الربيع التمهيد يدمجها في السياق. نحتاج أيضًا إلى إنشاء اختبار واحد على الأقل لتشغيل سياق الربيع في الاختبار.
package hello; import org.anystub.AnyStubId; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTest { @Test @AnyStubId public void test() { } }
هذا مرة أخرى اختبار فارغ - مهمته الوحيدة هي تشغيل سياق التطبيق. هنا نرى @AnystubId
توضيحيًا مهمًا @AnystubId
، لكنه لن يشارك بعد.
بعد التشغيل الأول ، ستجد ملف src/test/resources/anystub/stub.yml
يشتمل على جميع مكالمات قاعدة البيانات. سوف تفاجأ كيف يعمل الربيع خلف الكواليس مع قواعد البيانات. تجدر الإشارة إلى أن إجراء اختبارات جديدة لن يؤدي إلى وصول حقيقي إلى قاعدة البيانات. إذا قمت بحذف test.mv.db ، فلن يظهر بعد إجراء الاختبارات بشكل متكرر. يمكن الاطلاع على مجموعة كاملة من التغييرات على جيثب .
لتلخيص. مع anyStub:
- لا تحتاج إلى إعداد بيئة اختبار على وجه التحديد
- يتم الاختبار باستخدام بيانات حقيقية
- تثبت الجولة الأولى من الاختبارات افتراضاتك وتحفظ بيانات الاختبار ، بينما تتحقق الاختبارات اللاحقة من أن النظام لم يتدهور
ربما لديك أسئلة: كيف يغطي هذا الحالات التي لا توجد فيها قاعدة البيانات بعد ، ماذا تفعل مع الاختبار السلبي ومعالجة الاستثناءات. سنعود إلى هذا ، لكن أولاً ، سنتعامل مع كتابة اختبارات بسيطة.
نحن نجرب الآن تجربة استخدام خدمة RESTful Web Service . لا يحتوي هذا المشروع على مكونات يمكن اختبارها. يتم إنشاء فئتين أدناه ، والتي ينبغي أن تصور طبقتين من بعض تصميم الهندسة المعمارية.
package hello; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class DataProvider { private final RestTemplate restTemplate; public DataProvider(RestTemplate restTemplate) { this.restTemplate = restTemplate; } Quote provideData() { return restTemplate.getForObject( "https://gturnquist-quoters.cfapps.io/api/random", Quote.class); } }
DataProvider يوفر الوصول إلى البيانات في متغير نظام خارجي.
package hello; import org.springframework.stereotype.Component; @Component public class DataProcessor { private final DataProvider dataProvider; public DataProcessor(DataProvider dataProvider) { this.dataProvider = dataProvider; } int processData() { return dataProvider.provideData().getValue().getQuote().length(); } }
سوف DataProcessor معالجة البيانات من نظام خارجي.
نعتزم اختبار DataProcessor
. من الضروري اختبار صحة خوارزمية المعالجة وحماية النظام من التدهور من التغييرات المستقبلية.
لتحقيق هذه الأهداف ، قد تفكر في إنشاء كائن وهمي DataProvider مع مجموعة بيانات وتمريره إلى مُنشئ DataProcessor في الاختبارات. يمكن أن تكون طريقة أخرى لتحليل DataProcessor لتسليط الضوء على معالجة فئة اقتباس. ثم ، من السهل اختبار مثل هذا الفصل باستخدام اختبارات الوحدة (بالتأكيد ، هذه هي الطريقة الموصى بها في الكتب المحترمة حول الشفرة النظيفة). دعنا نحاول تجنب تغييرات الكود واختراع بيانات الاختبار وكتابة اختبار.
@RunWith(SpringRunner.class) @SpringBootTest public class DataProcessorTest { @Autowired private DataProcessor dataProcessor; @Test @AnyStubId(filename = "stub") public void processDataTest() { assertEquals(131, dataProcessor.processData()); } }
حان الوقت للحديث عن التعليقات التوضيحيةAnystubId. يساعد هذا التعليق التوضيحي في إدارة ملفات كعب الروتين والتحكم فيها في الاختبارات. يمكن استخدامه مع فئة اختبار أو طريقتها. ينشئ هذا التعليق التوضيحي ملف كعب روتين فرديًا للمنطقة المقابلة. إذا تمت تغطية أي منطقة في نفس الوقت بالتعليقات التوضيحية على مستوى الفئة والطريقة ، فإن التعليق التوضيحي للطريقة له الأسبقية. يحتوي هذا التعليق التوضيحي على معلمة اسم الملف ، والتي تحدد اسم ملف كعب الروتين. تتم إضافة الامتداد ".yml" تلقائيًا إذا تم حذفه. من خلال تشغيل هذا الاختبار ، لن تجد ملفًا جديدًا. تم بالفعل إنشاء src/test/resources/anystub/stub.yml
سابقًا وسيعمل هذا الاختبار على إعادة استخدامه. لقد حصلنا على الرقم 131 من هذا كعب الروتين عن طريق تحليل نتيجة الاستعلام.
@Test @AnyStubId public void processDataTest2() { assertEquals(131, dataProcessor.processData()); Base base = getStub(); assertEquals(1, base.times("GET")); assertTrue(base.history().findFirst().get().matchEx_to(null, null, ".*gturnquist-quoters.cfapps.io.*")); }
في هذا الاختبار ، يظهر التعليق التوضيحيAnyStubId بدون معلمة اسم الملف. في هذه الحالة ، src/test/resources/anystubprocessDataTest2.yml
. تم بناء اسم الملف من اسم الوظيفة (الفئة) + ".yml". بمجرد قيام anyStub بإنشاء ملف جديد لهذا الاختبار ، ستحتاج إلى إجراء مكالمة نظام حقيقية. ومن حسن حظنا أن الاقتباس الجديد له نفس الطول. تظهر الشيكتان الأخيرتان كيفية اختبار سلوك التطبيق. إنه متاح لك: تحديد الاستعلامات حسب المعلمات أو أجزاء من المعلمات وحساب عدد الاستعلامات. هناك عدة أشكال من الأوقات ووظائف المطابقة التي يمكن العثور عليها في الوثائق .
@Test @AnyStubId(requestMode = RequestMode.rmTrack) public void processDataTest3() { assertEquals(79, dataProcessor.processData()); assertEquals(79, dataProcessor.processData()); assertEquals(168, dataProcessor.processData()); assertEquals(79, dataProcessor.processData()); Base base = getStub(); assertEquals(4, base.times("GET")); }
في هذا الاختبار ، يظهرAnyStubId مع المعلمة requestMode الجديدة. انها تسمح لك لإدارة أذونات ملفات كعب الروتين. هناك جانبان للتحكم فيهما: البحث عن الملفات والإذن للاتصال بنظام خارجي.
RequestMode.rmTrack
بتعيين القواعد التالية: إذا تم إنشاء الملف للتو ، يتم إرسال جميع الطلبات إلى النظام الخارجي وتتم كتابتها إلى الملف مع الإجابات ، بغض النظر عما إذا كان هناك طلب مماثل في الملف (يُسمح بالنسخ المكررة في الملف). إذا كان ملف كعب الروتين موجودًا قبل إجراء الاختبارات ، فيُحظر تقديم الطلبات إلى النظام الخارجي. من المتوقع إجراء المكالمات بنفس التسلسل. إذا كان الطلب التالي لا يتطابق مع الطلب الموجود في الملف ، يتم طرح استثناء.
RequestMode.rmNew
يتم تنشيط هذا الوضع افتراضيًا. يتم البحث في كل طلب في ملف كعب الروتين. إذا تم العثور على طلب مطابقة - تتم استعادة النتيجة المقابلة من الملف ، يتم تأجيل الطلب إلى النظام الخارجي. إذا لم يتم العثور على الطلب ، يتم طلب النظام الخارجي ، يتم حفظ النتيجة في ملف. الطلبات المكررة في الملف - لا تحدث.
RequestMode.rmNone
يتم RequestMode.rmNone
كل طلب في ملف كعب روتين. إذا تم العثور على استعلام مطابق ، تتم استعادة نتائجه من الملف. إذا أنشأ الاختبار طلبًا غير موجود في الملف ، فسيتم طرح استثناء.
RequestMode.rmAll
قبل الطلب الأول ، يتم مسح ملف كعب الروتين. تتم كتابة جميع الطلبات إلى الملف (يُسمح بالنسخ المكررة في الملف). يمكنك استخدام هذا الوضع إذا كنت تريد مشاهدة عمل الاتصال.
RequestMode.rmPassThrough
جميع الطلبات يتم إرسالها مباشرة إلى النظام الخارجي ، متجاوزة كعب تنفيذ.
هذه التغييرات متاحة على جيثب.
ماذا بعد؟
رأينا كيف يحفظ anyStub الردود. إذا تم طرح استثناء عند الوصول إلى نظام خارجي ، فسوف يقوم anyStub بحفظه ، وسيتم تشغيله على الطلبات اللاحقة.
غالبًا ما يتم طرح استثناءات بواسطة فئات المستوى الأعلى ، بينما تتلقى فئات الاتصال استجابة صالحة (ربما مع رمز خطأ). في هذه الحالة ، يكون AnyStub مسؤولاً عن إعادة إنتاج الإجابة برمز الخطأ ، كما ستلقي الطبقات العليا استثناءات لاختباراتك.
إضافة ملفات كعب الروتين إلى مستودع التخزين.
لا تخف من حذف واستبدال ملفات كعب الروتين.
إدارة ملفات كعب الروتين بحكمة. يمكنك إعادة استخدام ملف واحد في عدة اختبارات أو تقديم ملف فردي لكل اختبار. اغتنم هذه الفرصة لاحتياجاتك. ولكن عادةً ما يكون استخدام ملف واحد مع أوضاع وصول مختلفة فكرة سيئة.
هذه هي جميع الميزات الرئيسية في anyStub.