مبانی تست زنی

این کد لبه بخشی از دوره Advanced Android in Kotlin است. اگر از طریق کدها به ترتیب کار کنید، بیشترین ارزش را از این دوره خواهید گرفت، اما اجباری نیست. همه کدهای دوره در صفحه فرود Android Advanced in Kotlin Codelabs فهرست شده اند.

مقدمه

هنگامی که اولین ویژگی اولین برنامه خود را پیاده سازی کردید، احتمالاً کد را اجرا کرده اید تا تأیید کنید که مطابق انتظار کار می کند. شما یک تست انجام دادید، البته یک تست دستی . همانطور که به افزودن و به‌روزرسانی ویژگی‌ها ادامه می‌دهید، احتمالاً به اجرای کد و تأیید کارکرد آن نیز ادامه داده‌اید. اما هر بار انجام این کار به صورت دستی خسته کننده است، مستعد اشتباه است و مقیاس نمی شود.

کامپیوترها در مقیاس بندی و اتوماسیون عالی هستند! بنابراین، توسعه‌دهندگان شرکت‌های بزرگ و کوچک ، تست‌های خودکار را می‌نویسند، این تست‌ها آزمایش‌هایی هستند که توسط نرم‌افزار اجرا می‌شوند و نیازی به اجرای دستی برنامه برای تأیید کارکرد کد ندارند.

چیزی که در این سری از نرم افزارهای کد یاد خواهید گرفت، نحوه ایجاد مجموعه ای از تست ها (معروف به مجموعه تست) برای یک برنامه واقعی است.

این اولین کد لبه مبانی تست در اندروید را پوشش می دهد، شما اولین تست های خود را می نویسید و یاد می گیرید که چگونه LiveData و ViewModel s را آزمایش کنید.

آنچه از قبل باید بدانید

باید با:

چیزی که یاد خواهید گرفت

با موضوعات زیر آشنا خواهید شد:

  • نحوه نوشتن و اجرای تست های واحد در اندروید
  • نحوه استفاده از Test Driven Development
  • نحوه انتخاب تست های ابزاردار و تست های محلی

شما با کتابخانه ها و مفاهیم کد زیر آشنا خواهید شد:

کاری که خواهی کرد

  • تست های محلی و ابزاری را در اندروید تنظیم، اجرا و تفسیر کنید.
  • تست های واحد را در اندروید با استفاده از JUnit4 و Hamcrest بنویسید.
  • تست های ساده LiveData و ViewModel را بنویسید.

در این سری از کدها، شما با برنامه TO-DO Notes کار خواهید کرد. این برنامه به شما امکان می دهد وظایف را برای تکمیل بنویسید و آنها را در یک لیست نمایش دهید. سپس می‌توانید آن‌ها را به‌عنوان تکمیل‌شده یا خیر علامت‌گذاری کنید، فیلتر کنید یا حذف کنید.

این برنامه به زبان Kotlin نوشته شده است، دارای چندین صفحه نمایش است، از اجزای Jetpack استفاده می کند و معماری را از یک راهنما تا معماری برنامه دنبال می کند. با یادگیری نحوه آزمایش این برنامه، می توانید برنامه هایی را که از کتابخانه ها و معماری مشابهی استفاده می کنند، آزمایش کنید.

برای شروع، کد را دانلود کنید:

زیپ را دانلود کنید

از طرف دیگر، می توانید مخزن Github را برای کد کلون کنید:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

در این کار، برنامه را اجرا کرده و پایه کد را کاوش خواهید کرد.

مرحله 1: برنامه نمونه را اجرا کنید

هنگامی که برنامه TO-DO را دانلود کردید، آن را در Android Studio باز کرده و اجرا کنید. باید کامپایل شود. با انجام موارد زیر برنامه را کاوش کنید:

  • با دکمه اکشن شناور پلاس یک کار جدید ایجاد کنید. ابتدا عنوانی را وارد کنید، سپس اطلاعات تکمیلی مربوط به کار را وارد کنید. آن را با چک سبز FAB ذخیره کنید.
  • در لیست کارها، روی عنوان کاری که به تازگی تکمیل کردید کلیک کنید و به صفحه جزئیات آن کار نگاه کنید تا بقیه توضیحات را ببینید.
  • در فهرست یا در صفحه جزئیات، کادر تأیید آن کار را علامت بزنید تا وضعیت آن را روی «تکمیل» تنظیم کنید.
  • به صفحه وظایف برگردید، منوی فیلتر را باز کنید و وظایف را بر اساس وضعیت فعال و تکمیل شده فیلتر کنید.
  • کشوی پیمایش را باز کنید و روی Statistics کلیک کنید.
  • به صفحه نمای کلی بازگشته و از منوی کشوی پیمایش، پاک کردن تکمیل شده را انتخاب کنید تا همه وظایف با وضعیت تکمیل شده حذف شوند.

مرحله 2: نمونه کد برنامه را کاوش کنید

برنامه TO-DO مبتنی بر نمونه آزمایشی و معماری محبوب طرح‌های معماری (با استفاده از نسخه معماری واکنشی نمونه) است. این برنامه از معماری یک راهنما به معماری برنامه پیروی می کند. از ViewModels با Fragments، Repository و Room استفاده می کند. اگر با هر یک از نمونه های زیر آشنا هستید، این برنامه معماری مشابهی دارد:

مهم‌تر است که معماری کلی برنامه را درک کنید تا اینکه درک عمیقی از منطق در هر لایه داشته باشید.

در اینجا خلاصه بسته هایی است که خواهید یافت:

بسته: com.example.android.architecture.blueprints.todoapp

.addedittask

صفحه افزودن یا ویرایش یک کار: کد لایه رابط کاربری برای افزودن یا ویرایش یک کار.

.data

لایه داده: این لایه با لایه داده وظایف سروکار دارد. این شامل پایگاه داده، شبکه و کد مخزن است.

.statistics

صفحه آمار: کد لایه رابط کاربری برای صفحه آمار.

.taskdetail

صفحه جزئیات کار: کد لایه رابط کاربری برای یک کار واحد.

.tasks

صفحه وظایف: کد لایه رابط کاربری برای لیست تمام وظایف.

.util

کلاس‌های کاربردی: کلاس‌های مشترک مورد استفاده در بخش‌های مختلف برنامه، به‌عنوان مثال برای طرح‌بندی بازخوانی کشیدن انگشت که در چندین صفحه استفاده می‌شود.

لایه داده (.data)

این برنامه شامل یک لایه شبکه شبیه سازی شده، در بسته راه دور ، و یک لایه پایگاه داده، در بسته محلی است. برای سادگی، در این پروژه لایه شبکه تنها با یک HashMap با تاخیر شبیه سازی می شود تا درخواست های واقعی شبکه.

DefaultTasksRepository بین لایه شبکه و لایه پایگاه داده مختصات یا واسطه می شود و همان چیزی است که داده ها را به لایه UI برمی گرداند.

لایه رابط کاربری (.addedittask، .statistics،.taskdetail، .tasks)

هر یک از بسته های لایه UI شامل یک قطعه و یک مدل view به همراه هر کلاس دیگری است که برای UI مورد نیاز است (مانند یک آداپتور برای لیست وظایف). TaskActivity اکتیویتی است که شامل تمام قطعات است.

ناوبری

ناوبری برای برنامه توسط مؤلفه ناوبری کنترل می شود. در فایل nav_graph.xml تعریف شده است. ناوبری در مدل های view با استفاده از کلاس Event فعال می شود. مدل‌های view نیز تعیین می‌کنند که چه آرگومان‌هایی باید منتقل شوند. قطعات Event را مشاهده می‌کنند و پیمایش واقعی بین صفحه‌ها را انجام می‌دهند.

در این کار، اولین تست های خود را اجرا می کنید.

  1. در Android Studio، پنجره Project را باز کنید و این سه پوشه را پیدا کنید:
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

این پوشه ها به عنوان مجموعه منبع شناخته می شوند. مجموعه‌های منبع، پوشه‌هایی هستند که حاوی کد منبع برنامه شما هستند. مجموعه های منبع، که سبز رنگ هستند ( آندرویدتست و تست ) حاوی تست های شما هستند. هنگامی که یک پروژه اندروید جدید ایجاد می کنید، به طور پیش فرض سه مجموعه منبع زیر را دریافت می کنید. آنها عبارتند از:

  • main : حاوی کد برنامه شما است. این کد در بین تمام نسخه‌های مختلف برنامه‌ای که می‌توانید بسازید به اشتراک گذاشته شده است (معروف به انواع ساخت )
  • androidTest : شامل تست هایی است که به عنوان تست های ابزاری شناخته می شوند.
  • test : شامل تست هایی است که به عنوان تست های محلی شناخته می شوند.

تفاوت بین تست های محلی و تست های ابزار دقیق در نحوه اجرا آنهاست.

تست های محلی ( مجموعه منبع test )

این تست ها به صورت محلی روی JVM ماشین توسعه شما اجرا می شوند و نیازی به شبیه ساز یا دستگاه فیزیکی ندارند. به همین دلیل، آنها سریع می دوند، اما وفاداری آنها کمتر است، به این معنی که کمتر مانند دنیای واقعی عمل می کنند.

در Android Studio تست های محلی با یک نماد مثلث سبز و قرمز نشان داده می شوند.

تست های ابزاری ( مجموعه منبع androidTest )

این تست‌ها بر روی دستگاه‌های اندرویدی واقعی یا شبیه‌سازی شده اجرا می‌شوند، بنابراین آنچه را که در دنیای واقعی اتفاق می‌افتد منعکس می‌کنند، اما همچنین بسیار کندتر هستند.

در اندروید استودیو، تست‌های ابزاردار با یک اندروید با نماد مثلث سبز و قرمز نشان داده می‌شوند.

مرحله 1: یک تست محلی را اجرا کنید

  1. پوشه test را باز کنید تا فایل ExampleUnitTest.kt را پیدا کنید.
  2. روی آن کلیک راست کرده و Run ExampleUnitTest را انتخاب کنید.

شما باید خروجی زیر را در پنجره Run در پایین صفحه مشاهده کنید:

  1. به علامت‌های سبز رنگ توجه کنید و نتایج آزمایش را گسترش دهید تا تأیید کنید که آزمونی به نام addition_isCorrect رد شده است. خوب است بدانید که اضافه کردن همانطور که انتظار می رود کار می کند!

مرحله 2: آزمون را با شکست مواجه کنید

در زیر آزمونی است که شما انجام دادید.

ExampleUnitTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

توجه کنید که تست ها

  • یک کلاس در یکی از مجموعه های منبع تست هستند.
  • شامل توابعی است که با حاشیه نویسی @Test شروع می شود (هر تابع یک تست واحد است).
  • u8 معمولاً حاوی عبارات ادعایی است.

اندروید از کتابخانه آزمایشی JUnit برای تست استفاده می کند (در این نرم افزار JUnit4). هر دو، ادعاها و حاشیه نویسی @Test از JUnit می آیند.

یک ادعا هسته اصلی آزمون شما است. این یک بیانیه کد است که بررسی می کند کد یا برنامه شما مطابق انتظار عمل کرده است. در این مورد، ادعا assertEquals(4, 2 + 2) است که بررسی می کند که 4 برابر با 2 + 2 است.

برای اینکه ببینید یک آزمون ناموفق چگونه به نظر می رسد، یک ادعا اضافه کنید که به راحتی می توانید ببینید که باید شکست بخورد. بررسی می کند که 3 برابر با 1+1 باشد.

  1. assertEquals(3, 1 + 1) را به آزمون addition_isCorrect اضافه کنید.

ExampleUnitTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. تست را اجرا کنید.
  1. در نتایج آزمون، به یک X در کنار آزمون توجه کنید.

  1. همچنین توجه کنید:
  • یک ادعای ناموفق در کل آزمون مردود است.
  • مقدار مورد انتظار (3) در مقابل مقداری که واقعاً محاسبه شده است (2) به شما گفته می شود.
  • شما به خط ادعای شکست خورده هدایت می شوید (ExampleUnitTest.kt:16) .

مرحله 3: یک تست ابزاری را اجرا کنید

تست‌های ابزاری در مجموعه منبع androidTest هستند.

  1. مجموعه منبع androidTest را باز کنید.
  2. تستی به نام ExampleInstrumentedTest را اجرا کنید.

ExampleInstrumentedTest

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

برخلاف آزمایش محلی، این آزمایش روی دستگاهی اجرا می‌شود (در مثال زیر یک تلفن شبیه‌سازی‌شده Pixel 2):

اگر دستگاهی متصل دارید یا شبیه ساز در حال اجرا است، باید اجرای آزمایشی را روی شبیه ساز ببینید.

در این کار، تست‌هایی را برای getActiveAndCompleteStats می‌نویسید، که درصد آمار کار فعال و کامل را برای برنامه شما محاسبه می‌کند. می توانید این اعداد را در صفحه آمار اپلیکیشن مشاهده کنید.

مرحله 1: یک کلاس آزمایشی ایجاد کنید

  1. در مجموعه منبع main ، در todoapp.statistics ، StatisticsUtils.kt باز کنید.
  2. تابع getActiveAndCompletedStats پیدا کنید.

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

تابع getActiveAndCompletedStats لیستی از وظایف را می پذیرد و یک StatsResult برمی گرداند. StatsResult یک کلاس داده است که شامل دو عدد است، درصد کارهای تکمیل شده و درصد فعال .

اندروید استودیو ابزارهایی برای تولید خرده آزمایشی به شما می دهد و به شما کمک می کند تا تست های این تابع را پیاده سازی کنید.

  1. روی getActiveAndCompletedStats کلیک راست کرده و Generate > Test را انتخاب کنید.

گفتگوی Create Test باز می شود:

  1. نام کلاس: را به StatisticsUtilsTest تغییر دهید (به جای StatisticsUtilsKtTest ؛ نداشتن KT در نام کلاس تست کمی بهتر است).
  2. بقیه موارد پیش فرض را حفظ کنید. JUnit 4 کتابخانه آزمایشی مناسبی است. بسته مقصد درست است (موقعیت کلاس StatisticsUtils را منعکس می‌کند) و نیازی به علامت زدن هیچ یک از کادرها نیست (این فقط کد اضافی ایجاد می‌کند، اما شما تست خود را از ابتدا می‌نویسید).
  3. OK را فشار دهید

گفتگوی Choose Destination Directory باز می شود:

شما یک تست محلی انجام می دهید زیرا تابع شما محاسبات ریاضی را انجام می دهد و شامل کد خاصی برای اندروید نمی شود. بنابراین، نیازی به اجرای آن بر روی یک دستگاه واقعی یا شبیه سازی شده نیست.

  1. دایرکتوری test (نه androidTest ) را انتخاب کنید زیرا در حال نوشتن تست های محلی هستید.
  2. روی OK کلیک کنید.
  3. به کلاس StatisticsUtilsTest ایجاد شده در test/statistics/ توجه کنید.

مرحله 2: اولین تابع تست خود را بنویسید

شما یک تست می نویسید که بررسی می کند:

  • اگر هیچ کار تکمیل شده و یک کار فعال وجود ندارد،
  • که درصد تست های فعال 100% است
  • و درصد کارهای انجام شده 0 درصد است.
  1. StatisticsUtilsTest باز کنید.
  2. تابعی به نام getActiveAndCompletedStats_noCompleted_returnsHundredZero ایجاد کنید.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. حاشیه‌نویسی @Test را در بالای نام تابع اضافه کنید تا نشان دهید که یک آزمایش است.
  2. فهرستی از وظایف ایجاد کنید.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. با این کارها با getActiveAndCompletedStats تماس بگیرید.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. با استفاده از ادعاها بررسی کنید که result همان چیزی است که انتظار داشتید.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

در اینجا کد کامل است.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. تست را اجرا کنید (راست کلیک کنید StatisticsUtilsTest و Run را انتخاب کنید).

باید بگذرد:

مرحله 3: وابستگی Hamcrest را اضافه کنید

از آنجایی که تست های شما به عنوان مستندی از آنچه کد شما انجام می دهد عمل می کند، زمانی که قابل خواندن توسط انسان باشد بسیار خوب است. دو ادعای زیر را با هم مقایسه کنید:

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

ادعای دوم بسیار بیشتر شبیه یک جمله انسانی است. این با استفاده از یک چارچوب ادعایی به نام Hamcrest نوشته شده است. یکی دیگر از ابزارهای خوب برای نوشتن ادعاهای خواندنی، کتابخانه حقیقت است. شما از Hamcrest در این کد لبه برای نوشتن ادعاها استفاده خواهید کرد.

  1. build.grade (Module: app) را باز کنید و وابستگی زیر را اضافه کنید.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

معمولاً هنگام اضافه کردن یک وابستگی از implementation استفاده می کنید، اما در اینجا از testImplementation استفاده می کنید. هنگامی که آماده به اشتراک گذاری برنامه خود با جهان هستید، بهتر است اندازه APK خود را با هیچ یک از کدهای آزمایشی یا وابستگی های برنامه خود پر نکنید. با استفاده از تنظیمات gradle می توانید تعیین کنید که آیا کتابخانه باید در کد اصلی یا آزمایشی گنجانده شود. رایج ترین تنظیمات عبارتند از:

  • implementation - وابستگی در همه مجموعه های منبع، از جمله مجموعه های منبع آزمایشی موجود است.
  • testImplementation - وابستگی فقط در مجموعه منبع آزمایشی موجود است.
  • androidTestImplementation — این وابستگی فقط در مجموعه منبع androidTest موجود است.

از کدام پیکربندی استفاده می‌کنید، محل استفاده از وابستگی را مشخص می‌کند. اگر بنویسید:

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

این بدان معنی است که Hamcrest فقط در مجموعه منبع آزمایشی در دسترس خواهد بود. همچنین تضمین می کند که Hamcrest در برنامه نهایی شما قرار نخواهد گرفت.

مرحله 4: از Hamcrest برای نوشتن اظهارات استفاده کنید

  1. برای استفاده assertThat Hamcrest به جای assertEquals ، تست getActiveAndCompletedStats_noCompleted_returnsHundredZero() را به روز کنید.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

توجه داشته باشید که در صورت درخواست می توانید از import import org.hamcrest.Matchers.`is` استفاده کنید.

تست نهایی شبیه کد زیر خواهد بود.

StatisticsUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

    }
}
  1. تست به روز شده خود را اجرا کنید تا مطمئن شوید هنوز کار می کند!

این کد لبه همه چیزهای Hamcrest را به شما آموزش نمی دهد، بنابراین اگر می خواهید بیشتر بدانید، آموزش رسمی را ببینید.

این یک کار اختیاری برای تمرین است.

در این کار، تست های بیشتری را با استفاده از JUnit و Hamcrest می نویسید. همچنین با استفاده از یک استراتژی برگرفته از تمرین برنامه توسعه تست محور، تست ها را می نویسید. Test Driven Development یا TDD یک مکتب تفکر برنامه نویسی است که می گوید به جای اینکه ابتدا کد ویژگی خود را بنویسید، ابتدا تست های خود را بنویسید. سپس کد ویژگی خود را با هدف قبولی در آزمون های خود می نویسید.

مرحله 1. تست ها را بنویسید

تست هایی را برای زمانی که لیست وظایف معمولی دارید بنویسید:

  1. اگر یک کار تکمیل شده وجود دارد و هیچ کار فعالی وجود ندارد، درصد activeTasks باید 0f و درصد کارهای تکمیل شده باید 100f باشد.
  2. اگر دو کار تکمیل شده و سه کار فعال وجود دارد، درصد تکمیل شده باید 40f و درصد فعال باید 60f باشد.

مرحله 2. یک تست برای یک اشکال بنویسید

کد getActiveAndCompletedStats همانطور که نوشته شده دارای یک اشکال است. توجه داشته باشید که چگونه آن را به درستی مدیریت نمی کند که چه اتفاقی می افتد اگر لیست خالی یا خالی باشد. در هر دو مورد، هر دو درصد باید صفر باشند.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

برای رفع کد و نوشتن تست ها، از توسعه مبتنی بر تست استفاده خواهید کرد. توسعه تست محور این مراحل را دنبال می کند.

  1. تست را با استفاده از ساختار Given, When, Then و با نامی که از قرارداد پیروی می کند بنویسید.
  2. عدم موفقیت آزمون را تأیید کنید.
  3. حداقل کد را برای قبولی در آزمون بنویسید.
  4. برای همه تست ها تکرار کنید!

به جای اینکه با رفع اشکال شروع کنید، ابتدا با نوشتن تست ها شروع می کنید. سپس می‌توانید تأیید کنید که آزمایش‌هایی دارید که از شما در برابر معرفی مجدد تصادفی این اشکالات در آینده محافظت می‌کند.

  1. اگر یک لیست خالی وجود داشته باشد ( emptyList() )، هر دو درصد باید 0f باشند.
  2. اگر در بارگیری وظایف خطایی وجود داشته باشد، لیست null خواهد بود و هر دو درصد باید 0f باشند.
  3. تست های خود را اجرا کنید و تأیید کنید که آنها شکست خورده اند :

مرحله 3. رفع اشکال

اکنون که آزمایشات خود را دارید، باگ را برطرف کنید.

  1. رفع اشکال در getActiveAndCompletedStats با برگرداندن 0f در صورت null یا خالی بودن tasks :
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. تست های خود را دوباره اجرا کنید و تأیید کنید که همه تست ها اکنون قبول شده اند!

با پیروی از TDD و نوشتن تست‌ها در ابتدا، به این اطمینان کمک کرده‌اید که:

  • عملکرد جدید همیشه دارای تست های مرتبط است. بنابراین آزمایشات شما به عنوان مستندی از آنچه کد شما انجام می دهد عمل می کند.
  • آزمایش‌های شما نتایج صحیح را بررسی می‌کنند و در برابر اشکالاتی که قبلاً دیده‌اید محافظت می‌کنند.

راه حل: نوشتن تست های بیشتر

در اینجا تمام تست ها و کد ویژگی مربوطه آمده است.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

کار عالی با اصول رایتینگ و اجرای تست ها! در ادامه نحوه نوشتن تست های ViewModel و LiveData را یاد خواهید گرفت.

در بقیه بخش کد، یاد خواهید گرفت که چگونه برای دو کلاس اندروید تست بنویسید که در اکثر برنامه ها رایج است ViewModel و LiveData .

شما با نوشتن تست هایی برای TasksViewModel شروع می کنید.


شما قرار است روی تست هایی تمرکز کنید که تمام منطق خود را در مدل view دارند و به کد مخزن متکی نیستند. کد مخزن شامل کدهای ناهمزمان، پایگاه‌های داده و تماس‌های شبکه است که همگی پیچیدگی تست را اضافه می‌کنند. فعلاً از آن اجتناب می‌کنید و بر روی نوشتن تست‌هایی برای عملکرد ViewModel تمرکز می‌کنید که مستقیماً هیچ چیز را در مخزن آزمایش نمی‌کند.



تستی که می نویسید بررسی می کند که وقتی متد addNewTask را فراخوانی می کنید، Event برای باز کردن پنجره وظیفه جدید فعال شود. در اینجا کد برنامه ای است که می خواهید آزمایش کنید.

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

مرحله 1. یک کلاس TasksViewModelTest بسازید

با دنبال کردن همان مراحلی که برای StatisticsUtilTest انجام دادید، در این مرحله، یک فایل آزمایشی برای TasksViewModelTest ایجاد می‌کنید.

  1. کلاسی را که می خواهید آزمایش کنید، در بسته tasks ، TasksViewModel.
  2. در کد، روی نام کلاس TasksViewModel -> Generate -> Test کلیک راست کنید.

  1. در صفحه Create Test ، برای پذیرش روی OK کلیک کنید (نیازی به تغییر هیچ یک از تنظیمات پیش فرض نیست).
  2. در گفتگوی Choose Destination Directory ، دایرکتوری آزمایشی را انتخاب کنید.

مرحله 2. شروع به نوشتن تست ViewModel خود کنید

در این مرحله شما یک تست مدل view اضافه می‌کنید تا آزمایش کنید که وقتی متد addNewTask را فراخوانی می‌کنید، Event برای باز کردن پنجره وظیفه جدید فعال می‌شود.

  1. یک آزمایش جدید به نام addNewTask_setsNewTaskEvent ایجاد کنید.

TasksViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

در مورد زمینه برنامه چطور؟

هنگامی که نمونه ای از TasksViewModel برای آزمایش ایجاد می کنید، سازنده آن به یک Application Context نیاز دارد. اما در این تست، شما یک برنامه کامل با فعالیت‌ها و رابط کاربری و قطعات ایجاد نمی‌کنید، پس چگونه می‌توانید زمینه برنامه را بدست آورید؟

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

کتابخانه‌های تست AndroidX شامل کلاس‌ها و روش‌هایی هستند که نسخه‌هایی از مؤلفه‌هایی مانند برنامه‌ها و فعالیت‌ها را در اختیار شما قرار می‌دهند که برای آزمایش‌ها در نظر گرفته شده‌اند. هنگامی که یک آزمایش محلی دارید که در آن به کلاس های فریم ورک اندروید شبیه سازی شده نیاز دارید (مانند یک زمینه برنامه)، این مراحل را برای تنظیم صحیح AndroidX Test دنبال کنید:

  1. هسته تست AndroidX و وابستگی های ext را اضافه کنید
  2. وابستگی کتابخانه تست روبولکتریک را اضافه کنید
  3. کلاس را با اجرای تست AndroidJunit4 حاشیه نویسی کنید
  4. کد تست AndroidX را بنویسید

شما قرار است این مراحل را کامل کنید و سپس بفهمید که آنها با هم چه می کنند.

مرحله 3. وابستگی های gradle را اضافه کنید

  1. این وابستگی ها را در فایل build.gradle ماژول برنامه خود کپی کنید تا وابستگی های هسته اصلی AndroidX Test و ext و همچنین وابستگی تست Robolectric را اضافه کنید.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

مرحله 4. JUnit Test Runner را اضافه کنید

  1. @RunWith(AndroidJUnit4::class) بالای کلاس آزمایشی خود اضافه کنید.

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

مرحله 5. از AndroidX Test استفاده کنید

در این مرحله، می توانید از کتابخانه AndroidX Test استفاده کنید. این شامل روش ApplicationProvider.getApplicationContex t است که یک متن برنامه را دریافت می کند.

  1. یک TasksViewModel با استفاده از ApplicationProvider.getApplicationContext() از کتابخانه تست AndroidX ایجاد کنید.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. با addNewTask در tasksViewModel تماس بگیرید.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

در این مرحله آزمون شما باید شبیه کد زیر باشد.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. تست خود را اجرا کنید تا مطمئن شوید که کار می کند.

مفهوم: تست AndroidX چگونه کار می کند؟

AndroidX Test چیست؟

AndroidX Test مجموعه ای از کتابخانه ها برای آزمایش است. این شامل کلاس‌ها و روش‌هایی است که نسخه‌هایی از مؤلفه‌هایی مانند برنامه‌ها و فعالیت‌ها را به شما می‌دهند که برای آزمایش‌ها در نظر گرفته شده‌اند. به عنوان مثال، این کدی که نوشتید نمونه‌ای از یک تابع تست AndroidX برای دریافت زمینه برنامه است.

ApplicationProvider.getApplicationContext()

یکی از مزایای API های تست AndroidX این است که برای تست های محلی و تست های ابزاری ساخته شده اند. این خوب است زیرا:

  • شما می توانید همان تست را به عنوان یک تست محلی یا یک تست ابزاری اجرا کنید.
  • شما نیازی به یادگیری API های مختلف تست برای تست های محلی در مقابل تست های ابزاردار ندارید.

به عنوان مثال، چون کد خود را با استفاده از کتابخانه های تست AndroidX نوشته اید، می توانید کلاس TasksViewModelTest خود را از پوشه test به پوشه androidTest منتقل کنید و تست ها همچنان اجرا می شوند. getApplicationContext() بسته به اینکه به عنوان یک تست محلی یا ابزاری اجرا شود کمی متفاوت عمل می کند:

  • اگر این یک تست ابزاری باشد، زمانی که یک شبیه‌ساز را راه‌اندازی می‌کند یا به یک دستگاه واقعی متصل می‌شود، زمینه واقعی برنامه ارائه شده را دریافت می‌کند.
  • اگر تست محلی باشد، از یک محیط اندروید شبیه سازی شده استفاده می کند.

روبولکتریک چیست؟

محیط شبیه سازی شده اندرویدی که AndroidX Test برای تست های محلی استفاده می کند توسط Robolectric ارائه شده است. Robolectric کتابخانه ای است که یک محیط اندرویدی شبیه سازی شده برای آزمایش ایجاد می کند و سریعتر از راه اندازی شبیه ساز یا اجرا بر روی یک دستگاه اجرا می شود. بدون وابستگی Robolectric، این خطا را دریافت خواهید کرد:

@RunWith(AndroidJUnit4::class) چه کاری انجام می دهد؟

یک دونده آزمایشی یک جزء JUnit است که تست ها را اجرا می کند. بدون آزمون دونده، آزمون های شما اجرا نمی شوند. یک تست پیش فرض توسط JUnit ارائه شده است که به صورت خودکار دریافت می کنید. @RunWith آن دونده آزمایشی پیش‌فرض را تعویض می‌کند.

اجراکننده تست AndroidJUnit4 به AndroidX Test اجازه می‌دهد تا بسته به اینکه آیا تست‌های ابزاری یا محلی هستند، آزمایش شما را متفاوت اجرا کند.

مرحله 6. رفع هشدارهای روبولکتریک

وقتی کد را اجرا می کنید، توجه کنید که Robolectric استفاده شده است.

به دلیل AndroidX Test و اجرای تست AndroidJunit4، این کار بدون اینکه مستقیماً یک خط کد Robolectric بنویسید انجام می شود!

ممکن است متوجه دو هشدار شوید.

  • No such manifest file: ./AndroidManifest.xml
  • "WARN: Android SDK 29 requires Java 9..."

می‌توانید با به‌روزرسانی فایل gradle، هشدار No such manifest file: ./AndroidManifest.xml برطرف کنید.

  1. خط زیر را به فایل gradle خود اضافه کنید تا از مانیفست اندروید درست استفاده شود. گزینه includeAndroidResources به شما امکان می دهد در تست های واحد خود از جمله فایل AndroidManifest خود به منابع اندروید دسترسی داشته باشید.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

هشدار "WARN: Android SDK 29 requires Java 9..." پیچیده تر است. اجرای آزمایش‌ها در Android Q به جاوا 9 نیاز دارد . به جای تلاش برای پیکربندی Android Studio برای استفاده از جاوا 9، برای این کد لبه، هدف خود را نگه دارید و SDK را روی 28 کامپایل کنید.

به طور خلاصه:

  • آزمایش‌های مدل Pure View معمولاً می‌توانند در مجموعه منبع test انجام شوند زیرا کد آنها معمولاً به Android نیاز ندارد.
  • می‌توانید از کتابخانه تست AndroidX برای دریافت نسخه‌های آزمایشی مؤلفه‌هایی مانند برنامه‌ها و فعالیت‌ها استفاده کنید.
  • اگر نیاز به اجرای کدهای اندروید شبیه‌سازی شده در مجموعه منبع test خود دارید، می‌توانید وابستگی Robolectric و حاشیه‌نویسی @RunWith(AndroidJUnit4::class) را اضافه کنید.

تبریک می‌گوییم، شما از کتابخانه تست AndroidX و Robolectric برای اجرای آزمایش استفاده می‌کنید. تست شما تمام نشده است (شما هنوز یک بیانیه ادعایی ننوشته اید، فقط می گوید // TODO test LiveData ). در ادامه یاد خواهید گرفت که بیانیه‌های ادعایی را با LiveData بنویسید.

در این کار، یاد خواهید گرفت که چگونه ارزش LiveData به درستی اعلام کنید.

اینجا جایی است که بدون آزمایش مدل مشاهده addNewTask_setsNewTaskEvent متوقف شدید.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

برای آزمایش LiveData توصیه می شود دو کار را انجام دهید:

  1. InstantTaskExecutorRule استفاده کنید
  2. از مشاهده LiveData اطمینان حاصل کنید

مرحله 1. از InstantTaskExecutorRule استفاده کنید

InstantTaskExecutorRule یک قانون JUnit است. وقتی از آن با حاشیه‌نویسی @get:Rule استفاده می‌کنید، باعث می‌شود برخی از کدها در کلاس InstantTaskExecutorRule قبل و بعد از آزمایش‌ها اجرا شوند (برای دیدن کد دقیق، می‌توانید از میانبر صفحه کلید Command+B برای مشاهده فایل استفاده کنید).

این قانون تمام کارهای پس‌زمینه مربوط به اجزای معماری را در یک رشته اجرا می‌کند تا نتایج آزمایش به صورت همزمان و به ترتیب تکرار شونده اتفاق بیفتد. وقتی تست هایی می نویسید که شامل تست LiveData می شود، از این قانون استفاده کنید!

  1. وابستگی gradle را برای کتابخانه آزمایشی هسته Architecture Components (که حاوی این قانون است) اضافه کنید.

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. TasksViewModelTest.kt را باز کنید
  2. InstantTaskExecutorRule را داخل کلاس TasksViewModelTest اضافه کنید.

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

مرحله 2. کلاس LiveDataTestUtil.kt را اضافه کنید

قدم بعدی شما این است که مطمئن شوید LiveData که آزمایش می کنید رعایت می شود.

هنگامی که از LiveData استفاده می کنید، معمولاً یک فعالیت یا قطعه ( LifecycleOwner ) دارید که LiveData مشاهده می کند.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

این مشاهده مهم است. شما به ناظران فعال در LiveData نیاز دارید

  • هر رویداد onChanged فعال کنید.
  • باعث ایجاد هرگونه تغییر و تحول شود.

برای دریافت رفتار LiveData مورد انتظار برای LiveData مدل view خود، باید LiveData با LifecycleOwner مشاهده کنید.

این یک مشکل ایجاد می کند: در تست TasksViewModel خود، فعالیت یا قطعه ای برای مشاهده LiveData خود ندارید. برای دور زدن این موضوع، می‌توانید از روش observeForever استفاده کنید، که تضمین می‌کند LiveData دائماً مشاهده می‌شود، بدون نیاز به LifecycleOwner . هنگامی که observeForever کنید، باید به یاد داشته باشید که ناظر خود را حذف کنید یا خطر نشت ناظر را به خطر بیندازید.

این چیزی شبیه به کد زیر است. بررسی کنید:

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

این تعداد زیادی کد دیگ بخار برای مشاهده یک LiveData در یک آزمایش است! چند راه برای خلاص شدن از شر این دیگ بخار وجود دارد. شما قصد دارید یک تابع افزونه به نام LiveDataTestUtil ایجاد کنید تا اضافه کردن ناظران را ساده‌تر کنید.

  1. یک فایل Kotlin جدید به نام LiveDataTestUtil.kt در مجموعه منبع test خود بسازید.


  1. کد زیر را کپی و پیست کنید.

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

این یک روش نسبتاً پیچیده است. این یک تابع پسوند Kotlin به نام getOrAwaitValue ایجاد می‌کند که یک مشاهده‌گر را اضافه می‌کند، مقدار LiveData را دریافت می‌کند، و سپس مشاهده‌گر را پاک می‌کند - اساساً یک نسخه کوتاه و قابل استفاده مجدد از کد observeForever که در بالا نشان داده شده است. برای توضیح کامل این کلاس، این پست وبلاگ را بررسی کنید.

مرحله 3. از getOrAwaitValue برای نوشتن ادعا استفاده کنید

در این مرحله، شما از متد getOrAwaitValue استفاده می‌کنید و یک عبارت assert می‌نویسید که بررسی می‌کند که newTaskEvent راه‌اندازی شده است.

  1. مقدار LiveData را برای newTaskEvent با استفاده از getOrAwaitValue دریافت کنید.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. ادعا کنید که مقدار صفر نیست.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

آزمون کامل باید مانند کد زیر باشد.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. کد خود را اجرا کنید و گذرگاه آزمون را تماشا کنید!

اکنون که دیده اید که چگونه یک آزمون بنویسید ، یکی را به تنهایی بنویسید. در این مرحله ، با استفاده از مهارتهایی که آموخته اید ، نوشتن یک تست TasksViewModel را تمرین کنید.

مرحله 1 آزمون ViewModel خود را بنویسید

شما setFilterAllTasks_tasksAddViewVisible() را می نویسید. این تست باید بررسی کند که اگر نوع فیلتر خود را برای نشان دادن تمام کارها تنظیم کرده اید ، که دکمه Add Task قابل مشاهده است.

  1. با استفاده از addNewTask_setsNewTaskEvent() برای مرجع ، یک تست را در TasksViewModelTest به نام setFilterAllTasks_tasksAddViewVisible() بنویسید که حالت فیلتر را بر روی ALL_TASKS تنظیم می کند و ادعا می کند که tasksAddViewVisible livivedata true است.


برای شروع از کد زیر استفاده کنید.

TaskSviewModeltest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

توجه:

  • Enum TasksFilterType برای همه کارها ALL_TASKS.
  • قابلیت مشاهده دکمه برای افزودن یک کار توسط TaskSaddviewVisible LiveData کنترل می شود tasksAddViewVisible.
  1. آزمون خود را اجرا کنید.

مرحله 2 آزمون خود را با راه حل مقایسه کنید

راه حل خود را با راه حل زیر مقایسه کنید.

TaskSviewModeltest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

بررسی کنید که آیا موارد زیر را انجام می دهید:

  • شما tasksViewModel خود را با استفاده از همان بیانیه Androidx ApplicationProvider.getApplicationContext() ایجاد می کنید.
  • شما با روش setFiltering تماس می گیرید و در enum نوع فیلتر ALL_TASKS عبور می کنید.
  • شما با استفاده از روش getOrAwaitNextValue ، tasksAddViewVisible درست است.

مرحله 3. یک قانون را اضافه کنید

توجه کنید که چگونه در شروع هر دو تست خود ، یک TasksViewModel تعریف می کنید.

TaskSviewModeltest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

هنگامی که کد تنظیمات را برای چندین آزمایش تکرار کرده اید ، می توانید از حاشیه نویسی برای ایجاد یک روش تنظیم و حذف کد مکرر استفاده کنید. از آنجا که همه این تست ها قصد دارند TasksViewModel را آزمایش کنند و به یک مدل نمایش نیاز دارند ، این کد را به یک بلوک @Before منتقل کنید.

  1. ایجاد یک متغیر نمونه lateinit به نام tasksViewModel| .
  2. روشی به نام setupViewModel ایجاد کنید.
  3. آن را با @Before حاشیه نویسی کنید.
  4. کد فوری مدل View را به setupViewModel منتقل کنید.

TaskSviewModeltest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. کد خود را اجرا کنید!

هشدار

موارد زیر را انجام ندهید ، شروع نکنید

tasksViewModel

با تعریف آن:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

این باعث می شود همان نمونه برای همه آزمایشات استفاده شود. این چیزی است که شما باید از آن جلوگیری کنید زیرا هر آزمایش باید نمونه جدیدی از موضوع تحت آزمایش داشته باشد (ViewModel در این مورد).

کد نهایی شما برای TasksViewModelTest باید مانند کد زیر باشد.

TaskSviewModeltest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

برای دیدن تفاوت بین کدی که شروع کرده اید و کد نهایی ، اینجا را کلیک کنید.

برای بارگیری کد برای CodeLab تمام شده ، می توانید از دستور GIT در زیر استفاده کنید:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


از طرف دیگر می توانید مخزن را به عنوان یک فایل ZIP بارگیری کنید ، آن را از حالت فشرده خارج کنید و آن را در Android Studio باز کنید.

زیپ را دانلود کنید

این CodeLab را پوشش داده است:

  • نحوه اجرای تست ها از Android Studio.
  • تفاوت بین تست های محلی ( test ) و ابزار دقیق ( androidTest ).
  • نحوه نوشتن تست های واحد محلی با استفاده از Junit و Hamcrest .
  • تنظیم تست های ViewModel با کتابخانه تست Androidx .

دوره Udacity:

مستندات توسعه دهنده اندروید:

ویدئوها:

دیگر:

برای پیوندها به سایر CodeLabs در این دوره ، به صفحه Advanced Android در Kotlin Codelabs Landing مراجعه کنید.