مبانی تست زنی

این کد لبه بخشی از دوره آموزشی 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) را به تست add_isCorrect 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 assertThat به جای 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()
  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 را یاد خواهید گرفت.

در بقیه بخش کد، نحوه نوشتن تست برای دو کلاس Android را یاد خواهید گرفت که در اکثر برنامه‌ها رایج است - 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 برای دریافت زمینه برنامه است.

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 و اجرای آزمایشی AndroidJunit4، این کار بدون اینکه مستقیماً یک خط کد Robolectric بنویسید انجام می شود!

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

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

می‌توانید با به‌روزرسانی No such manifest file: ./AndroidManifest.xml ، هشدار ./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. با استفاده از getOrAwaitValue ، مقدار LiveData را برای newTaskEvent دریافت کنید.
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. کد خود را اجرا کنید و آزمون قبولی را تماشا کنید!

حالا که نحوه نوشتن تست را دیدید، خودتان یکی بنویسید. In this step, using the skills you've learned, practice writing another TasksViewModel test.

Step 1. Write your own ViewModel test

You'll write setFilterAllTasks_tasksAddViewVisible() . This test should check that if you've set your filter type to show all tasks, that the Add task button is visible.

  1. Using addNewTask_setsNewTaskEvent() for reference, write a test in TasksViewModelTest called setFilterAllTasks_tasksAddViewVisible() that sets the filtering mode to ALL_TASKS and asserts that the tasksAddViewVisible LiveData is true .


Use the code below to get started.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

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

Note:

  • The TasksFilterType enum for all tasks is ALL_TASKS.
  • The visibility of the button to add a task is controlled by the LiveData tasksAddViewVisible.
  1. Run your test.

Step 2. Compare your test to the solution

Compare your solution to the solution below.

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))
    }

Check whether you do the following:

  • You create your tasksViewModel using the same AndroidX ApplicationProvider.getApplicationContext() statement.
  • You call the setFiltering method, passing in the ALL_TASKS filter type enum.
  • You check that the tasksAddViewVisible is true, using the getOrAwaitNextValue method.

Step 3. Add a @Before rule

Notice how at the start of both of your tests, you define a TasksViewModel .

TasksViewModelTest

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

When you have repeated setup code for multiple tests, you can use the @Before annotation to create a setup method and remove repeated code. Since all of these tests are going to test the TasksViewModel , and need a view model, move this code to a @Before block.

  1. Create a lateinit instance variable called tasksViewModel| .
  2. Create a method called setupViewModel .
  3. Annotate it with @Before .
  4. Move the view model instantiation code to setupViewModel .

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. Run your code!

Warning

Do not do the following, do not initialize the

tasksViewModel

with its definition:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

This will cause the same instance to be used for all tests. This is something you should avoid because each test should have a fresh instance of the subject under test (the ViewModel in this case).

Your final code for TasksViewModelTest should look like the code below.

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))
    }
    
}

Click here to see a diff between the code you started and the final code.

To download the code for the finished codelab, you can use the git command below:

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


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

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

This codelab covered:

  • How to run tests from Android Studio.
  • The difference between local ( test ) and instrumentation tests ( androidTest ).
  • How to write local unit tests using JUnit and Hamcrest .
  • Setting up ViewModel tests with the AndroidX Test Library .

دوره بی ادبی:

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

Videos:

Other:

برای پیوند به دیگر کدلب ها در این دوره، صفحه فرود Advanced Android in Kotlin Codelabs را ببینید.