عندما قابلت Kotlin DSL ، اعتقدت: شيء رائع ، إنه أمر مؤسف في تطوير المنتج ولن يكون مفيدًا. ومع ذلك ، كنت مخطئًا: لقد ساعدنا في إنشاء طريقة موجزة وأنيقة للغاية لكتابة اختبارات واجهة المستخدم الشاملة في Android.

حول الخدمة ، اختبار البيانات والسبب في أنها ليست بهذه البساطة
بادئ ذي بدء ، سياق قليل حول خدمتنا ، بحيث تفهم لماذا اتخذنا قرارات معينة.
نساعد الباحثين عن عمل وأصحاب العمل في العثور على بعضهم البعض:
- يقوم أصحاب العمل بتسجيل شركاتهم ونشر الوظائف الشاغرة
- يبحث الباحثون عن عمل عن وظائف ، ويضيفونها إلى المفضلة ، والاشتراك في نتائج البحث ، وإنشاء السير الذاتية وإرسال الملاحظات
من أجل محاكاة سيناريوهات المستخدم الحقيقي والتأكد من أن التطبيق يعمل بشكل صحيح عليها ، نحتاج إلى إنشاء جميع بيانات الاختبار هذه على الخادم. ستقول: "إذن قم بإنشاء أصحاب العمل والباحثين عن عمل مقدمًا ، ثم اعمل معهم في الاختبارات". ولكن هناك بعض المشاكل:
- خلال الاختبارات نقوم بتغيير البيانات.
- الاختبارات تعمل بالتوازي.
اختبار البيئة والتجهيزات
اختبارات نهاية إلى نهاية تعمل على مقاعد الاختبار. لديهم تقريبا بيئة عسكرية ، ولكن لا توجد بيانات حقيقية. في هذا الصدد ، عند إضافة بيانات جديدة ، يحدث الفهرسة على الفور تقريبًا.
لإضافة بيانات إلى الحامل ، نستخدم طرق تثبيت خاصة. يضيفون البيانات مباشرة إلى قاعدة البيانات وفهرستها على الفور:
interface TestFixtureUserApi { @POST("fx/employer/create") fun createEmployerUser(@Body employer: TestEmployer): Call<TestEmployer> }
تتوفر تركيبات فقط من الشبكة المحلية وفقط لمواقف الاختبار. يتم استدعاء الأساليب من الاختبار مباشرة قبل بدء نشاط البدء.
DSL
لذلك وصلنا إلى العصير. كيف يتم تعيين البيانات للاختبار؟
initialisation{ applicant { resume { title = "Resume for similar Vacancy" isOptional = true resumeStatus = ResumeStatus.APPROVED } resume { title = "Some other Resume" } } employer { vacancy { title = "Resume for similar Vacancy" } vacancy { title = "Resume for similar Vacancy" description = "Working hard" } vacancy { title = "Resume for similar Vacancy" description = "Working very hard" } } }
في كتلة التهيئة ، نبدأ في إنشاء الكيانات اللازمة للاختبار: في المثال أعلاه ، أنشأنا مقدم طلب وظيفة واحد مع اثنين من السير الذاتية ، وكذلك صاحب العمل الذي قدم العديد من الشواغر.
للقضاء على الأخطاء المرتبطة بتقاطع بيانات الاختبار ، نقوم بإنشاء معرف فريد للاختبار ولكل كيان.
العلاقات بين الكيانات
ما هو القيد الرئيسي عند العمل مع DSL؟ بسبب تركيبتها الشجرية ، يصعب بناء الروابط بين فروع الشجرة المختلفة.
على سبيل المثال ، في طلبنا للمتقدمين ، يوجد قسم "الشواغر المناسبة للسير الذاتية". لكي تظهر الوظائف الشاغرة في هذه القائمة ، نحتاج إلى تعيينها بطريقة ترتبط باستئناف المستخدم الحالي.
initialisation { applicant { resume { title = "TEST_VACANCY_$uniqueTestId" } } employer { vacancy { title = "TEST_VACANCY_$uniqueTestId" } } }
يتم استخدام معرف اختبار فريد لهذا الغرض. وبالتالي ، عند العمل مع التطبيق ، يوصى الشواغر المحددة لهذا السيرة الذاتية. بالإضافة إلى ذلك ، من المهم الإشارة إلى أنه لن تظهر أي شواغر أخرى في هذه القائمة.
تهيئة البيانات من نفس النوع
ولكن ماذا لو كنت بحاجة إلى عمل الكثير من الوظائف الشاغرة؟ هل كل كتلة نسخة لذلك؟ بالطبع لا! نحن نصنع طريقة مع مجموعة من الوظائف الشاغرة ، والتي تشير إلى العدد المطلوب من الوظائف الشاغرة ومحول لتنويعها اعتمادا على المعرف الفريد.
initialisation { employer { vacancyBlock { size = 10 transformer = { it.also { vacancyDsl -> vacancyDsl.description = "Some description with text ${vacancyDsl.uniqueVacancyId}" } } } } }
في كتلة vacancyBlock ، نشير إلى عدد عمليات نسخ الوظائف الشاغرة التي نحتاج إلى إنشائها وكيفية تحويلها وفقًا للرقم التسلسلي.
العمل مع البيانات في الاختبار
أثناء الاختبار ، يصبح العمل مع البيانات بسيطًا جدًا. جميع البيانات التي أنشأناها متاحة لنا. في تطبيقنا ، يتم تخزينها في أغلفة خاصة للمجموعات. يمكن الحصول على البيانات من خلال الرقم التسلسلي للوظيفة (الشواغر [0]) ، ومن خلال العلامة التي يمكن تعيينها في dsl (الشواغر ["شاغراتي"]) ، والاختصارات (vacancies.first ())
TaggedItemContainer class TaggedItemContainer<T>( private val items: MutableList<TaggedItem<T>> ) { operator fun get(index: Int): T { return items[index].data } operator fun get(tag: String): T { return items.first { it.tag == tag }.data } operator fun plusAssign(item: TaggedItem<T>) { items += item } fun forEach(action: (T) -> Unit) { for (item in items) action.invoke(item.data) } fun first(): T { return items[0].data } fun second(): T { return items[1].data } fun third(): T { return items[2].data } fun last(): T { return items[items.size - 1].data } }
في حوالي 100٪ من الحالات ، عند كتابة الاختبارات ، نستخدم الطريقتين الأولى () والثانية () ، ويتم الاحتفاظ بالباقي من أجل المرونة. فيما يلي مثال للاختبار مع التهيئة والخطوات في Kakao
initialisation { applicant { resume { title = "TEST_VACANCY_$uniqueTestId" } } }.run { mainScreen { positionField { click() } jobPositionScreen { positionEntry(vacancies.first().title) } searchButton { click() } } }
ما لا يصلح في DSL
هل يمكن أن تتوافق جميع البيانات مع DSL؟ كان هدفنا الحفاظ على DSL موجزة وبسيطة قدر الإمكان. في تنفيذنا ، نظرًا لحقيقة أن ترتيب الوظائف للمتقدمين وأرباب العمل ليس مهمًا ، فإنه لا يمكن أن يتناسب مع علاقتهم - الردود.
يتم تنفيذ الاستجابات بالفعل في الكتلة التالية من خلال عمليات على الكيانات التي تم إنشاؤها بالفعل على الخادم.
تنفيذ DSL
كما فهمت من المقالة ، فإن خوارزمية تحديد بيانات الاختبار وإجراء الاختبار هي كما يلي:
- يتم تحليل جزء من DSL في التهيئة ؛
- بناءً على القيم التي تم الحصول عليها ، يتم إنشاء بيانات الاختبار على الخادم ؛
- يتم تنفيذ كتلة التحويل الاختيارية ، حيث يمكنك تعيين الاستجابات ؛
- يتم إجراء اختبار باستخدام مجموعة بيانات نهائية بالفعل.
تحليل البيانات من كتلة التهيئة
أي نوع من السحر يحدث هناك؟ ضع في اعتبارك كيف يتم إنشاء عنصر TestCaseDsl ذي المستوى الأعلى:
@TestCaseDslMarker class TestCaseDsl { val applicants = mutableListOf<ApplicantDsl>() val employers = mutableListOf<EmployerDsl>() val uniqueTestId = CommonUtils.unique fun applicant(block: ApplicantDsl.() -> Unit = {}) { val applicantDsl = ApplicantDsl( uniqueTestId, uniqueApplicantId = CommonUtils.unique applicantDsl.block() applicants += applicantDsl } fun employer(block: EmployerDsl.() -> Unit = {}) { val employerDsl = EmployerDsl( uniqueTestId = uniqueTestId, uniqueEmployerId = CommonUtils.unique employerDsl.block() employers += employerDsl } }
في طريقة مقدم الطلب ، نقوم بإنشاء ApplicantDsl.
ApplicantDsl @TestCaseDslMarker class ApplicantDsl( val uniqueTestId: String, val uniqueApplicantId: String, var tag: String? = null, var login: String? = null, var password: String? = null, var firstName: String? = null, var middleName: String? = null, var lastName: String? = null, var email: String? = null, var siteId: Int? = null, var areaId: Int? = null, var resumeViewLimit: Int? = null, var isMailingSubscription: Boolean? = null ) { val resumes = mutableListOf<ResumeDsl>() fun resume(block: ResumeDsl.() -> Unit = {}) { val resumeDslBuilder = ResumeDsl( uniqueTestId = uniqueTestId, uniqueApplicantId = uniqueApplicantId, uniqueResumeId = CommonUtils.unique ) resumeDslBuilder.apply(block) this.resumes += resumeDslBuilder } }
ثم نقوم بإجراء عمليات عليه من كتلة الحظر: ApplicantDsl. () -> الوحدة. هذا هو التصميم الذي يسمح لنا بالعمل بسهولة مع حقول ApplicantDsl في DSL لدينا.
يرجى ملاحظة أن uniqueTestId و uniqueApplicantId (المعرفات الفريدة لربط الكيانات فيما بينها) في وقت تنفيذ الكتلة قد تم بالفعل ضبطها ويمكننا الوصول إليها.
تحتوي كتلة التهيئة داخليًا على بنية مشابهة:
fun initialisation(block: TestCaseDsl.() -> Unit): Initialisation { val testCaseDsl = TestCaseDsl().apply(block) val testCase = TestCaseCreator.create(testCaseDsl) return Initialisation(testCase) }
ننشئ اختبارًا ، ونطبق إجراءات الحظر عليه ، ثم نستخدم TestCaseCreator لإنشاء بيانات على الخادم ووضعها في مجموعات. وظيفة TestCaseCreator.create () بسيطة للغاية - نحن نكرر البيانات وننشئها على الخادم.
مطبات والأفكار
بعض الاختبارات متشابهة جدًا وتختلف فقط في بيانات الإدخال وطرق التحكم في شاشات العرض (على سبيل المثال ، عندما تتم الإشارة إلى عملات مختلفة في الوظيفة الشاغرة).
في حالتنا ، كان هناك عدد قليل من هذه الاختبارات ، وقررنا عدم فوضى DSL مع بناء جملة خاص
في الأيام التي سبقت DSL ، قمنا بفهرسة البيانات لفترة طويلة ، ولتوفير الوقت قمنا بالكثير من الاختبارات في فصل واحد وقمنا بإنشاء جميع البيانات في كتلة ثابتة.
لا تفعل هذا - سيجعل من المستحيل بالنسبة لك إعادة تشغيل الاختبار الساقط. الحقيقة هي أنه خلال إطلاق الاختبار الساقط ، يمكننا تغيير البيانات الأولية على الخادم. على سبيل المثال ، يمكننا إضافة وظيفة شاغرة لمفضلتك. بعد ذلك ، عند إعادة تشغيل الاختبار ، يؤدي النقر فوق علامة النجمة على العكس إلى إزالة الوظيفة الشاغرة من قائمة المفضلة ، وهذا سلوك لا نتوقعه.
النتائج
هذه الطريقة لتحديد بيانات الاختبار تبسيط العمل إلى حد كبير مع الاختبارات:
عند كتابة الاختبارات ، لن تحتاج إلى التفكير فيما إذا كان هناك خادم وبأي ترتيب تحتاج إلى تهيئة البيانات ؛
جميع الكيانات التي يمكن تعيينها على الخادم تظهر بسهولة في تلميحات IDE ؛
هناك طريقة واحدة لتهيئة البيانات والتواصل معها.
المواد ذات الصلة
إذا كنت مهتمًا بأسلوبنا في اختبار واجهة المستخدم ، فقبل أن تبدأ ، أقترح عليك أن تتعرف على المواد التالية:
ما التالي
هذا المقال هو الأول من سلسلة حول الأدوات والأطر عالية المستوى لكتابة ودعم اختبارات واجهة المستخدم في Android. مع توفر أجزاء جديدة ، سأربطها بهذا المقال.