این کد لبه بخشی از دوره آموزشی Advanced Android in Kotlin است. اگر از طریق کدها به صورت متوالی کار کنید، بیشترین ارزش را از این دوره خواهید گرفت، اما اجباری نیست. همه کدهای دوره در صفحه فرود Android Advanced in Kotlin Codelabs فهرست شده اند.
مقدمه
هنگامی که اولین ویژگی اولین برنامه خود را اجرا کردید، احتمالاً کد را اجرا کرده اید تا تأیید کنید که مطابق انتظار کار می کند. شما یک تست انجام دادید، البته یک تست دستی . همانطور که به افزودن و بهروزرسانی ویژگیها ادامه میدهید، احتمالاً به اجرای کد و تأیید کارکرد آن نیز ادامه دادهاید. اما هر بار انجام این کار به صورت دستی خسته کننده است، مستعد اشتباه است و مقیاس نمی شود.
کامپیوترها در مقیاس بندی و اتوماسیون عالی هستند! بنابراین، توسعهدهندگان شرکتهای بزرگ و کوچک، تستهای خودکار را مینویسند، این تستها آزمایشهایی هستند که توسط نرمافزار اجرا میشوند و نیازی به اجرای دستی برنامه برای تأیید کارکرد کد ندارند.
چیزی که در این سری از نرم افزارهای کد یاد خواهید گرفت، نحوه ایجاد مجموعه ای از تست ها (معروف به مجموعه تست) برای یک اپلیکیشن واقعی است.
این اولین کد لبه مبانی تست در اندروید را پوشش می دهد، شما اولین تست های خود را می نویسید و یاد می گیرید که چگونه LiveData
و ViewModel
s را تست کنید.
آنچه از قبل باید بدانید
باید با:
- زبان برنامه نویسی کاتلین
- کتابخانههای اصلی Android Jetpack:
ViewModel
وLiveData
- معماری برنامه، از الگوی راهنمای معماری برنامه و کد لبه های Android Fundamentals پیروی می کند
چیزی که یاد خواهید گرفت
شما با موضوعات زیر آشنا خواهید شد:
- نحوه نوشتن و اجرای تست های واحد در اندروید
- نحوه استفاده از 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 استفاده می کند. اگر با هر یک از نمونه های زیر آشنا هستید، این برنامه معماری مشابهی دارد:
- اتاق با نمای Codelab
- کد لبه های آموزشی Android Kotlin Fundamentals
- کد لبه های آموزش پیشرفته اندروید
- نمونه گل آفتابگردان اندروید
- دوره آموزشی توسعه اپلیکیشن اندروید با Kotlin Udacity
مهمتر است که معماری کلی برنامه را درک کنید تا اینکه درک عمیقی از منطق در هر لایه داشته باشید.
در اینجا خلاصه بسته هایی است که خواهید یافت:
بسته: | |
| صفحه افزودن یا ویرایش یک کار: کد لایه رابط کاربری برای افزودن یا ویرایش یک کار. |
| لایه داده: این لایه به لایه داده وظایف می پردازد. این شامل پایگاه داده، شبکه و کد مخزن است. |
| صفحه آمار: کد لایه رابط کاربری برای صفحه آمار. |
| صفحه جزئیات کار: کد لایه رابط کاربری برای یک کار واحد. |
| صفحه وظایف: کد لایه رابط کاربری برای لیست همه وظایف. |
| کلاسهای کاربردی : کلاسهای مشترک مورد استفاده در بخشهای مختلف برنامه، بهعنوان مثال برای طرحبندی بازخوانی کشیدن انگشتی که در چندین صفحه استفاده میشود. |
لایه داده (.data)
این برنامه شامل یک لایه شبکه شبیه سازی شده، در بسته راه دور ، و یک لایه پایگاه داده، در بسته محلی است. برای سادگی، در این پروژه لایه شبکه تنها با یک HashMap
با تاخیر شبیه سازی می شود تا درخواست های واقعی شبکه.
DefaultTasksRepository
بین لایه شبکه و لایه پایگاه داده مختصات یا واسطه می شود و همان چیزی است که داده ها را به لایه UI برمی گرداند.
لایه رابط کاربری (.addedittask، .statistics، .taskdetail، .tasks)
هر یک از بسته های لایه UI شامل یک قطعه و یک مدل view به همراه هر کلاس دیگری است که برای UI مورد نیاز است (مانند یک آداپتور برای لیست وظایف). TaskActivity
است که شامل تمام قطعات است.
جهت یابی
ناوبری برای برنامه توسط مؤلفه ناوبری کنترل می شود . در فایل nav_graph.xml
تعریف شده است. ناوبری در مدل های view با استفاده از کلاس Event
فعال می شود. مدلهای view نیز تعیین میکنند که چه آرگومانهایی باید منتقل شوند. قطعات Event
را مشاهده میکنند و پیمایش واقعی بین صفحهها را انجام میدهند.
در این کار، اولین تست های خود را اجرا می کنید.
- در 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: یک تست محلی را اجرا کنید
- پوشه
test
را باز کنید تا فایل ExampleUnitTest.kt را پیدا کنید. - روی آن کلیک راست کرده و Run ExampleUnitTest را انتخاب کنید.
شما باید خروجی زیر را در پنجره Run در پایین صفحه مشاهده کنید:
- به علامتهای سبز رنگ توجه کنید و نتایج آزمایش را گسترش دهید تا تأیید کنید که آزمونی به نام
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 باشد.
-
assertEquals(3, 1 + 1)
را به تست add_isCorrectaddition_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
}
}
- تست را اجرا کنید.
- در نتایج آزمایش، به یک X در کنار آزمون توجه کنید.
- همچنین توجه کنید:
- یک ادعای ناموفق در کل آزمون مردود است.
- مقدار مورد انتظار (3) در مقابل مقداری که واقعاً محاسبه شده است (2) به شما گفته می شود.
- شما به خط ادعای شکست خورده هدایت می
(ExampleUnitTest.kt:16)
.
مرحله 3: یک تست ابزاری را اجرا کنید
تستهای ابزاری در مجموعه منبع androidTest
هستند.
- مجموعه منبع
androidTest
را باز کنید. - تستی به نام
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: یک کلاس آزمایشی ایجاد کنید
- در مجموعه منبع
main
، درtodoapp.statistics
،StatisticsUtils.kt
را باز کنید. - تابع
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
یک کلاس داده است که شامل دو عدد است، درصد کارهای تکمیل شده و درصد فعال .
اندروید استودیو ابزارهایی را برای تولید نمونه های آزمایشی در اختیار شما قرار می دهد تا به شما در اجرای تست های این تابع کمک کند.
- روی
getActiveAndCompletedStats
کلیک راست کرده و Generate > Test را انتخاب کنید.
گفتگوی Create Test باز می شود:
- نام کلاس: را به
StatisticsUtilsTest
تغییر دهید (به جایStatisticsUtilsKtTest
؛ نداشتن KT در نام کلاس تست کمی بهتر است). - بقیه موارد پیش فرض را حفظ کنید. JUnit 4 کتابخانه آزمایشی مناسب است. بسته مقصد درست است (موقعیت کلاس
StatisticsUtils
را منعکس میکند) و نیازی به علامت زدن هیچ یک از جعبههای علامت ندارید (این فقط کد اضافی ایجاد میکند، اما شما تست خود را از ابتدا مینویسید). - OK را فشار دهید
گفتگوی Choose Destination Directory باز می شود:
شما یک تست محلی انجام می دهید زیرا تابع شما محاسبات ریاضی را انجام می دهد و شامل کد خاصی برای اندروید نمی شود. بنابراین، نیازی به اجرای آن بر روی دستگاه واقعی یا شبیه سازی شده نیست.
- دایرکتوری
test
(نهandroidTest
) را انتخاب کنید زیرا در حال نوشتن تست های محلی هستید. - روی OK کلیک کنید.
- به کلاس
StatisticsUtilsTest
ایجاد شده درtest/statistics/
توجه کنید.
مرحله 2: اولین تابع تست خود را بنویسید
شما می خواهید یک تست بنویسید که بررسی می کند:
- اگر هیچ کار تکمیل شده و یک کار فعال وجود ندارد،
- که درصد تست های فعال 100% است
- و درصد کارهای انجام شده 0 درصد است.
-
StatisticsUtilsTest
را باز کنید. - تابعی به نام
getActiveAndCompletedStats_noCompleted_returnsHundredZero
ایجاد کنید.
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- حاشیهنویسی
@Test
را بالای نام تابع اضافه کنید تا نشان دهید که یک آزمایش است. - فهرستی از وظایف ایجاد کنید.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- با این کارها با
getActiveAndCompletedStats
تماس بگیرید.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- با استفاده از ادعاها بررسی کنید که
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)
}
}
- تست را اجرا کنید (کلیک راست کنید
StatisticsUtilsTest
و Run را انتخاب کنید).
باید بگذرد:
مرحله 3: وابستگی Hamcrest را اضافه کنید
از آنجایی که آزمایشهای شما به عنوان مستندی از آنچه کد شما انجام میدهد عمل میکند، وقتی قابل خواندن توسط انسان باشد، خوب است. دو ادعای زیر را با هم مقایسه کنید:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
ادعای دوم بسیار بیشتر شبیه یک جمله انسانی است. این با استفاده از یک چارچوب ادعایی به نام Hamcrest نوشته شده است. یکی دیگر از ابزارهای خوب برای نوشتن ادعاهای خواندنی، کتابخانه حقیقت است . شما از Hamcrest در این کد لبه برای نوشتن ادعاها استفاده خواهید کرد.
-
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 برای نوشتن اظهارات استفاده کنید
- برای استفاده از 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))
}
}
- تست به روز شده خود را اجرا کنید تا مطمئن شوید هنوز کار می کند!
این کد لبه همه چیزهای Hamcrest را به شما آموزش نمی دهد، بنابراین اگر می خواهید بیشتر بدانید، آموزش رسمی را ببینید.
این یک کار اختیاری برای تمرین است.
در این کار، تست های بیشتری را با استفاده از JUnit و Hamcrest می نویسید. همچنین میتوانید با استفاده از یک استراتژی که از تمرین برنامه توسعه تست محور مشتق شده است، آزمایش بنویسید. Test Driven Development یا TDD یک مکتب فکری برنامه نویسی است که می گوید به جای اینکه ابتدا کد ویژگی خود را بنویسید، ابتدا تست های خود را بنویسید. سپس کد ویژگی خود را با هدف قبولی در آزمون های خود می نویسید.
مرحله 1. تست ها را بنویسید
برای زمانی که لیست وظایف معمولی دارید، تست بنویسید:
- اگر یک کار تکمیل شده وجود دارد و هیچ کار فعالی وجود ندارد، درصد
activeTasks
باید0f
و درصد کارهای تکمیل شده باید100f
باشد. - اگر دو کار تکمیل شده و سه کار فعال وجود دارد، درصد تکمیل شده باید
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()
)
}
برای رفع کد و نوشتن تست ها، از توسعه تست محور استفاده خواهید کرد. توسعه تست محور این مراحل را دنبال می کند.
- تست را با استفاده از ساختار Given, When, Then و با نامی که از قرارداد پیروی می کند بنویسید.
- عدم موفقیت آزمون را تأیید کنید.
- حداقل کد را برای قبولی در آزمون بنویسید.
- برای همه تست ها تکرار کنید!
به جای اینکه با رفع اشکال شروع کنید، ابتدا با نوشتن تست ها شروع می کنید. سپس می توانید تأیید کنید که آزمایش هایی دارید که از شما در برابر معرفی مجدد تصادفی این اشکالات در آینده محافظت می کند.
- اگر یک لیست خالی وجود داشته باشد (
emptyList()
- اگر در بارگیری وظایف خطایی رخ داده است، لیست
null
خواهد بود و هر دو درصد باید 0f باشند. - تست های خود را اجرا کنید و تأیید کنید که آنها شکست خورده اند :
مرحله 3. رفع اشکال
اکنون که آزمایشات خود را دارید، باگ را برطرف کنید.
- رفع اشکال در
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
)
}
}
- تست های خود را دوباره اجرا کنید و تأیید کنید که همه تست ها اکنون قبول شده اند!
با پیروی از 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
ایجاد میکنید.
- کلاسی را که می خواهید آزمایش کنید، در بسته
tasks
،TasksViewModel.
- در کد، روی نام کلاس
TasksViewModel
-> Generate -> Test کلیک راست کنید.
- در صفحه Create Test ، برای پذیرش روی OK کلیک کنید (نیازی به تغییر هیچ یک از تنظیمات پیش فرض نیست).
- در گفتگوی Choose Destination Directory ، دایرکتوری آزمایشی را انتخاب کنید.
مرحله 2. شروع به نوشتن تست ViewModel خود کنید
در این مرحله یک آزمایش مدل view اضافه میکنید تا آزمایش کنید که وقتی متد addNewTask
را فراخوانی میکنید، Event
برای باز کردن پنجره وظیفه جدید فعال میشود.
- یک آزمایش جدید به نام
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 دنبال کنید:
- هسته تست AndroidX و وابستگی های ext را اضافه کنید
- وابستگی کتابخانه تست روبولکتریک را اضافه کنید
- کلاس را با اجرای تست AndroidJunit4 حاشیه نویسی کنید
- کد تست AndroidX را بنویسید
شما قرار است این مراحل را کامل کنید و سپس بفهمید که آنها با هم چه می کنند.
مرحله 3. وابستگی های gradle را اضافه کنید
- این وابستگیها را در فایل
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 را اضافه کنید
-
@RunWith(AndroidJUnit4::class)
را بالای کلاس آزمایشی خود اضافه کنید.
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
مرحله 5. از AndroidX Test استفاده کنید
در این مرحله، می توانید از کتابخانه AndroidX Test استفاده کنید. این شامل روش ApplicationProvider.getApplicationContex
t
است که یک متن برنامه را دریافت می کند.
- یک
TasksViewModel
با استفاده ازApplicationProvider.getApplicationContext()
از کتابخانه تست AndroidX ایجاد کنید.
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- با
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
}
- تست خود را اجرا کنید تا مطمئن شوید که کار می کند.
مفهوم: تست 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 را برطرف کنید.
- خط زیر را به فایل 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
توصیه می شود دو کار را انجام دهید:
- از
InstantTaskExecutorRule
استفاده کنید - از مشاهده
LiveData
اطمینان حاصل کنید
مرحله 1. از InstantTaskExecutorRule استفاده کنید
InstantTaskExecutorRule
یک قانون JUnit است. وقتی از آن با @get:Rule
استفاده میکنید، باعث میشود برخی از کدها در کلاس InstantTaskExecutorRule
قبل و بعد از آزمایشها اجرا شوند (برای دیدن کد دقیق، میتوانید از میانبر صفحه کلید Command+B برای مشاهده فایل استفاده کنید).
این قانون تمام کارهای پسزمینه مربوط به اجزای معماری را در یک رشته اجرا میکند تا نتایج آزمایش به صورت همزمان و به ترتیب تکرار شونده اتفاق بیفتد. وقتی تست هایی می نویسید که شامل تست LiveData می شود، از این قانون استفاده کنید!
- وابستگی gradle را برای کتابخانه آزمایشی هسته Architecture Components (که حاوی این قانون است) اضافه کنید.
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
-
TasksViewModelTest.kt
باز کنید -
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
ایجاد کنید تا اضافه کردن ناظران را سادهتر کنید.
- یک فایل Kotlin جدید به نام
LiveDataTestUtil.kt
در مجموعه منبعtest
خود بسازید.
- رمز زیر را کپی - پیست کنید.
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
شده است.
- با استفاده از
getOrAwaitValue
، مقدارLiveData
را برایnewTaskEvent
دریافت کنید.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- ادعا کنید که مقدار صفر نیست.
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()))
}
}
- کد خود را اجرا کنید و آزمون قبولی را تماشا کنید!
حالا که نحوه نوشتن تست را دیدید، خودتان یکی بنویسید. 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.
- Using
addNewTask_setsNewTaskEvent()
for reference, write a test inTasksViewModelTest
calledsetFilterAllTasks_tasksAddViewVisible()
that sets the filtering mode toALL_TASKS
and asserts that thetasksAddViewVisible
LiveData istrue
.
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 isALL_TASKS.
- The visibility of the button to add a task is controlled by the
LiveData
tasksAddViewVisible.
- 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 AndroidXApplicationProvider.getApplicationContext()
statement. - You call the
setFiltering
method, passing in theALL_TASKS
filter type enum. - You check that the
tasksAddViewVisible
is true, using thegetOrAwaitNextValue
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.
- Create a
lateinit
instance variable calledtasksViewModel|
. - Create a method called
setupViewModel
. - Annotate it with
@Before
. - Move the view model instantiation code to
setupViewModel
.
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- 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 .
دوره بی ادبی:
مستندات توسعه دهنده اندروید:
- Guide to app architecture
- JUnit4
- Hamcrest
- Robolectric Testing library
- AndroidX Test Library
- AndroidX Architecture Components Core Test Library
- source sets
- Test from the command line
Videos:
Other:
برای پیوند به دیگر کدلب ها در این دوره، صفحه فرود Advanced Android in Kotlin Codelabs را ببینید.