پیاده سازی کتابخانه Paging در برنامه شما باید با یک استراتژی تست قوی همراه باشد. شما باید اجزای بارگذاری داده مانند PagingSource
و RemoteMediator
را آزمایش کنید تا مطمئن شوید که مطابق انتظار کار می کنند. همچنین باید تستهای سرتاسری بنویسید تا بررسی کنید که همه اجزای موجود در پیادهسازی Paging به درستی بدون عوارض جانبی غیرمنتظره با هم کار میکنند.
این راهنما نحوه آزمایش کتابخانه Paging در لایههای معماری مختلف برنامه و همچنین نحوه نوشتن تستهای سرتاسر برای کل پیادهسازی Paging را توضیح میدهد.
تست لایه رابط کاربری
دادههای واکشی شده با کتابخانه Paging در رابط کاربری بهعنوان یک Flow<PagingData<Value>>
مصرف میشوند. برای نوشتن آزمایشهایی برای تأیید دادهها در رابط کاربری همانطور که انتظار دارید، وابستگی paging-testing
نیز در نظر بگیرید. این شامل پسوند asSnapshot()
در یک Flow<PagingData<Value>>
است. این APIها را در گیرنده لامبدا خود ارائه می دهد که امکان تعاملات اسکرول مسخره را فراهم می کند. یک List<Value>
استاندارد را برمیگرداند که توسط تعاملات پیمایشی مسخرهشده تولید شده است، که به شما امکان میدهد دادههای صفحهشده حاوی عناصر مورد انتظار تولید شده توسط آن تعاملها باشد. این در قطعه زیر نشان داده شده است:
fun test_items_contain_one_to_ten() = runTest {
// Get the Flow of PagingData from the ViewModel under test
val items: Flow<PagingData<String>> = viewModel.items
val itemsSnapshot: List<String> = items.asSnapshot {
// Scroll to the 50th item in the list. This will also suspend till
// the prefetch requirement is met if there's one.
// It also suspends until all loading is complete.
scrollTo(index = 50)
}
// With the asSnapshot complete, you can now verify that the snapshot
// has the expected values
assertEquals(
expected = (0..50).map(Int::toString),
actual = itemsSnapshot
)
}
همچنین، میتوانید پیمایش کنید تا زمانی که یک گزاره داده شده، همانطور که در قطعه زیر مشاهده میشود، برآورده شود:
fun test_footer_is_visible() = runTest {
// Get the Flow of PagingData from the ViewModel under test
val items: Flow<PagingData<String>> = viewModel.items
val itemsSnapshot: List<String> = items.asSnapshot {
// Scroll till the footer is visible
appendScrollWhile { item: String -> item != "Footer" }
}
آزمایش تحولات
همچنین باید آزمونهای واحدی بنویسید که هر تغییری را که در جریان PagingData
اعمال میکنید، پوشش دهد. از پسوند asPagingSourceFactory
استفاده کنید. این برنامه افزودنی در انواع داده های زیر موجود است:
-
List<Value>
. -
Flow<List<Value>>
.
انتخاب برنامه افزودنی برای استفاده بستگی به چیزی دارد که می خواهید آزمایش کنید. استفاده کنید:
-
List<Value>.asPagingSourceFactory()
: اگر می خواهید تبدیل های استاتیک مانندmap()
وinsertSeparators()
را روی داده ها آزمایش کنید. -
Flow<List<Value>>.asPagingSourceFactory()
: اگر میخواهید آزمایش کنید که چگونه بهروزرسانیهای دادههای شما، مانند نوشتن در منبع داده پشتیبان، بر خط لوله صفحهبندی شما تأثیر میگذارد.
برای استفاده از هر یک از این پسوندها از الگوی زیر پیروی کنید:
-
PagingSourceFactory
با استفاده از برنامه افزودنی مناسب برای نیازهای خود ایجاد کنید. - از
PagingSourceFactory
بازگشتی به صورت جعلی برایRepository
خود استفاده کنید. - آن
Repository
را بهViewModel
خود منتقل کنید.
سپس ViewModel
می توان همانطور که در بخش قبل توضیح داده شد آزمایش کرد. ViewModel
زیر را در نظر بگیرید:
class MyViewModel(
myRepository: myRepository
) {
val items = Pager(
config: PagingConfig,
initialKey = null,
pagingSourceFactory = { myRepository.pagingSource() }
)
.flow
.map { pagingData ->
pagingData.insertSeparators<String, String> { before, _ ->
when {
// Add a dashed String separator if the prior item is a multiple of 10
before.last() == '0' -> "---------"
// Return null to avoid adding a separator between two items.
else -> null
}
}
}
برای آزمایش تبدیل در MyViewModel
، یک نمونه جعلی از MyRepository
ارائه دهید که به List
ثابتی که نشاندهنده دادههایی است که باید تبدیل شوند، همانطور که در قطعه زیر نشان داده شده است، تفویض میکند:
class FakeMyRepository(): MyRepository {
private val items = (0..100).map(Any::toString)
private val pagingSourceFactory = items.asPagingSourceFactory()
val pagingSource = pagingSourceFactory()
}
سپس می توانید مانند قطعه زیر یک تست برای منطق جداکننده بنویسید:
fun test_separators_are_added_every_10_items() = runTest {
// Create your ViewModel
val viewModel = MyViewModel(
myRepository = FakeMyRepository()
)
// Get the Flow of PagingData from the ViewModel with the separator transformations applied
val items: Flow<PagingData<String>> = viewModel.items
val snapshot: List<String> = items.asSnapshot()
// With the asSnapshot complete, you can now verify that the snapshot
// has the expected separators.
}
تست لایه داده
تست های واحد را برای اجزای لایه داده خود بنویسید تا مطمئن شوید که آنها داده ها را از منابع داده شما به درستی بارگذاری می کنند. نسخههای جعلی وابستگیها را ارائه کنید تا تأیید کنید که اجزای تحت آزمایش بهصورت مجزا به درستی کار میکنند. اجزای اصلی که باید در لایه مخزن آزمایش کنید، PagingSource
و RemoteMediator
هستند. مثالهای موجود در بخشهای بعدی بر اساس صفحهبندی با نمونه شبکه است.
تست های PagingSource
تستهای واحد برای پیادهسازی PagingSource
شامل راهاندازی نمونه PagingSource
و بارگیری دادهها از آن با TestPager
است.
برای تنظیم نمونه PagingSource
برای آزمایش، داده های جعلی را در اختیار سازنده قرار دهید. این به شما امکان کنترل داده ها را در آزمایش های خود می دهد. در مثال زیر، پارامتر RedditApi
یک رابط Retrofit است که درخواستهای سرور و کلاسهای پاسخ را تعریف میکند. یک نسخه جعلی میتواند رابط را پیادهسازی کند، همه عملکردهای مورد نیاز را لغو کند، و روشهای راحت را برای پیکربندی نحوه واکنش شی جعلی در آزمایشها ارائه دهد.
پس از اینکه جعلی ها در جای خود قرار گرفتند، وابستگی ها را تنظیم کنید و شی PagingSource
را در تست مقداردهی اولیه کنید. مثال زیر مقدار دهی اولیه شی FakeRedditApi
را با لیستی از پست های آزمایشی و آزمایش نمونه RedditPagingSource
نشان می دهد:
class SubredditPagingSourceTest {
private val mockPosts = listOf(
postFactory.createRedditPost(DEFAULT_SUBREDDIT),
postFactory.createRedditPost(DEFAULT_SUBREDDIT),
postFactory.createRedditPost(DEFAULT_SUBREDDIT)
)
private val fakeApi = FakeRedditApi().apply {
mockPosts.forEach { post -> addPost(post) }
}
@Test
fun loadReturnsPageWhenOnSuccessfulLoadOfItemKeyedData() = runTest {
val pagingSource = RedditPagingSource(
fakeApi,
DEFAULT_SUBREDDIT
)
val pager = TestPager(CONFIG, pagingSource)
val result = pager.refresh() as LoadResult.Page
// Write assertions against the loaded data
assertThat(result.data)
.containsExactlyElementsIn(mockPosts)
.inOrder()
}
}
TestPager
همچنین به شما امکان می دهد کارهای زیر را انجام دهید:
- بارهای متوالی را از
PagingSource
خود آزمایش کنید:
@Test
fun test_consecutive_loads() = runTest {
val page = with(pager) {
refresh()
append()
append()
} as LoadResult.Page
assertThat(page.data)
.containsExactlyElementsIn(testPosts)
.inOrder()
}
- تست سناریوهای خطا در
PagingSource
:
@Test
fun refresh_returnError() {
val pagingSource = RedditPagingSource(
fakeApi,
DEFAULT_SUBREDDIT
)
// Configure your fake to return errors
fakeApi.setReturnsError()
val pager = TestPager(CONFIG, source)
runTest {
source.errorNextLoad = true
val result = pager.refresh()
assertTrue(result is LoadResult.Error)
val page = pager.getLastLoadedPage()
assertThat(page).isNull()
}
}
تست های RemoteMediator
هدف از تست های واحد RemoteMediator
این است که تأیید کند که تابع load()
MediatorResult
صحیح را برمی گرداند. آزمایشهای عوارض جانبی، مانند دادههایی که در پایگاه داده درج میشوند، برای آزمایشهای ادغام مناسبتر هستند.
اولین قدم این است که تعیین کنید پیاده سازی RemoteMediator
شما به چه وابستگی هایی نیاز دارد. مثال زیر اجرای RemoteMediator
را نشان می دهد که به یک پایگاه داده اتاق، یک رابط Retrofit و یک رشته جستجو نیاز دارد:
کاتلین
@OptIn(ExperimentalPagingApi::class) class PageKeyedRemoteMediator( private val db: RedditDb, private val redditApi: RedditApi, private val subredditName: String ) : RemoteMediator<Int, RedditPost>() { ... }
جاوا
public class PageKeyedRemoteMediator extends RxRemoteMediator<Integer, RedditPost> { @NonNull private RedditDb db; @NonNull private RedditPostDao postDao; @NonNull private SubredditRemoteKeyDao remoteKeyDao; @NonNull private RedditApi redditApi; @NonNull private String subredditName; public PageKeyedRemoteMediator( @NonNull RedditDb db, @NonNull RedditApi redditApi, @NonNull String subredditName ) { this.db = db; this.postDao = db.posts(); this.remoteKeyDao = db.remoteKeys(); this.redditApi = redditApi; this.subredditName = subredditName; ... } }
جاوا
public class PageKeyedRemoteMediator extends ListenableFutureRemoteMediator<Integer, RedditPost> { @NonNull private RedditDb db; @NonNull private RedditPostDao postDao; @NonNull private SubredditRemoteKeyDao remoteKeyDao; @NonNull private RedditApi redditApi; @NonNull private String subredditName; @NonNull private Executor bgExecutor; public PageKeyedRemoteMediator( @NonNull RedditDb db, @NonNull RedditApi redditApi, @NonNull String subredditName, @NonNull Executor bgExecutor ) { this.db = db; this.postDao = db.posts(); this.remoteKeyDao = db.remoteKeys(); this.redditApi = redditApi; this.subredditName = subredditName; this.bgExecutor = bgExecutor; ... } }
می توانید رابط Retrofit و رشته جستجو را همانطور که در بخش تست های PagingSource نشان داده شده است ارائه دهید. ارائه یک نسخه ساختگی از پایگاه داده اتاق بسیار مهم است، بنابراین ارائه یک پیاده سازی در حافظه پایگاه داده به جای نسخه کامل ساختگی می تواند آسان تر باشد. از آنجا که ایجاد یک پایگاه داده اتاق به یک شی Context
نیاز دارد، باید این تست RemoteMediator
را در پوشه androidTest
قرار دهید و آن را با اجرای آزمایشی AndroidJUnit4 اجرا کنید تا به زمینه برنامه آزمایشی دسترسی داشته باشد. برای اطلاعات بیشتر در مورد تست های ابزار دقیق، به ساخت تست های واحد ابزار دقیق مراجعه کنید.
برای اطمینان از اینکه حالت بین توابع آزمایشی نشت نمی کند، توابع حذف را تعریف کنید. این امر نتایج یکسانی را بین اجرای آزمایشی تضمین می کند.
کاتلین
@ExperimentalPagingApi @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class PageKeyedRemoteMediatorTest { private val postFactory = PostFactory() private val mockPosts = listOf( postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT), postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT), postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT) ) private val mockApi = mockRedditApi() private val mockDb = RedditDb.create( ApplicationProvider.getApplicationContext(), useInMemory = true ) @After fun tearDown() { mockDb.clearAllTables() // Clear out failure message to default to the successful response. mockApi.failureMsg = null // Clear out posts after each test run. mockApi.clearPosts() } }
جاوا
@RunWith(AndroidJUnit4.class) public class PageKeyedRemoteMediatorTest { static PostFactory postFactory = new PostFactory(); static List<RedditPost> mockPosts = new ArrayList<>(); static MockRedditApi mockApi = new MockRedditApi(); private RedditDb mockDb = RedditDb.Companion.create( ApplicationProvider.getApplicationContext(), true ); static { for (int i=0; i<3; i++) { RedditPost post = postFactory.createRedditPost(DEFAULT_SUBREDDIT); mockPosts.add(post); } } @After public void tearDown() { mockDb.clearAllTables(); // Clear the failure message after each test run. mockApi.setFailureMsg(null); // Clear out posts after each test run. mockApi.clearPosts(); } }
جاوا
@RunWith(AndroidJUnit4.class) public class PageKeyedRemoteMediatorTest { static PostFactory postFactory = new PostFactory(); static List<RedditPost> mockPosts = new ArrayList<>(); static MockRedditApi mockApi = new MockRedditApi(); private RedditDb mockDb = RedditDb.Companion.create( ApplicationProvider.getApplicationContext(), true ); static { for (int i=0; i<3; i++) { RedditPost post = postFactory.createRedditPost(DEFAULT_SUBREDDIT); mockPosts.add(post); } } @After public void tearDown() { mockDb.clearAllTables(); // Clear the failure message after each test run. mockApi.setFailureMsg(null); // Clear out posts after each test run. mockApi.clearPosts(); } }
مرحله بعدی آزمایش تابع load()
است. در این مثال، سه حالت برای آزمایش وجود دارد:
- اولین مورد زمانی است که
mockApi
داده های معتبر را برمی گرداند. تابعload()
بایدMediatorResult.Success
را برگرداند و ویژگیendOfPaginationReached
بایدfalse
باشد. - حالت دوم زمانی است که
mockApi
یک پاسخ موفق را برمی گرداند، اما داده های برگشتی خالی هستند. تابعload()
بایدMediatorResult.Success
را برگرداند و ویژگیendOfPaginationReached
بایدtrue
باشد. - مورد سوم زمانی است که
mockApi
هنگام واکشی داده ها یک استثنا ایجاد می کند. تابعload()
بایدMediatorResult.Error
را برگرداند.
برای تست مورد اول مراحل زیر را دنبال کنید:
-
mockApi
با داده های پست برای بازگشت تنظیم کنید. - شی
RemoteMediator
را راه اندازی کنید. - تابع
load()
را تست کنید.
کاتلین
@Test fun refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() = runTest { // Add mock results for the API to return. mockPosts.forEach { post -> mockApi.addPost(post) } val remoteMediator = PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ) val pagingState = PagingState<Int, RedditPost>( listOf(), null, PagingConfig(10), 10 ) val result = remoteMediator.load(LoadType.REFRESH, pagingState) assertTrue { result is MediatorResult.Success } assertFalse { (result as MediatorResult.Success).endOfPaginationReached } }
جاوا
@Test public void refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() throws InterruptedException { // Add mock results for the API to return. for (RedditPost post: mockPosts) { mockApi.addPost(post); } PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); remoteMediator.loadSingle(LoadType.REFRESH, pagingState) .test() .await() .assertValueCount(1) .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Success && ((RemoteMediator.MediatorResult.Success) value).endOfPaginationReached() == false); }
جاوا
@Test public void refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() throws InterruptedException, ExecutionException { // Add mock results for the API to return. for (RedditPost post: mockPosts) { mockApi.addPost(post); } PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT, new CurrentThreadExecutor() ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); RemoteMediator.MediatorResult result = remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get(); assertThat(result, instanceOf(RemoteMediator.MediatorResult.Success.class)); assertFalse(((RemoteMediator.MediatorResult.Success) result).endOfPaginationReached()); }
تست دوم به mockApi
نیاز دارد که یک نتیجه خالی را برگرداند. از آنجایی که پس از هر بار اجرای آزمایشی، دادهها را از mockApi
پاک میکنید، بهطور پیشفرض یک نتیجه خالی برمیگرداند.
کاتلین
@Test fun refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() = runTest { // To test endOfPaginationReached, don't set up the mockApi to return post // data here. val remoteMediator = PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ) val pagingState = PagingState<Int, RedditPost>( listOf(), null, PagingConfig(10), 10 ) val result = remoteMediator.load(LoadType.REFRESH, pagingState) assertTrue { result is MediatorResult.Success } assertTrue { (result as MediatorResult.Success).endOfPaginationReached } }
جاوا
@Test public void refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() throws InterruptedException() { // To test endOfPaginationReached, don't set up the mockApi to return post // data here. PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); remoteMediator.loadSingle(LoadType.REFRESH, pagingState) .test() .await() .assertValueCount(1) .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Success && ((RemoteMediator.MediatorResult.Success) value).endOfPaginationReached() == true); }
جاوا
@Test public void refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() throws InterruptedException, ExecutionException { // To test endOfPaginationReached, don't set up the mockApi to return post // data here. PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT, new CurrentThreadExecutor() ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); RemoteMediator.MediatorResult result = remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get(); assertThat(result, instanceOf(RemoteMediator.MediatorResult.Success.class)); assertTrue(((RemoteMediator.MediatorResult.Success) result).endOfPaginationReached()); }
آزمایش نهایی به mockApi
نیاز دارد که یک استثنا ایجاد کند تا آزمایش بتواند تأیید کند که تابع load()
به درستی MediatorResult.Error
برمی گرداند.
کاتلین
@Test fun refreshLoadReturnsErrorResultWhenErrorOccurs() = runTest { // Set up failure message to throw exception from the mock API. mockApi.failureMsg = "Throw test failure" val remoteMediator = PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ) val pagingState = PagingState<Int, RedditPost>( listOf(), null, PagingConfig(10), 10 ) val result = remoteMediator.load(LoadType.REFRESH, pagingState) assertTrue {result is MediatorResult.Error } }
جاوا
@Test public void refreshLoadReturnsErrorResultWhenErrorOccurs() throws InterruptedException { // Set up failure message to throw exception from the mock API. mockApi.setFailureMsg("Throw test failure"); PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); remoteMediator.loadSingle(LoadType.REFRESH, pagingState) .test() .await() .assertValueCount(1) .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Error); }
جاوا
@Test public void refreshLoadReturnsErrorResultWhenErrorOccurs() throws InterruptedException, ExecutionException { // Set up failure message to throw exception from the mock API. mockApi.setFailureMsg("Throw test failure"); PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT, new CurrentThreadExecutor() ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); RemoteMediator.MediatorResult result = remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get(); assertThat(result, instanceOf(RemoteMediator.MediatorResult.Error.class)); }
تست های پایان به انتها
تستهای واحد اطمینان حاصل میکنند که اجزای پیجینگ مجزا بهصورت مجزا کار میکنند، اما تستهای انتها به انتها اطمینان بیشتری نسبت به کارکرد برنامه بهعنوان یک کل ارائه میدهند. این تستها همچنان به برخی وابستگیهای ساختگی نیاز دارند، اما معمولاً بیشتر کد برنامه شما را پوشش میدهند.
مثال در این بخش از یک وابستگی ساختگی API برای جلوگیری از استفاده از شبکه در آزمایشات استفاده می کند. API ساختگی به گونه ای پیکربندی شده است که مجموعه ای ثابت از داده های آزمایشی را برگرداند که منجر به آزمایش های تکرار شونده می شود. بر اساس آنچه که هر وابستگی انجام می دهد، میزان سازگاری خروجی آن و میزان وفاداری که از آزمایشات خود نیاز دارید، تصمیم بگیرید که کدام وابستگی ها را با پیاده سازی های ساختگی جایگزین کنید.
کد خود را به گونه ای بنویسید که به شما امکان می دهد به راحتی نسخه های ساختگی وابستگی های خود را تعویض کنید. مثال زیر از یک پیاده سازی یاب سرویس پایه برای ارائه و تغییر وابستگی ها در صورت نیاز استفاده می کند. در برنامه های بزرگتر، استفاده از کتابخانه تزریق وابستگی مانند Hilt می تواند به مدیریت نمودارهای وابستگی پیچیده تر کمک کند.
کاتلین
class RedditActivityTest { companion object { private const val TEST_SUBREDDIT = "test" } private val postFactory = PostFactory() private val mockApi = MockRedditApi().apply { addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT)) addPost(postFactory.createRedditPost(TEST_SUBREDDIT)) addPost(postFactory.createRedditPost(TEST_SUBREDDIT)) } @Before fun init() { val app = ApplicationProvider.getApplicationContext<Application>() // Use a controlled service locator with a mock API. ServiceLocator.swap( object : DefaultServiceLocator(app = app, useInMemoryDb = true) { override fun getRedditApi(): RedditApi = mockApi } ) } }
جاوا
public class RedditActivityTest { public static final String TEST_SUBREDDIT = "test"; private static PostFactory postFactory = new PostFactory(); private static MockRedditApi mockApi = new MockRedditApi(); static { mockApi.addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); } @Before public void setup() { Application app = ApplicationProvider.getApplicationContext(); // Use a controlled service locator with a mock API. ServiceLocator.Companion.swap( new DefaultServiceLocator(app, true) { @NotNull @Override public RedditApi getRedditApi() { return mockApi; } } ); } }
جاوا
public class RedditActivityTest { public static final String TEST_SUBREDDIT = "test"; private static PostFactory postFactory = new PostFactory(); private static MockRedditApi mockApi = new MockRedditApi(); static { mockApi.addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); } @Before public void setup() { Application app = ApplicationProvider.getApplicationContext(); // Use a controlled service locator with a mock API. ServiceLocator.Companion.swap( new DefaultServiceLocator(app, true) { @NotNull @Override public RedditApi getRedditApi() { return mockApi; } } ); } }
پس از تنظیم ساختار تست، گام بعدی تأیید صحت داده های بازگشتی توسط پیاده سازی Pager
است. یک تست باید اطمینان حاصل کند که شی Pager
دادههای پیشفرض را هنگام بارگیری صفحه برای اولین بار بارگیری میکند، و آزمایش دیگری باید تأیید کند که شی Pager
به درستی دادههای اضافی را بر اساس ورودی کاربر بارگیری میکند. در مثال زیر، آزمایش تأیید میکند که شی Pager
RecyclerView.Adapter
را با تعداد صحیح آیتمهای برگردانده شده از API هنگامی که کاربر یک Subreddit دیگر را برای جستجو وارد میکند، بهروزرسانی میکند.
کاتلین
@Test fun loadsTheDefaultResults() { ActivityScenario.launch(RedditActivity::class.java) onView(withId(R.id.list)).check { view, noViewFoundException -> if (noViewFoundException != null) { throw noViewFoundException } val recyclerView = view as RecyclerView assertEquals(1, recyclerView.adapter?.itemCount) } } @Test // Verify that the default data is swapped out when the user searches for a // different subreddit. fun loadsTheTestResultsWhenSearchingForSubreddit() { ActivityScenario.launch(RedditActivity::class.java ) onView(withId(R.id.list)).check { view, noViewFoundException -> if (noViewFoundException != null) { throw noViewFoundException } val recyclerView = view as RecyclerView // Verify that it loads the default data first. assertEquals(1, recyclerView.adapter?.itemCount) } // Search for test subreddit instead of default to trigger new data load. onView(withId(R.id.input)).perform( replaceText(TEST_SUBREDDIT), pressKey(KeyEvent.KEYCODE_ENTER) ) onView(withId(R.id.list)).check { view, noViewFoundException -> if (noViewFoundException != null) { throw noViewFoundException } val recyclerView = view as RecyclerView assertEquals(2, recyclerView.adapter?.itemCount) } }
جاوا
@Test public void loadsTheDefaultResults() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(1, recyclerView.getAdapter().getItemCount()); }); } @Test // Verify that the default data is swapped out when the user searches for a // different subreddit. public void loadsTheTestResultsWhenSearchingForSubreddit() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; // Verify that it loads the default data first. assertEquals(1, recyclerView.getAdapter().getItemCount()); }); // Search for test subreddit instead of default to trigger new data load. onView(withId(R.id.input)).perform( replaceText(TEST_SUBREDDIT), pressKey(KeyEvent.KEYCODE_ENTER) ); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(2, recyclerView.getAdapter().getItemCount()); }); }
جاوا
@Test public void loadsTheDefaultResults() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(1, recyclerView.getAdapter().getItemCount()); }); } @Test // Verify that the default data is swapped out when the user searches for a // different subreddit. public void loadsTheTestResultsWhenSearchingForSubreddit() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; // Verify that it loads the default data first. assertEquals(1, recyclerView.getAdapter().getItemCount()); }); // Search for test subreddit instead of default to trigger new data load. onView(withId(R.id.input)).perform( replaceText(TEST_SUBREDDIT), pressKey(KeyEvent.KEYCODE_ENTER) ); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(2, recyclerView.getAdapter().getItemCount()); }); }
آزمایشهای ابزاری باید تأیید کنند که دادهها به درستی در رابط کاربری نمایش داده میشوند. این کار را با تأیید اینکه تعداد صحیح آیتمها در RecyclerView.Adapter
وجود دارد، یا با تکرار در نماهای ردیف جداگانه و تأیید اینکه دادهها به درستی قالببندی شدهاند، انجام دهید.
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- صفحه از شبکه و پایگاه داده
- مهاجرت به صفحه 3
- بارگیری و نمایش داده های صفحه بندی شده