إنشاء اختبارات الوحدات المحلية

يتم إجراء الاختبار المحلي مباشرةً على محطة العمل الخاصة بك، بدلاً من جهاز Android أو برنامج محاكاة. وبالتالي، فإنه يستخدم جهاز Java الافتراضي (JVM) المحلي، بدلاً من جهاز Android لإجراء الاختبارات. تتيح لك الاختبارات المحلية تقييم منطق تطبيقك بسرعة أكبر. ومع ذلك، يؤدي عدم القدرة على التفاعل مع إطار عمل Android إلى فرض قيود على أنواع الاختبارات التي يمكنك إجراؤها.

يتحقّق اختبار unit من سلوك قسم صغير من الرمز، وهو الوحدة التي تخضع للاختبار. ويقوم بذلك عن طريق تنفيذ هذا التعليمات البرمجية والتحقق من النتيجة.

عادةً ما تكون اختبارات الوحدات بسيطة، لكن إعدادها قد يمثل مشكلة عندما لا يتم تصميم الوحدة قيد الاختبار مع وضع قابلية الاختبار في الاعتبار:

  • يجب أن يكون الرمز الذي تريد التحقّق منه قابلاً للوصول من خلال الاختبار. على سبيل المثال، لا يمكنك اختبار طريقة خاصة مباشرةً. وبدلاً من ذلك، يمكنك اختبار الفصل باستخدام واجهات برمجة التطبيقات العامة الخاصة بها.
  • من أجل إجراء اختبارات الوحدة في وضع عزل، يجب استبدال تبعيات الوحدة الخاضعة للاختبارات بمكونات يمكنك التحكم فيها، مثل العناصر المزيّفة أو غير ذلك من مضاعفات الاختبارات. يمثل هذا مشكلة خاصة إذا كانت التعليمات البرمجية تعتمد على إطار عمل Android.

للتعرّف على الاستراتيجيات الشائعة لاختبار الوحدات في نظام Android، يُرجى قراءة ما يجب اختباره.

الموقع الجغرافي للاختبارات المحلية

يتم تلقائيًا وضع الملفات المصدر لاختبارات الوحدات المحلية في module-name/src/test/. يتوفر هذا الدليل مسبقًا عند إنشاء مشروع جديد باستخدام استوديو Android.

إضافة تبعيات الاختبار

يجب أيضًا إعداد تبعيات الاختبار لمشروعك من أجل استخدام واجهات برمجة التطبيقات العادية التي يوفّرها إطار عمل الاختبار JUnit.

لإجراء ذلك، افتح ملف build.gradle في الوحدة الخاصة بتطبيقك وحدِّد المكتبات التالية كتبعيات. استخدِم الدالة testImplementation للإشارة إلى أنّها تنطبق على مجموعة مصدر الاختبار المحلي وليس على التطبيق:

dependencies {
  // Required -- JUnit 4 framework
  testImplementation "junit:junit:$jUnitVersion"
  // Optional -- Robolectric environment
  testImplementation "androidx.test:core:$androidXTestVersion"
  // Optional -- Mockito framework
  testImplementation "org.mockito:mockito-core:$mockitoVersion"
  // Optional -- mockito-kotlin
  testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
  // Optional -- Mockk framework
  testImplementation "io.mockk:mockk:$mockkVersion"
}

إنشاء فئة لاختبار الوحدة المحلية

يمكنك كتابة فئة اختبار الوحدة المحلية كفئة اختبار JUnit 4.

لإجراء ذلك، أنشِئ فئة تحتوي على طريقة اختبار واحدة أو أكثر، تكون عادةً في module-name/src/test/. تبدأ طريقة الاختبار بالتعليق التوضيحي @Test وتحتوي على الرمز للتمرين والتحقق من جانب واحد من المكون الذي تريد اختباره.

يوضح المثال التالي كيفية تطبيق فئة اختبار الوحدات المحلية. تحاول طريقة الاختبار emailValidator_correctEmailSimple_returnsTrue()التحقق من isValidEmail()، وهي طريقة داخل التطبيق. ستعرض دالة الاختبار القيمة "true" إذا كانت isValidEmail() أيضًا تعرض "صحيح".

Kotlin


import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class EmailValidatorTest {
  @Test fun emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"))
  }

}

Java


import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

class EmailValidatorTest {
  @Test
  public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"));
  }
}

يجب عليك إنشاء اختبارات سهلة القراءة التي تقيّم ما إذا كانت المكونات في تطبيقك تعرض النتائج المتوقعة أم لا. ننصحك باستخدام مكتبة تأكيدات مثل junit.Assert أو Hamcrest أو Truth. المقتطف أعلاه هو مثال على كيفية استخدام junit.Assert.

مكتبة Android قابلة للمحاكاة

عند تنفيذ اختبارات الوحدات المحلية، يتضمّن المكوِّن الإضافي Android Gradle مكتبة تحتوي على جميع واجهات برمجة التطبيقات لإطار عمل Android، تمامًا مع الإصدار المستخدَم في مشروعك. تحتوي المكتبة على جميع الطرق وفئات واجهات برمجة التطبيقات العامة هذه، ولكن تمت إزالة التعليمة البرمجية داخل الطرق. إذا تم الوصول إلى أي من الطرق، فإن الاختبار يقدم استثناءً.

يتيح ذلك إنشاء اختبارات محلية عند الإشارة إلى الصفوف في إطار عمل Android مثل Context. والأهم من ذلك، أنه يسمح لك باستخدام إطار عمل محاكاة مع فصول Android.

محاكاة تبعيات Android

المشكلة النموذجية هي إيجاد أن إحدى الفئات تستخدم مورد سلسلة. يمكنك الحصول على موارد السلاسل من خلال استدعاء طريقة getString() في الفئة Context. ومع ذلك، لا يمكن لاختبار محلي استخدام Context أو أي من طرقه لأنها تنتمي إلى إطار عمل Android. من الناحية المثالية، ستتم إزالة المكالمة الواردة إلى getString() من الصف، ولكن هذا ليس عمليًا دائمًا. الحل هو إنشاء نموذج تجريبي أو كعب من Context يعرض دائمًا القيمة نفسها عند استدعاء طريقة getString() الخاصة به.

باستخدام مكتبة Android Mockito وأُطر عمل نماذج تجريبية، مثل Mockito أو MockK، يمكنك برمجة سلوك النماذج التجريبية لصفوف Android في اختبارات الوحدة.

لإضافة كائن وهمي إلى اختبار الوحدة المحلية باستخدام Mockito، اتبع نموذج البرمجة هذا:

  1. ضمِّن اعتمادية مكتبة Mockito في ملف build.gradle، على النحو الموضَّح في إعداد بيئة الاختبار.
  2. في بداية تعريف فئة اختبار الوحدة، أضِف تعليق @RunWith(MockitoJUnitRunner.class) التوضيحي. ويخبر هذا التعليق التوضيحي تشغيل اختبار Muockito بالتحقق من صحة استخدامك لإطار العمل ويبسط عملية تهيئة الكائنات التجريبية.
  3. لإنشاء كائن وهمي لتبعية Android، أضِف التعليق التوضيحي @Mock قبل تعريف الحقل.
  4. لكبح سلوك التبعية، يمكنك تحديد شرط وقيمة عرض عند استيفاء هذا الشرط باستخدام الطريقتين when() وthenReturn().

يوضّح المثال التالي كيفية إنشاء اختبار وحدة يستخدم نموذجًا تجريبيًا من عنصر Context في لغة Kotlin تم إنشاؤها باستخدام Mockito-Kotlin.

import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

private const val FAKE_STRING = "HELLO WORLD"

@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {

  @Mock
  private lateinit var mockContext: Context

  @Test
  fun readStringFromContext_LocalizedString() {
    // Given a mocked Context injected into the object under test...
    val mockContext = mock<Context> {
        on { getString(R.string.name_label) } doReturn FAKE_STRING
    }

    val myObjectUnderTest = ClassUnderTest(mockContext)

    // ...when the string is returned from the object under test...
    val result: String = myObjectUnderTest.getName()

    // ...then the result should be the expected one.
    assertEquals(result, FAKE_STRING)
  }
}

لمعرفة المزيد من المعلومات حول استخدام إطار عمل Mockito، يُرجى الاطّلاع على مرجع واجهة برمجة التطبيقاتMockito API والفئة SharedPreferencesHelperTest في نموذج الرمز. ويمكنك أيضًا تجربة الدرس التطبيقي حول ترميز اختبار Android.

خطأ: "طريقة ... لم يتم تقليدها"

تعرض مكتبة Android Mockable استثناءً إذا حاولت الوصول إلى أي من طرقها باستخدام رسالة Error: "Method ... not mocked.

إذا كانت الاستثناءات المطروحة تمثّل مشكلة في اختباراتك، يمكنك تغيير السلوك بحيث تعرض الطرق بدلاً من ذلك قيمة فارغة أو صفرًا، اعتمادًا على نوع الإرجاع. لإجراء ذلك، أضِف الإعداد التالي في ملف build.gradle ذي المستوى الأعلى لمشروعك في Groovy:

android {
  ...
  testOptions {
    unitTests.returnDefaultValues = true
  }