این کد لبه بخشی از دوره Advanced Android in Kotlin است. اگر از طریق کدها به ترتیب کار کنید، بیشترین ارزش را از این دوره خواهید گرفت، اما اجباری نیست. همه کدهای دوره در صفحه فرود Android Advanced in Kotlin Codelabs فهرست شده اند.
مقدمه
این دومین کد لبه آزمایشی همه چیز در مورد آزمایش دوگانه است: زمان استفاده از آنها در اندروید، و نحوه پیاده سازی آنها با استفاده از تزریق وابستگی، الگوی یاب سرویس و کتابخانه ها. با انجام این کار، یاد می گیرید که چگونه بنویسید:
- تست های واحد مخزن
- تست های ادغام قطعات و viewmodel
- تست های ناوبری قطعه
آنچه از قبل باید بدانید
باید با:
- زبان برنامه نویسی کاتلین
- آزمایش مفاهیم تحت پوشش اولین کد: نوشتن و اجرای تستهای واحد در اندروید، با استفاده از JUnit، Hamcrest، تست AndroidX، Robolectric، و همچنین تست LiveData
- کتابخانههای اصلی Android Jetpack:
ViewModel
،LiveData
و Navigation Component - معماری برنامه، از الگوی راهنمای معماری اپلیکیشن و کد لبه های Android Fundamentals پیروی می کند
- اصول اولیه کوروتین ها در اندروید
چیزی که یاد خواهید گرفت
- نحوه برنامه ریزی استراتژی تست
- نحوه ایجاد و استفاده از دوبل های آزمایشی، یعنی تقلبی و ساختگی
- نحوه استفاده از تزریق وابستگی دستی در اندروید برای تست های واحد و ادغام
- نحوه اعمال الگوی یاب سرویس
- نحوه آزمایش مخازن، قطعات، مدلهای مشاهده و مؤلفه Navigation
شما از کتابخانه ها و مفاهیم کد زیر استفاده خواهید کرد:
کاری که خواهی کرد
- تست های واحد را برای یک مخزن با استفاده از تزریق دوتایی و وابستگی بنویسید.
- تست های واحد را برای یک مدل view با استفاده از دوتایی تست و تزریق وابستگی بنویسید.
- با استفاده از چارچوب تست UI Espresso، تست های یکپارچه سازی قطعات و مدل های نمای آنها را بنویسید.
- تست های ناوبری را با استفاده از موکیتو و اسپرسو بنویسید.
در این سری از کدها، شما با برنامه TO-DO Notes کار خواهید کرد. این برنامه به شما امکان می دهد وظایف را برای تکمیل بنویسید و آنها را در یک لیست نمایش دهید. سپس میتوانید آنها را بهعنوان تکمیلشده یا خیر علامتگذاری کنید، فیلتر کنید یا حذف کنید.
این برنامه به زبان Kotlin نوشته شده است، دارای چند صفحه نمایش است، از اجزای Jetpack استفاده می کند و معماری را از یک راهنما به معماری برنامه دنبال می کند. با یادگیری نحوه آزمایش این برنامه، می توانید برنامه هایی را که از کتابخانه ها و معماری مشابهی استفاده می کنند، آزمایش کنید.
کد را دانلود کنید
برای شروع، کد را دانلود کنید:
از طرف دیگر، می توانید مخزن Github را برای کد کلون کنید:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
با پیروی از دستورالعملهای زیر، کمی وقت بگذارید و با کد آشنا شوید.
مرحله 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 اعمال میشود.
هرم تست
وقتی در مورد استراتژی تست فکر می کنیم، سه جنبه تست مرتبط وجود دارد:
- محدوده — تست چه مقدار از کد را لمس می کند؟ آزمایشها میتوانند بر روی یک روش واحد، در کل برنامه یا جایی در میان اجرا شوند.
- سرعت — تست با چه سرعتی اجرا می شود؟ سرعت تست می تواند از میلی ثانیه تا چند دقیقه متفاوت باشد.
- وفاداری — آزمون چقدر «دنیای واقعی» است؟ به عنوان مثال، اگر بخشی از کدی که در حال آزمایش آن هستید نیاز به درخواست شبکه داشته باشد، آیا کد آزمایشی واقعاً این درخواست شبکه را انجام می دهد یا نتیجه را جعلی می کند؟ اگر آزمون واقعاً با شبکه صحبت می کند، به این معنی است که وفاداری بالاتری دارد. معاوضه این است که اجرای آزمایش ممکن است بیشتر طول بکشد، در صورت قطع شدن شبکه ممکن است منجر به خطا شود یا استفاده از آن پرهزینه باشد.
بین این جنبه ها مبادلات ذاتی وجود دارد. به عنوان مثال، سرعت و وفاداری یک مبادله هستند - هر چه تست سریعتر باشد، به طور کلی، وفاداری کمتر است، و بالعکس. یکی از روش های رایج برای تقسیم تست های خودکار به این سه دسته است:
- تست های واحد — این تست ها بسیار متمرکز هستند که روی یک کلاس اجرا می شوند، معمولاً یک متد در آن کلاس. اگر تست واحد ناموفق باشد، میتوانید دقیقاً بدانید که مشکل در کجای کدتان است. آنها وفاداری پایینی دارند زیرا در دنیای واقعی، برنامه شما بسیار بیشتر از اجرای یک متد یا کلاس است. آنها به اندازه ای سریع هستند که هر بار که کد خود را تغییر می دهید اجرا شوند. آنها اغلب به صورت محلی (در مجموعه منبع
test
) اجرا می شوند. مثال: تست تک روش ها در مدل های view و مخازن. - تستهای یکپارچهسازی : این تستها تعامل چندین کلاس را آزمایش میکنند تا مطمئن شوند که هنگام استفاده با هم مطابق انتظار رفتار میکنند. یکی از راههای ساختاربندی تستهای یکپارچهسازی این است که آنها یک ویژگی واحد را آزمایش کنند، مانند توانایی ذخیره یک کار. آنها محدوده وسیع تری از کد را نسبت به تست های واحد آزمایش می کنند، اما همچنان برای اجرای سریع، در مقابل داشتن وفاداری کامل، بهینه شده اند. بسته به شرایط میتوان آنها را به صورت محلی یا بهعنوان تست ابزار دقیق اجرا کرد. مثال: آزمایش تمام عملکردهای یک جفت مدل قطعه و نمایش.
- تست های پایان به انتها (E2e) - ترکیبی از ویژگی ها را آزمایش کنید که با هم کار می کنند. آنها بخشهای بزرگی از برنامه را آزمایش میکنند، استفاده واقعی را از نزدیک شبیهسازی میکنند و بنابراین معمولاً کند هستند. آنها بالاترین وفاداری را دارند و به شما می گویند که برنامه شما در واقع به طور کلی کار می کند. به طور کلی، این تست ها تست های ابزاری خواهند بود (در مجموعه منبع
androidTest
)
مثال: راه اندازی کل برنامه و آزمایش چند ویژگی با هم.
نسبت پیشنهادی این تستها اغلب توسط یک هرم نشان داده میشود که اکثریت قریب به اتفاق تستها تستهای واحد هستند.
معماری و آزمایش
توانایی شما برای آزمایش برنامه خود در تمام سطوح مختلف هرم آزمایشی به طور ذاتی با معماری برنامه شما مرتبط است. به عنوان مثال، یک برنامه کاربردی با معماری بسیار ضعیف ممکن است تمام منطق خود را در یک متد قرار دهد. ممکن است بتوانید یک تست پایان به انتها برای این کار بنویسید، زیرا این تستها تمایل دارند بخشهای بزرگی از برنامه را آزمایش کنند، اما در مورد تستهای واحد نوشتن یا یکپارچهسازی چطور؟ با وجود همه کدها در یک مکان، آزمایش فقط کد مربوط به یک واحد یا ویژگی دشوار است.
یک رویکرد بهتر این است که منطق برنامه را به روشها و کلاسهای متعدد تقسیم کنیم و به هر قطعه اجازه میدهیم به صورت مجزا آزمایش شود. معماری راهی برای تقسیم و سازماندهی کد شما است که امکان تست واحد و یکپارچه سازی آسان تر را فراهم می کند. برنامه TO-DO که آزمایش می کنید از معماری خاصی پیروی می کند:
در این درس، نحوه تست بخش هایی از معماری فوق را به صورت مجزا مشاهده خواهید کرد:
- ابتدا مخزن را واحد تست خواهید کرد.
- سپس از یک تست دوبل در مدل view استفاده خواهید کرد که برای تست واحد و تست یکپارچه سازی مدل view ضروری است.
- در مرحله بعد، نوشتن تستهای یکپارچهسازی برای قطعات و مدلهای نمای آنها را یاد خواهید گرفت.
- در نهایت، نوشتن تستهای یکپارچهسازی را یاد خواهید گرفت که شامل مولفه Navigation باشد.
تست پایان تا پایان در درس بعدی پوشش داده خواهد شد.
وقتی برای بخشی از یک کلاس (یک متد یا مجموعه کوچکی از متدها) یک تست واحد می نویسید، هدف شما این است که فقط کد موجود در آن کلاس را آزمایش کنید .
آزمایش فقط کد در یک کلاس یا کلاس های خاص می تواند مشکل باشد. بیایید به یک مثال نگاه کنیم. کلاس data.source.DefaultTaskRepository
را در مجموعه منبع main
باز کنید. این مخزن برنامه است و کلاسی است که در مرحله بعدی تست های واحد را می نویسید.
هدف شما این است که فقط کدهای موجود در آن کلاس را آزمایش کنید. با این حال، DefaultTaskRepository
برای عملکرد به کلاسهای دیگر، مانند LocalTaskDataSource
و RemoteTaskDataSource
بستگی دارد. راه دیگری برای بیان این موضوع این است که LocalTaskDataSource
و RemoteTaskDataSource
وابستگی های DefaultTaskRepository
هستند.
بنابراین هر متد در DefaultTaskRepository
متدهایی را بر روی کلاس های منبع داده فراخوانی می کند، که به نوبه خود متدهای کلاس های دیگر را برای ذخیره اطلاعات در پایگاه داده یا برقراری ارتباط با شبکه فراخوانی می کند.
برای مثال، نگاهی به این روش در DefaultTasksRepo
بیندازید.
suspend fun getTasks(forceUpdate: Boolean = false): Result<List<Task>> {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
getTasks
یکی از "پایه" ترین تماس هایی است که ممکن است با مخزن خود برقرار کنید. این روش شامل خواندن از پایگاه داده SQLite و برقراری تماس های شبکه (تماس برای updateTasksFromRemoteDataSource
) است. این شامل کد بسیار بیشتری از کد مخزن است.
در اینجا چند دلیل خاص وجود دارد که چرا آزمایش مخزن سخت است:
- شما باید به فکر ایجاد و مدیریت یک پایگاه داده باشید تا حتی ساده ترین تست ها را برای این مخزن انجام دهید. این سؤالاتی مانند "آیا این یک آزمون محلی یا ابزاری است؟" و اگر باید از AndroidX Test برای دریافت یک محیط اندروید شبیه سازی شده استفاده کنید.
- اجرای برخی از بخشهای کد، مانند کدهای شبکه، ممکن است زمان زیادی طول بکشد، یا حتی گاهی اوقات با شکست مواجه میشوند و آزمایشهای طولانیمدت و پوستهپوستی ایجاد میکنند.
- تستهای شما ممکن است توانایی خود را برای تشخیص اینکه کدام کد مقصر خطای تست است، از دست بدهند. آزمایشهای شما میتوانند شروع به آزمایش کد غیر مخزن کنند، بنابراین، برای مثال، آزمایشهای واحد «مخزن» فرضی شما ممکن است به دلیل مشکل در برخی از کدهای وابسته، مانند کد پایگاه داده، با شکست مواجه شوند.
تست دوبل
راه حل این است که وقتی در حال آزمایش مخزن هستید، از کد شبکه یا پایگاه داده واقعی استفاده نکنید ، بلکه از یک تست دوبل استفاده کنید. تست دو نسخه ای از یک کلاس است که به طور خاص برای آزمایش ساخته شده است. این به معنای جایگزینی نسخه واقعی یک کلاس در تست ها است. شبیه این است که یک بدلکار بازیگری است که در بدلکاری تخصص دارد و بازیگر واقعی را برای اقدامات خطرناک جایگزین می کند.
در اینجا چند نوع تست دوبل آورده شده است:
جعلی | یک تست دوتایی که یک پیادهسازی «کار» کلاس دارد، اما به گونهای پیادهسازی شده است که برای آزمایشها خوب است اما برای تولید نامناسب است. |
مسخره کردن | یک تست دوبل که ردیابی می کند کدام یک از متدهای آن فراخوانی شده است. سپس بسته به اینکه متدهای آن به درستی فراخوانی شده باشند، در یک آزمون موفق می شود یا ناموفق می شود. |
خرد | یک تست دوبل که شامل هیچ منطقی نیست و فقط آنچه را که برنامه ریزی کرده اید برمی گرداند. یک |
ساختگی | یک تست دوتایی که در اطراف ارسال می شود اما استفاده نمی شود، مثلاً اگر فقط باید آن را به عنوان یک پارامتر ارائه کنید. اگر |
جاسوس | یک تست دوبل که همچنین برخی از اطلاعات اضافی را ردیابی می کند. برای مثال، اگر یک |
برای کسب اطلاعات بیشتر در مورد تست های دوتایی، تست در توالت: دوبل های آزمایشی خود را بشناسید .
رایج ترین تست های دوگانه مورد استفاده در اندروید Fakes و Mocks هستند.
در این کار، شما قصد دارید یک FakeDataSource
تست دو به واحد، DefaultTasksRepository
جدا از منابع داده واقعی ایجاد کنید.
مرحله 1: کلاس FakeDataSource را ایجاد کنید
در این مرحله شما می خواهید کلاسی به نام FakeDataSouce
ایجاد کنید که دو تست LocalDataSource
و RemoteDataSource
خواهد بود.
- در مجموعه منبع آزمایشی ، روی New -> Package کلیک راست کنید.
- یک بسته داده با یک بسته منبع در داخل بسازید.
- یک کلاس جدید به نام
FakeDataSource
در بسته data/source ایجاد کنید.
مرحله 2: رابط TasksDataSource را پیاده سازی کنید
برای اینکه بتوانید از کلاس جدید FakeDataSource
خود به عنوان دو تست استفاده کنید، باید بتواند جایگزین سایر منابع داده شود. این منابع داده عبارتند از TasksLocalDataSource
و TasksRemoteDataSource
.
- توجه کنید که هر دوی اینها چگونه رابط
TasksDataSource
را پیاده سازی می کنند.
class TasksLocalDataSource internal constructor(
private val tasksDao: TasksDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksDataSource { ... }
object TasksRemoteDataSource : TasksDataSource { ... }
- کاری کنید که
FakeDataSource
TasksDataSource
را پیاده سازی کند:
class FakeDataSource : TasksDataSource {
}
Android Studio شکایت خواهد کرد که روشهای لازم را برای TasksDataSource
پیادهسازی نکردهاید.
- از منوی رفع سریع استفاده کنید و Implement Members را انتخاب کنید.
- همه روش ها را انتخاب کرده و OK را فشار دهید.
مرحله 3: متد getTasks را در FakeDataSource پیاده سازی کنید
FakeDataSource
نوع خاصی از تست دوگانه است که به آن جعلی گفته می شود. جعلی یک آزمایش دوگانه است که دارای یک پیادهسازی «در حال کار» از کلاس است، اما به گونهای پیادهسازی شده است که برای آزمایش خوب است اما برای تولید نامناسب است. پیاده سازی "کار" به این معنی است که کلاس خروجی های واقعی را با ورودی های داده شده تولید می کند.
برای مثال، منبع داده جعلی شما به شبکه متصل نمیشود یا چیزی را در پایگاه داده ذخیره نمیکند، بلکه فقط از یک لیست درون حافظه استفاده میکند. این "همانطور که انتظار دارید کار می کند" زیرا روش های دریافت یا ذخیره کارها نتایج مورد انتظار را به دست خواهند آورد، اما هرگز نمی توانید از این پیاده سازی در تولید استفاده کنید، زیرا در سرور یا پایگاه داده ذخیره نمی شود.
یک FakeDataSource
- به شما امکان می دهد کد را در
DefaultTasksRepository
بدون نیاز به تکیه بر پایگاه داده یا شبکه واقعی آزمایش کنید. - یک پیاده سازی "به اندازه کافی واقعی" برای آزمایش ها ارائه می دهد.
- سازنده
FakeDataSource
را برای ایجاد یکvar
به نامtasks
تغییر دهید کهMutableList<Task>?
با مقدار پیش فرض یک لیست خالی قابل تغییر.
class FakeDataSource(var tasks: MutableList<Task>? = mutableListOf()) : TasksDataSource { // Rest of class }
این لیستی از وظایفی است که به عنوان یک پایگاه داده یا پاسخ سرور "جعل" می شوند. در حال حاضر، هدف آزمایش روش getTasks
مخزن است. این روشهای getTasks
، deleteAllTasks
و saveTask
منبع داده را فراخوانی میکند.
یک نسخه جعلی از این روش ها بنویسید:
-
getTasks
بنویسید: اگرtasks
null
نیستند، یک نتیجهSuccess
را برگردانید. اگرtasks
null
است، یک نتیجهError
را برگردانید. - نوشتن
deleteAllTasks
: لیست وظایف قابل تغییر را پاک کنید. -
saveTask
بنویسید: وظیفه را به لیست اضافه کنید.
این روشها که برای FakeDataSource
پیادهسازی شدهاند، شبیه کد زیر هستند.
override suspend fun getTasks(): Result<List<Task>> {
tasks?.let { return Success(ArrayList(it)) }
return Error(
Exception("Tasks not found")
)
}
override suspend fun deleteAllTasks() {
tasks?.clear()
}
override suspend fun saveTask(task: Task) {
tasks?.add(task)
}
در صورت نیاز، بیانیههای واردات آمده است:
import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
این مشابه نحوه عملکرد منابع داده محلی و راه دور واقعی است.
در این مرحله، میخواهید از تکنیکی به نام تزریق وابستگی دستی استفاده کنید تا بتوانید از تست جعلی که ایجاد کردهاید استفاده کنید.
مسئله اصلی این است که شما یک FakeDataSource
دارید، اما نحوه استفاده از آن در تست ها مشخص نیست. باید جایگزین TasksRemoteDataSource
و TasksLocalDataSource
شود، اما فقط در تستها. هر دو TasksRemoteDataSource
و TasksLocalDataSource
وابستگی های DefaultTasksRepository
هستند، به این معنی که DefaultTasksRepositories
برای اجرا به این کلاس ها نیاز دارد یا به آنها "وابسته" دارد.
در حال حاضر، وابستگی ها در متد init
DefaultTasksRepository
ساخته می شوند.
DefaultTasksRepository.kt
class DefaultTasksRepository private constructor(application: Application) {
private val tasksRemoteDataSource: TasksDataSource
private val tasksLocalDataSource: TasksDataSource
// Some other code
init {
val database = Room.databaseBuilder(application.applicationContext,
ToDoDatabase::class.java, "Tasks.db")
.build()
tasksRemoteDataSource = TasksRemoteDataSource
tasksLocalDataSource = TasksLocalDataSource(database.taskDao())
}
// Rest of class
}
از آنجایی که شما در حال ایجاد و تخصیص taskLocalDataSource
و tasksRemoteDataSource
در DefaultTasksRepository
هستید، اساساً کدگذاری سختی دارند. هیچ راهی برای تعویض در تست دوبل شما وجود ندارد.
کاری که میخواهید انجام دهید، این است که این منابع داده را بهجای کدگذاری سخت، در اختیار کلاس قرار دهید . ارائه وابستگی ها به عنوان تزریق وابستگی شناخته می شود. روش های مختلفی برای ارائه وابستگی ها و در نتیجه انواع مختلفی از تزریق وابستگی وجود دارد.
Constructor Dependency Injection به شما این امکان را می دهد که با ارسال آن به سازنده، دو برابر تست را تعویض کنید.
بدون تزریق | تزریق |
مرحله 1: از Injection وابستگی سازنده در DefaultTasksRepository استفاده کنید
- سازنده
DefaultTaskRepository
را از دریافت یکApplication
به دریافت هر دو منبع داده و توزیع کننده Coroutine تغییر دهید (که شما همچنین باید آن را برای تست های خود تعویض کنید - این با جزئیات بیشتر در بخش درس سوم در مورد کوروتین ها توضیح داده شده است).
DefaultTasksRepository.kt
// REPLACE
class DefaultTasksRepository private constructor(application: Application) { // Rest of class }
// WITH
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { // Rest of class }
- چون وابستگیها را وارد کردید، متد
init
حذف کنید. دیگر نیازی به ایجاد وابستگی ندارید. - همچنین متغیرهای نمونه قدیمی را حذف کنید. شما آنها را در سازنده تعریف می کنید:
DefaultTasksRepository.kt
// Delete these old variables
private val tasksRemoteDataSource: TasksDataSource
private val tasksLocalDataSource: TasksDataSource
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
- در نهایت، متد
getRepository
را برای استفاده از سازنده جدید به روز کنید:
DefaultTasksRepository.kt
companion object {
@Volatile
private var INSTANCE: DefaultTasksRepository? = null
fun getRepository(app: Application): DefaultTasksRepository {
return INSTANCE ?: synchronized(this) {
val database = Room.databaseBuilder(app,
ToDoDatabase::class.java, "Tasks.db")
.build()
DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
INSTANCE = it
}
}
}
}
شما اکنون از تزریق وابستگی سازنده استفاده می کنید!
مرحله 2: از FakeDataSource خود در آزمایشات خود استفاده کنید
اکنون که کد شما از تزریق وابستگی سازنده استفاده می کند، می توانید از منبع داده جعلی خود برای آزمایش DefaultTasksRepository
خود استفاده کنید.
- روی نام کلاس
DefaultTasksRepository
کلیک راست کرده و Generate و سپس Test را انتخاب کنید. - دستورات را برای ایجاد
DefaultTasksRepositoryTest
در مجموعه منبع آزمایشی دنبال کنید. - در بالای کلاس
DefaultTasksRepositoryTest
جدید، متغیرهای عضو را در زیر اضافه کنید تا دادهها را در منابع داده جعلی خود نشان دهید.
DefaultTasksRepositoryTest.kt
private val task1 = Task("Title1", "Description1")
private val task2 = Task("Title2", "Description2")
private val task3 = Task("Title3", "Description3")
private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
private val localTasks = listOf(task3).sortedBy { it.id }
private val newTasks = listOf(task3).sortedBy { it.id }
- سه متغیر، دو متغیر عضو
FakeDataSource
(یکی برای هر منبع داده برای مخزن شما) و یک متغیر برایDefaultTasksRepository
که آن را آزمایش خواهید کرد، ایجاد کنید.
DefaultTasksRepositoryTest.kt
private lateinit var tasksRemoteDataSource: FakeDataSource
private lateinit var tasksLocalDataSource: FakeDataSource
// Class under test
private lateinit var tasksRepository: DefaultTasksRepository
روشی برای راه اندازی و مقداردهی اولیه یک DefaultTasksRepository
قابل آزمایش بسازید. این DefaultTasksRepository
از دو تست شما، FakeDataSource
استفاده خواهد کرد.
- روشی به نام
createRepository
ایجاد کنید و آن را با@Before
حاشیه نویسی کنید. - منابع داده جعلی خود را با استفاده از لیست های
remoteTasks
وlocalTasks
نمونه سازی کنید. -
tasksRepository
خود را با استفاده از دو منبع داده جعلی که ایجاد کردهاید وDispatchers.Unconfined
نمونهسازی کنید.
روش نهایی باید مانند کد زیر باشد.
DefaultTasksRepositoryTest.kt
@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
// Get a reference to the class under test
tasksRepository = DefaultTasksRepository(
// TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main
// this requires understanding more about coroutines + testing
// so we will keep this as Unconfined for now.
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
)
}
مرحله 3: DefaultTasksRepository getTasks() Test را بنویسید
زمان نوشتن یک تست DefaultTasksRepository
است!
- یک تست برای متد
getTasks
مخزن بنویسید. بررسی کنید که وقتیgetTasks
باtrue
فرا میخوانید (به این معنی که باید از منبع داده راه دور مجدداً بارگیری شود)، دادهها را از منبع داده راه دور بازگرداند (بر خلاف منبع داده محلی).
DefaultTasksRepositoryTest.kt
@Test
fun getTasks_requestsAllTasksFromRemoteDataSource(){
// When tasks are requested from the tasks repository
val tasks = tasksRepository.getTasks(true) as Success
// Then tasks are loaded from the remote data source
assertThat(tasks.data, IsEqual(remoteTasks))
}
هنگام فراخوانی getTasks:
مرحله 4: runBlockingTest را اضافه کنید
خطای Coroutine انتظار می رود زیرا getTasks
یک تابع suspend
است و برای فراخوانی آن باید یک Coroutine راه اندازی کنید. برای آن، شما به یک محدوده کاری نیاز دارید. برای رفع این خطا، باید چند وابستگی gradle را برای مدیریت راهاندازی کوروتینها در تستهای خود اضافه کنید.
- با استفاده از
testImplementation
وابستگی های مورد نیاز برای تست کوروتین ها را به مجموعه منبع تست اضافه کنید.
app/build.gradle
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
همگام سازی را فراموش نکنید!
kotlinx-coroutines-test
کتابخانه تست coroutines است که به طور خاص برای آزمایش کوروتین ها در نظر گرفته شده است. برای اجرای تست های خود، از تابع runBlockingTest
استفاده کنید. این تابعی است که توسط کتابخانه تست coroutines ارائه شده است. یک بلوک از کد را می گیرد و سپس این بلوک کد را در یک زمینه ویژه کاری اجرا می کند که به صورت همزمان و بلافاصله اجرا می شود، به این معنی که اقدامات به ترتیب قطعی انجام می شوند. این اساسا باعث میشود که کوروتینهای شما مانند برنامههای غیرکوروتین اجرا شوند، بنابراین برای آزمایش کد در نظر گرفته شده است.
هنگام فراخوانی یک تابع suspend
از runBlockingTest
در کلاس های آزمایشی خود استفاده کنید. شما در مورد نحوه عملکرد runBlockingTest
و نحوه آزمایش کوروتین ها در کدهای بعدی این مجموعه اطلاعات بیشتری کسب خواهید کرد.
-
@ExperimentalCoroutinesApi
را بالای کلاس اضافه کنید. این نشان میدهد که میدانید از یک api کوروتین آزمایشی (runBlockingTest
) در کلاس استفاده میکنید. بدون آن، شما یک هشدار دریافت خواهید کرد. - در
DefaultTasksRepositoryTest
خود،runBlockingTest
اضافه کنید تا در کل آزمایش شما به عنوان یک "بلاک" از کد استفاده شود.
این تست نهایی شبیه کد زیر است.
DefaultTasksRepositoryTest.kt
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.core.IsEqual
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
@ExperimentalCoroutinesApi
class DefaultTasksRepositoryTest {
private val task1 = Task("Title1", "Description1")
private val task2 = Task("Title2", "Description2")
private val task3 = Task("Title3", "Description3")
private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
private val localTasks = listOf(task3).sortedBy { it.id }
private val newTasks = listOf(task3).sortedBy { it.id }
private lateinit var tasksRemoteDataSource: FakeDataSource
private lateinit var tasksLocalDataSource: FakeDataSource
// Class under test
private lateinit var tasksRepository: DefaultTasksRepository
@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
// Get a reference to the class under test
tasksRepository = DefaultTasksRepository(
// TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main
// this requires understanding more about coroutines + testing
// so we will keep this as Unconfined for now.
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
)
}
@Test
fun getTasks_requestsAllTasksFromRemoteDataSource() = runBlockingTest {
// When tasks are requested from the tasks repository
val tasks = tasksRepository.getTasks(true) as Success
// Then tasks are loaded from the remote data source
assertThat(tasks.data, IsEqual(remoteTasks))
}
}
- تست
getTasks_requestsAllTasksFromRemoteDataSource
جدید خود را اجرا کنید و تأیید کنید که کار می کند و خطا برطرف شده است!
شما به تازگی نحوه تست واحد یک مخزن را دیدید. در این مراحل بعدی، مجدداً از تزریق وابستگی استفاده میکنید و دوبار تست دیگری ایجاد میکنید – این بار برای نشان دادن نحوه نوشتن تستهای واحد و ادغام برای مدلهای view خود.
تستهای واحد فقط باید کلاس یا روشی را که به آن علاقهمندید آزمایش کنند. این به عنوان تست در انزوا شناخته میشود، که در آن شما به وضوح "واحد" خود را جدا میکنید و فقط کدی را که بخشی از آن واحد است آزمایش میکنید.
بنابراین TasksViewModelTest
فقط باید کد TasksViewModel
را آزمایش کند - نباید در پایگاه داده، شبکه یا کلاس های مخزن تست شود. بنابراین برای مدلهای view خود، دقیقاً مانند آنچه که برای مخزن خود انجام دادید، یک مخزن جعلی ایجاد میکنید و برای استفاده از آن در آزمایشهای خود از تزریق وابستگی استفاده میکنید.
در این کار، تزریق وابستگی را برای مشاهده مدل ها اعمال می کنید.
مرحله 1. یک رابط TasksRepository ایجاد کنید
اولین قدم برای استفاده از تزریق وابستگی سازنده، ایجاد یک رابط مشترک بین کلاس جعلی و واقعی است.
این در عمل چگونه به نظر می رسد؟ به TasksRemoteDataSource
، TasksLocalDataSource
و FakeDataSource
نگاه کنید، و توجه کنید که همه آنها یک رابط مشترک دارند: TasksDataSource
. این به شما امکان می دهد در سازنده DefaultTasksRepository
بگویید که یک TasksDataSource
را وارد می کنید.
DefaultTasksRepository.kt
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
این همان چیزی است که به ما امکان می دهد در FakeDataSource
شما مبادله کنیم!
در مرحله بعد، همانند منابع داده، یک رابط برای DefaultTasksRepository
ایجاد کنید. باید شامل تمام متدهای عمومی (سطح API عمومی) DefaultTasksRepository
باشد.
-
DefaultTasksRepository
را باز کرده و روی نام کلاس کلیک راست کنید . سپس Refactor -> Extract -> Interface را انتخاب کنید.
- برای جداسازی فایل Extract را انتخاب کنید.
- در پنجره Extract Interface ، نام رابط را به
TasksRepository
تغییر دهید. - در قسمت Members to form interface ، همه اعضا به جز دو عضو همراه و متدهای خصوصی را بررسی کنید.
- روی Refactor کلیک کنید. رابط
TasksRepository
جدید باید در بسته داده/منبع ظاهر شود.
و DefaultTasksRepository
اکنون TasksRepository
را پیاده سازی می کند.
- برنامه خود را اجرا کنید (نه تست ها) تا مطمئن شوید که همه چیز هنوز در حالت کار است.
مرحله 2. FakeTestRepository را ایجاد کنید
اکنون که اینترفیس را دارید، می توانید تست دوبل DefaultTaskRepository
ایجاد کنید.
- در مجموعه منبع آزمایشی ، در data/source فایل Kotlin و کلاس
FakeTestRepository.kt
را ایجاد کنید و از رابطTasksRepository
گسترش دهید.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
}
به شما گفته می شود که باید متدهای رابط را پیاده سازی کنید.
- ماوس را روی خطا نگه دارید تا منوی پیشنهاد را مشاهده کنید، سپس کلیک کرده و Implement Members را انتخاب کنید.
- همه روش ها را انتخاب کرده و OK را فشار دهید.
مرحله 3. روش های FakeTestRepository را پیاده سازی کنید
اکنون یک کلاس FakeTestRepository
با متدهای "نیست پیاده سازی" دارید. مشابه نحوه پیادهسازی FakeDataSource
، FakeTestRepository
به جای پرداختن به میانجیگری پیچیده بین منابع داده محلی و راه دور، توسط یک ساختار داده پشتیبانی میشود.
توجه داشته باشید که FakeTestRepository
شما نیازی به استفاده از FakeDataSource
یا هر چیز دیگری ندارد. فقط باید خروجی های جعلی واقع بینانه را برگرداند. شما از LinkedHashMap
برای ذخیره لیست وظایف و MutableLiveData
برای وظایف قابل مشاهده خود استفاده خواهید کرد.
- در
FakeTestRepository
، هم یک متغیرLinkedHashMap
که لیست فعلی وظایف را نشان می دهد و هم یکMutableLiveData
برای وظایف قابل مشاهده خود اضافه کنید.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private val observableTasks = MutableLiveData<Result<List<Task>>>()
// Rest of class
}
روش های زیر را اجرا کنید:
-
getTasks
— این روش بایدtasksServiceData
را گرفته و با استفاده ازtasksServiceData.values.toList()
آن را به یک لیست تبدیل کند و سپس آن را به عنوان نتیجهSuccess
برگرداند. -
refreshTasks
- مقدارobservableTasks
بهروزرسانی میکند تا همان چیزی باشد که توسطgetTasks()
برگردانده میشود. -
observeTasks
— با استفاده ازrunBlocking
یک برنامه مشترک ایجاد می کند وrefreshTasks
اجرا می کند، سپسobservableTasks
برمی گرداند.
در زیر کد آن متدها آمده است.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private val observableTasks = MutableLiveData<Result<List<Task>>>()
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
return Result.Success(tasksServiceData.values.toList())
}
override suspend fun refreshTasks() {
observableTasks.value = getTasks()
}
override fun observeTasks(): LiveData<Result<List<Task>>> {
runBlocking { refreshTasks() }
return observableTasks
}
// Rest of class
}
مرحله 4. روشی برای آزمایش به addTasks اضافه کنید
هنگام آزمایش، بهتر است برخی از Tasks
از قبل در مخزن خود داشته باشید. میتوانید چندین بار با saveTask
تماس بگیرید، اما برای آسانتر کردن این کار، یک روش کمکی مخصوص آزمایشها اضافه کنید که به شما امکان میدهد وظایف را اضافه کنید.
- متد
addTasks
را اضافه کنید که چندین کارvararg
را انجام میدهد، هر کدام را بهHashMap
اضافه میکند و سپس کارها را تازه میکند.
FakeTestRepository.kt
fun addTasks(vararg tasks: Task) {
for (task in tasks) {
tasksServiceData[task.id] = task
}
runBlocking { refreshTasks() }
}
در این مرحله شما یک مخزن جعلی برای آزمایش با چند روش کلیدی پیاده سازی شده دارید. بعد، از این در تست های خود استفاده کنید!
در این کار از یک کلاس جعلی در داخل ViewModel
استفاده می کنید. از تزریق وابستگی سازنده برای دریافت دو منبع داده از طریق تزریق وابستگی سازنده با افزودن یک متغیر TasksRepository
به سازنده TasksViewModel
استفاده کنید.
این فرآیند با مدلهای view کمی متفاوت است زیرا شما آنها را مستقیماً نمیسازید. به عنوان مثال:
class TasksFragment : Fragment() {
private val viewModel by viewModels<TasksViewModel>()
// Rest of class...
}
همانطور که در کد بالا وجود دارد، شما از ویژگی delegate viewModel's
استفاده می کنید که مدل view را ایجاد می کند. برای تغییر نحوه ساخت مدل view، باید ViewModelProvider.Factory
را اضافه کرده و از آن استفاده کنید. اگر با ViewModelProvider.Factory
آشنا نیستید، میتوانید در اینجا درباره آن اطلاعات بیشتری کسب کنید.
مرحله 1. یک ViewModelFactory در TasksViewModel بسازید و از آن استفاده کنید
شما با به روز رسانی کلاس ها و تست های مربوط به صفحه Tasks
شروع می کنید.
-
TasksViewModel
باز کنید . - سازنده
TasksViewModel
را تغییر دهید تا به جای ساختن آن در داخل کلاس،TasksRepository
را بگیرد.
TasksViewModel.kt
// REPLACE
class TasksViewModel(application: Application) : AndroidViewModel(application) {
private val tasksRepository = DefaultTasksRepository.getRepository(application)
// Rest of class
}
// WITH
class TasksViewModel( private val tasksRepository: TasksRepository ) : ViewModel() {
// Rest of class
}
از آنجایی که سازنده را تغییر دادید، اکنون باید از یک کارخانه برای ساخت TasksViewModel
استفاده کنید. کلاس کارخانه را در همان فایل TasksViewModel
قرار دهید، اما می توانید آن را در فایل خودش نیز قرار دهید.
- در پایین فایل
TasksViewModel
، خارج از کلاس، یکTasksViewModelFactory
اضافه کنید که یکTasksRepository
ساده را می گیرد.
TasksViewModel.kt
@Suppress("UNCHECKED_CAST")
class TasksViewModelFactory (
private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>) =
(TasksViewModel(tasksRepository) as T)
}
این روش استانداردی است که شما نحوه ساخت ViewModel
ها را تغییر می دهید. اکنون که کارخانه را دارید، از آن در هر کجا که مدل view خود را می سازید استفاده کنید.
- برای استفاده از کارخانه،
TasksFragment
به روز کنید.
TasksFragment.kt
// REPLACE
private val viewModel by viewModels<TasksViewModel>()
// WITH
private val viewModel by viewModels<TasksViewModel> {
TasksViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
- کد برنامه خود را اجرا کنید و مطمئن شوید که همه چیز همچنان کار می کند!
مرحله 2. از FakeTestRepository در TasksViewModelTest استفاده کنید
اکنون به جای استفاده از مخزن واقعی در تست های مدل view خود، می توانید از مخزن جعلی استفاده کنید.
-
TasksViewModelTest
را باز کنید . - یک ویژگی
FakeTestRepository
درTasksViewModelTest
اضافه کنید.
TaskViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Use a fake repository to be injected into the viewmodel
private lateinit var tasksRepository: FakeTestRepository
// Rest of class
}
- روش
setupViewModel
به روز کنید تا یکFakeTestRepository
با سه کار بسازید و سپسtasksViewModel
با این مخزن بسازید.
TasksViewModelTest.kt
@Before
fun setupViewModel() {
// We initialise the tasks to 3, with one active and two completed
tasksRepository = FakeTestRepository()
val task1 = Task("Title1", "Description1")
val task2 = Task("Title2", "Description2", true)
val task3 = Task("Title3", "Description3", true)
tasksRepository.addTasks(task1, task2, task3)
tasksViewModel = TasksViewModel(tasksRepository)
}
- از آنجایی که دیگر از کد AndroidX Test
ApplicationProvider.getApplicationContext
استفاده نمی کنید، می توانید حاشیه نویسی@RunWith(AndroidJUnit4::class)
را نیز حذف کنید. - تست های خود را اجرا کنید، مطمئن شوید که همه آنها هنوز کار می کنند!
با استفاده از تزریق وابستگی سازنده، اکنون DefaultTasksRepository
به عنوان یک وابستگی حذف کردهاید و آن را با FakeTestRepository
خود در تستها جایگزین کردهاید.
مرحله 3. همچنین TaskDetail Fragment و ViewModel را به روز کنید
دقیقاً همان تغییرات را برای TaskDetailFragment
و TaskDetailViewModel
ایجاد کنید. این کد را برای زمانی که تست های بعدی TaskDetail
را می نویسید آماده می کند.
-
TaskDetailViewModel
باز کنید . - سازنده را به روز کنید:
TaskDetailViewModel.kt
// REPLACE
class TaskDetailViewModel(application: Application) : AndroidViewModel(application) {
private val tasksRepository = DefaultTasksRepository.getRepository(application)
// Rest of class
}
// WITH
class TaskDetailViewModel(
private val tasksRepository: TasksRepository
) : ViewModel() { // Rest of class }
- در پایین فایل
TaskDetailViewModel
، خارج از کلاس، یکTaskDetailViewModelFactory
اضافه کنید.
TaskDetailViewModel.kt
@Suppress("UNCHECKED_CAST")
class TaskDetailViewModelFactory (
private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>) =
(TaskDetailViewModel(tasksRepository) as T)
}
- برای استفاده از کارخانه،
TasksFragment
به روز کنید.
TasksFragment.kt
// REPLACE
private val viewModel by viewModels<TaskDetailViewModel>()
// WITH
private val viewModel by viewModels<TaskDetailViewModel> {
TaskDetailViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
- کد خود را اجرا کنید و مطمئن شوید که همه چیز کار می کند.
اکنون می توانید به جای مخزن واقعی در TasksFragment
و TasksDetailFragment
FakeTestRepository
استفاده کنید.
در مرحله بعد تست های ادغام را برای آزمایش تعامل قطعه و نمایش مدل خود می نویسید. خواهید فهمید که آیا کد مدل مشاهده شما به طور مناسب UI شما را به روز می کند یا خیر. برای این کار شما استفاده می کنید
- الگوی سرویس دهنده
- کتابخانه های اسپرسو و مسخره
تست های ادغام تعامل چندین کلاس را آزمایش کنید تا اطمینان حاصل شود که آنها همانطور که انتظار می رفت هنگام استفاده با هم رفتار می کنند. این آزمایشات را می توان به صورت محلی (مجموعه منبع test
) یا به عنوان تست های ابزار دقیق (مجموعه منبع androidTest
) اجرا کرد.
در مورد شما ، برای آزمایش ویژگی های اصلی این قطعه ، هر قطعه و تست های ادغام نوشتن را برای قطعه و مدل مشاهده می کنید.
مرحله 1. وابستگی های Gradle را اضافه کنید
- وابستگی های درجه زیر را اضافه کنید.
app/build.gradle
// Dependencies for Android instrumented unit tests
androidTestImplementation "junit:junit:$junitVersion"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
// Testing code should not be included in the main code.
// Once https://issuetracker.google.com/128612536 is fixed this can be fixed.
implementation "androidx.fragment:fragment-testing:$fragmentVersion"
implementation "androidx.test:core:$androidXTestCoreVersion"
این وابستگی ها عبارتند از:
-
junit:junit
- Junit ، که برای نوشتن بیانیه های آزمون اساسی لازم است. -
androidx.test:core
-
kotlinx-coroutines-test
کتابخانه تست Coroutines -
androidx.fragment:fragment-testing
برای ایجاد قطعات در آزمایشات و تغییر وضعیت آنها.
از آنجا که شما از این کتابخانه ها در مجموعه منبع androidTest
استفاده می کنید ، از androidTestImplementation
استفاده کنید تا آنها را به عنوان وابستگی اضافه کنید.
مرحله 2. یک کلاس TaskDetailFragmentTest درست کنید
TaskDetailFragment
اطلاعات مربوط به یک کار واحد را نشان می دهد.
شما با نوشتن یک آزمون قطعه برای TaskDetailFragment
شروع می کنید زیرا عملکرد نسبتاً اساسی در مقایسه با سایر قطعات دارد.
-
taskdetail.TaskDetailFragment
باز کنید . - همانطور که قبلاً انجام داده اید ، آزمایشی برای
TaskDetailFragment
ایجاد کنید . گزینه های پیش فرض را بپذیرید و آن را در مجموعه منبع AndroidTest قرار دهید (نه مجموعه منبعtest
).
- حاشیه نویسی های زیر را به کلاس
TaskDetailFragmentTest
اضافه کنید.
TaskDetailFragmentTest.kt
@MediumTest
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
}
هدف از این حاشیه نویسی:
-
@MediumTest
تست را به عنوان تست ادغام "زمان متوسط در زمان" (در مقابل تست های واحد@SmallTest
و@LargeTest
تست های بزرگ پایان به پایان) علامت گذاری می کند. این به شما کمک می کند تا گروهی را انتخاب کنید و اندازه آن را انتخاب کنید. -
@RunWith(AndroidJUnit4::class)
- در هر کلاس با استفاده از تست Androidx استفاده می شود.
مرحله 3. یک قطعه را از یک آزمایش راه اندازی کنید
در این کار ، شما می خواهید TaskDetailFragment
با استفاده از کتابخانه تست Androidx راه اندازی کنید. FragmentScenario
یک کلاس از AndroidX است که به دور یک قطعه می پیچد و به شما کنترل مستقیم چرخه عمر این قطعه را برای آزمایش می دهد. برای نوشتن تست برای قطعات ، شما برای قطعه ای که در حال آزمایش هستید ( TaskDetailFragment
) یک FragmentScenario
ایجاد می کنید.
- این تست را در
TaskDetailFragmentTest
کپی کنید .
TaskDetailFragmentTest.kt
@Test
fun activeTaskDetails_DisplayedInUi() {
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
این کد در بالا:
- یک کار ایجاد می کند.
- یک
Bundle
ایجاد می کند ، که نشان دهنده آرگومان های قطعه برای کاری است که به این قطعه منتقل می شود). - عملکرد
launchFragmentInContainer
با این بسته نرم افزاری و یک موضوع ، یکFragmentScenario
ایجاد می کند.
این هنوز یک تست تمام نشده است ، زیرا چیزی را ادعا نمی کند. در حال حاضر ، آزمایش را اجرا کنید و آنچه را که اتفاق می افتد مشاهده کنید.
- این یک تست ابزار دقیق است ، بنابراین اطمینان حاصل کنید که شبیه ساز یا دستگاه شما قابل مشاهده است.
- آزمون را اجرا کنید .
چند مورد باید اتفاق بیفتد.
- اول ، از آنجا که این یک تست ابزار دقیق است ، آزمایش روی دستگاه فیزیکی شما (در صورت اتصال) یا یک شبیه ساز اجرا می شود.
- باید این قطعه را راه اندازی کند.
- توجه کنید که چگونه از طریق هر قطعه دیگر حرکت نمی کند یا منوهای مرتبط با فعالیت را دارد - این فقط قطعه است.
سرانجام ، از نزدیک نگاه کنید و توجه کنید که این قطعه "بدون داده" می گوید زیرا با موفقیت داده های کار را بارگیری نمی کند.
آزمایش شما هر دو نیاز به بارگیری TaskDetailFragment
(که انجام داده اید) و ادعا می کنند که داده ها به درستی بارگیری شده اند. چرا هیچ داده ای وجود ندارد؟ این امر به این دلیل است که شما یک کار ایجاد کرده اید ، اما آن را در مخزن ذخیره نکردید.
@Test
fun activeTaskDetails_DisplayedInUi() {
// This DOES NOT save the task anywhere
val activeTask = Task("Active Task", "AndroidX Rocks", false)
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
شما این FakeTestRepository
دارید ، اما به راهی نیاز دارید تا مخزن واقعی خود را با جعلی خود جایگزین کنید. شما این کار را بعد انجام خواهید داد!
در این کار ، مخزن جعلی خود را با استفاده از یک ServiceLocator
ارائه می دهید. این به شما امکان می دهد قطعه خود را بنویسید و تست های ادغام مدل را مشاهده کنید.
شما نمی توانید از تزریق وابستگی سازنده در اینجا استفاده کنید ، همانطور که قبلاً انجام دادید ، در صورت نیاز به ارائه وابستگی به مدل نمایش یا مخزن. تزریق وابستگی سازنده مستلزم ساخت کلاس است. قطعات و فعالیت ها نمونه ای از کلاس هایی است که شما نمی سازید و به طور کلی به سازنده دسترسی ندارید.
از آنجا که شما این قطعه را نمی سازید ، نمی توانید از تزریق وابستگی سازنده برای تعویض تست مخزن دو برابر ( FakeTestRepository
) به این قطعه استفاده کنید. در عوض ، از الگوی سرویس یاب استفاده کنید. الگوی سرویس یاب سرویس جایگزینی برای تزریق وابستگی است. این شامل ایجاد یک کلاس Singleton به نام "Service Locator" است که هدف آن فراهم کردن وابستگی ها ، چه برای کد منظم و چه برای آزمون است. در کد برنامه معمولی (مجموعه منبع main
) ، همه این وابستگی ها وابستگی های معمولی برنامه هستند. برای تست ها ، شما یاب سرویس را اصلاح می کنید تا نسخه های دوتایی وابستگی ها را ارائه دهید.
استفاده از یاب سرویس | با استفاده از یاب سرویس |
برای این برنامه CodeLab ، موارد زیر را انجام دهید:
- یک کلاس یاب سرویس ایجاد کنید که قادر به ساخت و ذخیره مخزن باشد. به طور پیش فرض ، یک مخزن "عادی" ایجاد می کند.
- کد خود را به گونه ای تغییر دهید تا در صورت نیاز به مخزن ، از Service Locator استفاده کنید.
- در کلاس تست خود ، با یک روش در یاب سرویس تماس بگیرید که مخزن "عادی" را با دو برابر تست خود تعویض می کند.
مرحله 1. ServiceLocator را ایجاد کنید
بیایید یک کلاس ServiceLocator
بسازیم. این در منبع اصلی تنظیم شده با بقیه کد برنامه زندگی می کند زیرا توسط کد برنامه اصلی استفاده می شود.
توجه: ServiceLocator
یک تک آهنگ است ، بنابراین از کلمه کلیدی object
Kotlin برای کلاس استفاده کنید.
- File servicelocator.kt را در سطح بالای مجموعه منبع اصلی ایجاد کنید.
- یک
object
به نامServiceLocator
را تعریف کنید. - متغیرهای نمونه
database
وrepository
را ایجاد کنید و هر دو راnull
تنظیم کنید. - مخزن را با
@Volatile
حاشیه نویسی کنید زیرا می تواند توسط چندین موضوع استفاده شود (@Volatile
در اینجا به تفصیل توضیح داده شده است).
کد شما باید به صورت زیر نشان داده شود.
object ServiceLocator {
private var database: ToDoDatabase? = null
@Volatile
var tasksRepository: TasksRepository? = null
}
در حال حاضر تنها کاری که ServiceLocator
شما باید انجام دهد این است که بدانید چگونه یک TasksRepository
برگردانید. این یک DefaultTasksRepository
از قبل موجود را باز می گرداند یا در صورت لزوم یک DefaultTasksRepository
جدید را ساخته و باز می گرداند.
توابع زیر را تعریف کنید:
-
provideTasksRepository
- یک مخزن موجود در حال حاضر موجود را فراهم می کند یا یک مورد جدید ایجاد می کند. این روش باید درthis
synchronized
شود تا در موقعیت هایی با چندین موضوع اجرا شود ، تا کنون به طور تصادفی دو نمونه مخزن را ایجاد کند. -
createTasksRepository
- کد برای ایجاد یک مخزن جدید. باcreateTaskLocalDataSource
تماس می گیرید و یکTasksRemoteDataSource
جدید ایجاد می کند. -
createTaskLocalDataSource
- کد برای ایجاد یک منبع داده محلی جدید.createDataBase
تماس می گیرد. -
createDataBase
- کد برای ایجاد یک پایگاه داده جدید.
کد تکمیل شده در زیر است.
servicelocator.kt
object ServiceLocator {
private var database: ToDoDatabase? = null
@Volatile
var tasksRepository: TasksRepository? = null
fun provideTasksRepository(context: Context): TasksRepository {
synchronized(this) {
return tasksRepository ?: createTasksRepository(context)
}
}
private fun createTasksRepository(context: Context): TasksRepository {
val newRepo = DefaultTasksRepository(TasksRemoteDataSource, createTaskLocalDataSource(context))
tasksRepository = newRepo
return newRepo
}
private fun createTaskLocalDataSource(context: Context): TasksDataSource {
val database = database ?: createDataBase(context)
return TasksLocalDataSource(database.taskDao())
}
private fun createDataBase(context: Context): ToDoDatabase {
val result = Room.databaseBuilder(
context.applicationContext,
ToDoDatabase::class.java, "Tasks.db"
).build()
database = result
return result
}
}
مرحله 2 از Servicelocator در برنامه استفاده کنید
شما می خواهید در کد برنامه اصلی خود (نه تست های خود) تغییری ایجاد کنید تا مخزن را در یک مکان ، ServiceLocator
خود ایجاد کنید.
این مهم است که شما فقط یک نمونه از کلاس مخزن را تهیه کنید. برای اطمینان از این امر ، در کلاس برنامه کاربردی من از سرویس دهنده خدمات استفاده خواهید کرد.
- در سطح بالای سلسله مراتب بسته بندی خود
TodoApplication
باز کنید و یکval
خود را ایجاد کنید و مخزن آن را اختصاص دهید که با استفاده ازServiceLocator.provideTaskRepository
به دست می آید.
todoapplication.kt
class TodoApplication : Application() {
val taskRepository: TasksRepository
get() = ServiceLocator.provideTasksRepository(this)
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(DebugTree())
}
}
اکنون که یک مخزن در برنامه ایجاد کرده اید ، می توانید روش قدیمی getRepository
را در DefaultTasksRepository
حذف کنید.
-
DefaultTasksRepository
را باز کرده و شیء همراه را حذف کنید.
defaulttasksrepository.kt
// DELETE THIS COMPANION OBJECT
companion object {
@Volatile
private var INSTANCE: DefaultTasksRepository? = null
fun getRepository(app: Application): DefaultTasksRepository {
return INSTANCE ?: synchronized(this) {
val database = Room.databaseBuilder(app,
ToDoDatabase::class.java, "Tasks.db")
.build()
DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
INSTANCE = it
}
}
}
}
اکنون در هر کجا که از getRepository
استفاده می کردید ، به جای آن از taskRepository
برنامه استفاده کنید. این تضمین می کند که به جای اینکه مخزن را مستقیماً تهیه کنید ، هر نوع مخزن را که ServiceLocator
ارائه شده است دریافت می کنید.
-
TaskDetailFragement
را باز کنید و فراخوانی برایgetRepository
را در بالای کلاس پیدا کنید. - این تماس را با تماس تلفنی جایگزین کنید که مخزن را از
TodoApplication
دریافت می کند.
TaskDetailFragment.kt
// REPLACE this code
private val viewModel by viewModels<TaskDetailViewModel> {
TaskDetailViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
// WITH this code
private val viewModel by viewModels<TaskDetailViewModel> {
TaskDetailViewModelFactory((requireContext().applicationContext as TodoApplication).taskRepository)
}
- همین کار را برای
TasksFragment
انجام دهید.
TaskSfragment.kt
// REPLACE this code
private val viewModel by viewModels<TasksViewModel> {
TasksViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
// WITH this code
private val viewModel by viewModels<TasksViewModel> {
TasksViewModelFactory((requireContext().applicationContext as TodoApplication).taskRepository)
}
- برای
StatisticsViewModel
وAddEditTaskViewModel
، کدی را که مخزن را به دست می آورد ، به روز کنید تا از مخزن ازTodoApplication
استفاده کنید.
TaskSfragment.kt
// REPLACE this code
private val tasksRepository = DefaultTasksRepository.getRepository(application)
// WITH this code
private val tasksRepository = (application as TodoApplication).taskRepository
- برنامه خود را اجرا کنید (نه آزمون)!
از آنجا که شما فقط مجدداً مورد استفاده قرار می گیرید ، برنامه باید بدون مشکل همین کار را اجرا کند.
مرحله 3. ایجاد fakeandidoidtesterpository
شما قبلاً در مجموعه منبع آزمایش یک FakeTestRepository
دارید. شما نمی توانید کلاس های تست را بین مجموعه های test
و androidTest
به طور پیش فرض به اشتراک بگذارید. بنابراین ، شما باید یک کلاس FakeTestRepository
کپی در مجموعه منبع androidTest
بسازید و آن را FakeAndroidTestRepository
بنامید.
- روی مجموعه منبع
androidTest
راست کلیک کرده و یک بسته داده را تهیه کنید. دوباره کلیک راست کرده و یک بسته منبع درست کنید. - یک کلاس جدید را در این بسته منبع به نام
FakeAndroidTestRepository.kt
ایجاد کنید. - کد زیر را در آن کلاس کپی کنید.
fakeandroidtestrepository.kt
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.runBlocking
import java.util.LinkedHashMap
class FakeAndroidTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private var shouldReturnError = false
private val observableTasks = MutableLiveData<Result<List<Task>>>()
fun setReturnError(value: Boolean) {
shouldReturnError = value
}
override suspend fun refreshTasks() {
observableTasks.value = getTasks()
}
override suspend fun refreshTask(taskId: String) {
refreshTasks()
}
override fun observeTasks(): LiveData<Result<List<Task>>> {
runBlocking { refreshTasks() }
return observableTasks
}
override fun observeTask(taskId: String): LiveData<Result<Task>> {
runBlocking { refreshTasks() }
return observableTasks.map { tasks ->
when (tasks) {
is Result.Loading -> Result.Loading
is Error -> Error(tasks.exception)
is Success -> {
val task = tasks.data.firstOrNull() { it.id == taskId }
?: return@map Error(Exception("Not found"))
Success(task)
}
}
}
}
override suspend fun getTask(taskId: String, forceUpdate: Boolean): Result<Task> {
if (shouldReturnError) {
return Error(Exception("Test exception"))
}
tasksServiceData[taskId]?.let {
return Success(it)
}
return Error(Exception("Could not find task"))
}
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
if (shouldReturnError) {
return Error(Exception("Test exception"))
}
return Success(tasksServiceData.values.toList())
}
override suspend fun saveTask(task: Task) {
tasksServiceData[task.id] = task
}
override suspend fun completeTask(task: Task) {
val completedTask = Task(task.title, task.description, true, task.id)
tasksServiceData[task.id] = completedTask
}
override suspend fun completeTask(taskId: String) {
// Not required for the remote data source.
throw NotImplementedError()
}
override suspend fun activateTask(task: Task) {
val activeTask = Task(task.title, task.description, false, task.id)
tasksServiceData[task.id] = activeTask
}
override suspend fun activateTask(taskId: String) {
throw NotImplementedError()
}
override suspend fun clearCompletedTasks() {
tasksServiceData = tasksServiceData.filterValues {
!it.isCompleted
} as LinkedHashMap<String, Task>
}
override suspend fun deleteTask(taskId: String) {
tasksServiceData.remove(taskId)
refreshTasks()
}
override suspend fun deleteAllTasks() {
tasksServiceData.clear()
refreshTasks()
}
fun addTasks(vararg tasks: Task) {
for (task in tasks) {
tasksServiceData[task.id] = task
}
runBlocking { refreshTasks() }
}
}
مرحله 4. سرویس دهنده خود را برای آزمایش آماده کنید
خوب ، زمان استفاده از ServiceLocator
برای تعویض در تست در هنگام آزمایش. برای انجام این کار ، باید مقداری کد را به کد ServiceLocator
خود اضافه کنید.
- Open
ServiceLocator.kt
. - تنظیم کننده
tasksRepository
را به عنوان@VisibleForTesting
علامت گذاری کنید. این حاشیه نویسی راهی برای بیان این است که دلیل عمومی بودن تنظیم کننده به دلیل آزمایش است.
servicelocator.kt
@Volatile
var tasksRepository: TasksRepository? = null
@VisibleForTesting set
چه تست خود را به تنهایی یا در گروهی از تست ها اجرا کنید ، تست های شما باید دقیقاً یکسان باشد. این بدان معنی است که تست های شما نباید رفتاری داشته باشند که به یکدیگر وابسته باشد (این به معنای جلوگیری از به اشتراک گذاری اشیاء بین تست ها است).
از آنجا که ServiceLocator
یک مجرد است ، این امکان را دارد که به طور تصادفی بین آزمایشات به اشتراک گذاشته شود. برای جلوگیری از این امر ، روشی را ایجاد کنید که به درستی حالت ServiceLocator
بین تست ها را مجدداً تنظیم کند.
- یک متغیر نمونه به نام
lock
باAny
مقدار اضافه کنید.
servicelocator.kt
private val lock = Any()
- یک روش خاص آزمایش به نام
resetRepository
را اضافه کنید که پایگاه داده را پاک می کند و هم مخزن و هم پایگاه داده را به NULL تنظیم می کند.
servicelocator.kt
@VisibleForTesting
fun resetRepository() {
synchronized(lock) {
runBlocking {
TasksRemoteDataSource.deleteAllTasks()
}
// Clear all data to avoid test pollution.
database?.apply {
clearAllTables()
close()
}
database = null
tasksRepository = null
}
}
مرحله 5 از سرویس دهنده خود استفاده کنید
در این مرحله از ServiceLocator
استفاده می کنید.
-
TaskDetailFragmentTest
را باز کنید . - متغیر
lateinit TasksRepository
اعلام کنید. - قبل از هر آزمایش ، یک تنظیم و یک روش پاره کردن را برای تنظیم یک
FakeAndroidTestRepository
اضافه کرده و بعد از هر آزمایش آن را تمیز کنید.
TaskDetailFragmentTest.kt
private lateinit var repository: TasksRepository
@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}
@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}
- بدنه عملکرد
activeTaskDetails_DisplayedInUi()
را درrunBlockingTest
بپیچانید. - قبل از راه اندازی این قطعه ،
activeTask
در مخزن ذخیره کنید.
repository.saveTask(activeTask)
آزمون نهایی مانند این کد در زیر به نظر می رسد.
TaskDetailFragmentTest.kt
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
- کل کلاس را با
@ExperimentalCoroutinesApi
حاشیه نویسی کنید.
پس از اتمام ، کد به این شکل خواهد بود.
TaskDetailFragmentTest.kt
@MediumTest
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
private lateinit var repository: TasksRepository
@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}
@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
}
- آزمایش
activeTaskDetails_DisplayedInUi()
را اجرا کنید.
دقیقاً مانند گذشته ، شما باید این قطعه را به جز این بار مشاهده کنید ، زیرا به درستی مخزن را تنظیم کرده اید ، اکنون اطلاعات کار را نشان می دهد.
در این مرحله از کتابخانه تست Espresso UI برای تکمیل اولین تست ادغام خود استفاده خواهید کرد. شما کد خود را ساختار داده اید تا بتوانید با ادعاهای مربوط به UI خود تست ها را اضافه کنید. برای انجام این کار ، از کتابخانه تست اسپرسو استفاده خواهید کرد.
اسپرسو به شما کمک می کند:
- تعامل با نمای ، مانند کلیک بر روی دکمه ها ، کشویی یک نوار یا پیمایش به پایین صفحه.
- ادعا کنید که دیدگاه های خاصی روی صفحه نمایش قرار دارند یا در حالت خاصی قرار دارند (مانند حاوی متن خاص ، یا اینکه یک کادر چک بررسی می شود و غیره).
مرحله 1. وابستگی درجه یک توجه داشته باشید
شما در حال حاضر وابستگی اصلی اسپرسو را خواهید داشت زیرا به طور پیش فرض در پروژه های Android گنجانده شده است.
app/build.gradle
dependencies {
// ALREADY in your code
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
// Other dependencies
}
androidx.test.espresso:espresso-core
این وابستگی اصلی اسپرسو هنگام تهیه یک پروژه جدید Android به طور پیش فرض درج شده است. این شامل کد تست اساسی برای اکثر نمایش ها و اقدامات مربوط به آنها است.
مرحله 2 انیمیشن ها را خاموش کنید
تست های اسپرسو بر روی یک دستگاه واقعی اجرا می شوند و بنابراین تست های ابزار دقیق از نظر طبیعت هستند. یکی از موضوعاتی که ایجاد می شود انیمیشن ها است: اگر یک انیمیشن عقب بماند و سعی کنید اگر یک نمای روی صفحه باشد ، آزمایش کنید ، اما هنوز هم انیمیشن است ، اسپرسو می تواند به طور تصادفی یک آزمایش را شکست دهد. این می تواند آزمایش های اسپرسو پوسته پوسته شود.
برای آزمایش UI اسپرسو ، بهترین تمرین برای خاموش کردن انیمیشن ها (همچنین تست شما سریعتر اجرا می شود!):
- در دستگاه تست خود به تنظیمات> گزینه های توسعه دهنده بروید.
- این سه تنظیمات را غیرفعال کنید: مقیاس انیمیشن پنجره ، مقیاس انیمیشن انتقال و مقیاس مدت زمان انیماتور .
مرحله 3. به یک تست اسپرسو نگاه کنید
قبل از نوشتن تست اسپرسو ، به برخی از کد های اسپرسو نگاهی بیندازید.
onView(withId(R.id.task_detail_complete_checkbox)).perform(click()).check(matches(isChecked()))
آنچه این عبارت انجام می دهد ، یافتن نمای کادر با ID task_detail_complete_checkbox
، روی آن کلیک می کند ، سپس ادعا می کند که بررسی شده است.
اکثر اظهارات اسپرسو از چهار بخش تشکیل شده است:
onView
onView
نمونه ای از یک روش اسپرسو استاتیک است که بیانیه اسپرسو را شروع می کند. onView
یکی از رایج ترین موارد است ، اما گزینه های دیگری مانند onData
وجود دارد.
2. ViewMatcher
withId(R.id.task_detail_title_text)
withId
نمونه ای از ViewMatcher
است که با شناسه آن نمای می شود. تطبیق های دید دیگری وجود دارد که می توانید در اسناد و مدارک جستجو کنید.
3. مشاهده
perform(click())
روش perform
که ViewAction
می کند. یک ViewAction
کاری است که می تواند به نمای انجام شود ، به عنوان مثال در اینجا ، روی نمای کلیک می کند.
check(matches(isChecked()))
check
کدام یک از ViewAssertion
را می گیرد. ViewAssertion
S را در مورد نمایش بررسی یا ادعا می کند. رایج ترین ViewAssertion
شما استفاده می کنید ادعای matches
است. برای به پایان رساندن این ادعا ، از یک ViewMatcher
دیگر استفاده کنید ، در این مورد isChecked
.
توجه داشته باشید که شما همیشه با هر دو perform
تماس نمی گیرید و در بیانیه اسپرسو check
. شما می توانید بیانیه هایی داشته باشید که فقط با استفاده از check
ادعا می کنند یا فقط با استفاده از perform
یک ViewAction
انجام می دهند.
-
TaskDetailFragmentTest.kt
را باز کنید . - تست
activeTaskDetails_DisplayedInUi
را به روز کنید .
TaskDetailFragmentTest.kt
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
// THEN - Task details are displayed on the screen
// make sure that the title/description are both shown and correct
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_title_text)).check(matches(withText("Active Task")))
onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
// and make sure the "active" checkbox is shown unchecked
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(not(isChecked())))
}
در اینجا بیانیه های واردات ، در صورت لزوم:
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.core.IsNot.not
- همه چیز بعد از
// THEN
نظر از اسپرسو استفاده می کند. ساختار تست و استفاده ازwithId
را بررسی کنید و بررسی کنید تا در مورد نحوه نگاه صفحه جزئیات ادعا کنید. - آزمون را اجرا کنید و آن را تأیید کنید.
مرحله 4. اختیاری ، تست اسپرسو خود را بنویسید
حالا خودتان یک تست بنویسید.
- یک آزمایش جدید به نام
completedTaskDetails_DisplayedInUi
ایجاد کنید و این کد اسکلت را کپی کنید.
TaskDetailFragmentTest.kt
@Test
fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add completed task to the DB
// WHEN - Details fragment launched to display task
// THEN - Task details are displayed on the screen
// make sure that the title/description are both shown and correct
}
- با نگاهی به آزمون قبلی ، این تست را کامل کنید .
- پاس های آزمون را اجرا کنید و تأیید کنید.
تمام شده به پایان completedTaskDetails_DisplayedInUi
باید مانند این کد باشد.
TaskDetailFragmentTest.kt
@Test
fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add completed task to the DB
val completedTask = Task("Completed Task", "AndroidX Rocks", true)
repository.saveTask(completedTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(completedTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
// THEN - Task details are displayed on the screen
// make sure that the title/description are both shown and correct
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_title_text)).check(matches(withText("Completed Task")))
onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
// and make sure the "active" checkbox is shown unchecked
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isChecked()))
}
در این مرحله آخر ، شما می توانید نحوه آزمایش مؤلفه ناوبری را با استفاده از نوع دیگری از تست دو برابر به نام Mock و کتابخانه آزمایش Mockito یاد بگیرید.
در این CodeLab شما از یک تست دو برابر به نام جعلی استفاده کرده اید. تقلبی یکی از انواع مختلف دو برابر تست است. برای آزمایش مؤلفه ناوبری از کدام تست دو برابر باید استفاده کنید؟
در مورد چگونگی وقوع ناوبری فکر کنید. تصور کنید که یکی از وظایف موجود در TasksFragment
را برای حرکت به صفحه جزئیات کار فشار دهید.
در اینجا کد در TasksFragment
وجود دارد که هنگام فشار دادن به صفحه نمایش جزئیات کار می کند.
TaskSfragment.kt
private fun openTaskDetails(taskId: String) {
val action = TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment(taskId)
findNavController().navigate(action)
}
ناوبری به دلیل تماس با روش navigate
اتفاق می افتد. اگر نیاز به نوشتن یک بیانیه ادعا دارید ، راهی ساده برای آزمایش اینکه آیا به TaskDetailFragment
حرکت کرده اید وجود ندارد. پیمایش یک اقدام پیچیده است که منجر به تغییر و تحول در حالت روشن یا تغییر حالت نمی شود ، فراتر از اولیه سازی TaskDetailFragment
.
آنچه می توانید ادعا کنید این است که روش navigate
با پارامتر عمل صحیح فراخوانی شده است. این دقیقاً همان کاری است که یک تست مسخره انجام می دهد - این بررسی می کند که آیا روش های خاص خوانده شده است یا خیر.
Mockito چارچوبی برای ساخت دو برابر تست است. در حالی که کلمه مسخره در API و نام استفاده می شود ، فقط برای ساختن مسخره نیست . همچنین می تواند خرد و جاسوسی ایجاد کند.
شما از Mockito برای ساختن یک NavigationController
مسخره استفاده خواهید کرد که می تواند ادعا کند که روش پیمایش به درستی خوانده می شود.
مرحله 1. وابستگی های Gradle را اضافه کنید
- وابستگی های Gradle را اضافه کنید .
app/build.gradle
// Dependencies for Android instrumented unit tests
androidTestImplementation "org.mockito:mockito-core:$mockitoVersion"
androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:$dexMakerVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
org.mockito:mockito-core
این وابستگی مسخره است.-
dexmaker-mockito
این کتابخانه موظف است از Mockito در یک پروژه Android استفاده کند. Mockito باید در زمان اجرا کلاس تولید کند. در Android ، این کار با استفاده از کد Dex Byte انجام می شود ، بنابراین این کتابخانه Mockito را قادر می سازد تا در زمان اجرا در Android اشیاء تولید کند. -
androidx.test.espresso:espresso-contrib
این کتابخانه از مشارکتهای خارجی (از این رو نام) تشکیل شده است که حاوی کد آزمایش برای نماهای پیشرفته تر مانندDatePicker
وRecyclerView
است. همچنین شامل چک های دسترسی و کلاس به نامCountingIdlingResource
است که بعداً تحت پوشش قرار می گیرد.
مرحله 2. ایجاد TaskSfragmentTest
-
TasksFragment
باز کردن. - بر روی نام کلاس
TasksFragment
راست کلیک کرده و Generate را انتخاب کنید و سپس تست کنید . در مجموعه منبع AndroidTest تست ایجاد کنید. - این کد را در
TasksFragmentTest
کپی کنید.
TaskSfragmentTest.kt
@RunWith(AndroidJUnit4::class)
@MediumTest
@ExperimentalCoroutinesApi
class TasksFragmentTest {
private lateinit var repository: TasksRepository
@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}
@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}
}
این کد شبیه به کد TaskDetailFragmentTest
است که شما نوشتید. آن را تنظیم می کند و یک FakeAndroidTestRepository
را پاره می کند. یک تست ناوبری اضافه کنید تا آزمایش کنید که وقتی روی یک کار در لیست کار کلیک می کنید ، شما را به سمت TaskDetailFragment
صحیح می برد.
- تست
clickTask_navigateToDetailFragmentOne
اضافه کنید .
TaskSfragmentTest.kt
@Test
fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))
// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
}
- برای ایجاد مسخره از عملکرد
mock
Mockito استفاده کنید.
TaskSfragmentTest.kt
val navController = mock(NavController::class.java)
برای مسخره کردن در Mockito ، در کلاس که می خواهید مسخره کنید عبور کنید.
در مرحله بعد ، شما باید NavController
خود را با این قطعه مرتبط کنید. onFragment
به شما امکان می دهد روش هایی را در مورد خود قطعه فراخوانی کنید.
- مسخره جدید خود را
NavController
قطعه قطعه کنید.
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}
- کد را اضافه کنید تا روی مورد در
RecyclerView
که دارای متن "عنوان 1" است کلیک کنید.
// WHEN - Click on the first list item
onView(withId(R.id.tasks_list))
.perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText("TITLE1")), click()))
RecyclerViewActions
بخشی از کتابخانه espresso-contrib
است و به شما امکان می دهد اقدامات اسپرسو را در یک بازیافت انجام دهید.
- تأیید کنید که با استدلال صحیح ،
navigate
نامیده شد.
// THEN - Verify that we navigate to the first detail screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1")
روش verify
Mockito همان چیزی است که این مسئله را مسخره می کند - شما قادر به تأیید navController
مسخره به نام یک روش خاص ( navigate
) با یک پارامتر ( actionTasksFragmentToTaskDetailFragment
با شناسه "ID1") هستید.
تست کامل به این شکل است:
@Test
fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))
// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
val navController = mock(NavController::class.java)
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}
// WHEN - Click on the first list item
onView(withId(R.id.tasks_list))
.perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText("TITLE1")), click()))
// THEN - Verify that we navigate to the first detail screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1")
)
}
- آزمون خود را اجرا کنید!
به طور خلاصه ، برای آزمایش ناوبری می توانید:
- برای ایجاد مسخره
NavController
از Mockito استفاده کنید. - آن
NavController
مسخره را به قطعه وصل کنید. - تأیید کنید که پیمایش با عمل صحیح و پارامتر (ها) فراخوانی شده است.
مرحله 3 اختیاری ، نوشتن clickaddtaskbutton_navigatetoaddeditfragment
برای دیدن اینکه آیا می توانید خودتان یک آزمایش ناوبری بنویسید ، این کار را امتحان کنید.
- تست
clickAddTaskButton_navigateToAddEditFragment
را بنویسید که بررسی می کند که اگر روی + fab کلیک کنید ، به سمتAddEditTaskFragment
اضافه می کنید.
پاسخ در زیر آمده است.
TaskSfragmentTest.kt
@Test
fun clickAddTaskButton_navigateToAddEditFragment() {
// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
val navController = mock(NavController::class.java)
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}
// WHEN - Click on the "+" button
onView(withId(R.id.add_task_fab)).perform(click())
// THEN - Verify that we navigate to the add screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToAddEditTaskFragment(
null, getApplicationContext<Context>().getString(R.string.add_task)
)
)
}
برای دیدن تفاوت بین کدی که شروع کرده اید و کد نهایی ، اینجا را کلیک کنید.
برای بارگیری کد برای CodeLab تمام شده ، می توانید از دستور GIT در زیر استفاده کنید:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_2
از طرف دیگر می توانید مخزن را به عنوان یک فایل ZIP بارگیری کنید ، آن را از حالت فشرده خارج کنید و آن را در Android Studio باز کنید.
این CodeLab نحوه تنظیم تزریق وابستگی دستی ، یک سرویس دهنده خدمات و نحوه استفاده از جعل و مسخره در برنامه های Android Kotlin خود را پوشش می دهد. به طور خاص:
- آنچه می خواهید آزمایش کنید و استراتژی تست خود انواع آزمایشی را که می خواهید برای برنامه خود اجرا کنید تعیین کنید. تست های واحد متمرکز و سریع هستند. تست های ادغام تعامل بین بخش هایی از برنامه شما را تأیید می کند. تست های پایان به پایان ، ویژگی ها را تأیید می کنند ، بالاترین وفاداری را دارند ، اغلب مورد استفاده قرار می گیرند و ممکن است بیشتر طول بکشد.
- معماری برنامه شما تأثیر می گذارد که آزمایش چقدر سخت است.
- توسعه TDD یا تست محور یک استراتژی است که در آن ابتدا تست ها را می نویسید ، سپس این ویژگی را برای گذراندن تست ها ایجاد کنید.
- برای جداسازی بخش هایی از برنامه خود برای آزمایش ، می توانید از دو برابر تست استفاده کنید. Test Double نسخه ای از کلاس است که به طور خاص برای آزمایش ساخته شده است. به عنوان مثال ، شما داده های دریافت داده از یک پایگاه داده یا اینترنت را جعلی می کنید.
- از تزریق وابستگی برای جایگزینی یک کلاس واقعی با یک کلاس آزمایش استفاده کنید ، به عنوان مثال ، یک مخزن یا یک لایه شبکه.
- برای راه اندازی اجزای UI از تست Nstrumented (
androidTest
) استفاده کنید. - هنگامی که نمی توانید از تزریق وابستگی سازنده استفاده کنید ، به عنوان مثال برای راه اندازی یک قطعه ، اغلب می توانید از یک سرویس دهنده خدمات استفاده کنید. الگوی سرویس یاب سرویس جایگزینی برای تزریق وابستگی است. این شامل ایجاد یک کلاس Singleton به نام "Service Locator" است که هدف آن فراهم کردن وابستگی ها ، چه برای کد منظم و چه برای آزمون است.
دوره بی ادبی:
مستندات توسعه دهنده اندروید:
- راهنمای معماری اپلیکیشن
-
runBlocking
وrunBlockingTest
-
FragmentScenario
- اسپرسو
- مسخره
- junit4
- کتابخانه تست Androidx
- کتابخانه تست اصلی Androidx Components Components
- مجموعه منبع
- از خط فرمان آزمون کنید
ویدئوها:
دیگر:
برای پیوندها به سایر CodeLabs در این دوره ، به صفحه Advanced Android در Kotlin Codelabs Landing مراجعه کنید.