مهم است که منطق ناوبری برنامه خود را قبل از ارسال آزمایش کنید تا مطمئن شوید که برنامه شما همانطور که انتظار دارید کار می کند.
مؤلفه Navigation تمام کارهای مدیریت پیمایش بین مقصدها، ارسال آرگومان ها و کار با FragmentManager
را انجام می دهد. این قابلیتها قبلاً بهشدت آزمایش شدهاند، بنابراین نیازی به آزمایش مجدد آنها در برنامه شما نیست. با این حال، آنچه برای آزمایش مهم است، تعامل بین کد خاص برنامه در قطعات شما و NavController
آنها است. این راهنما به چند سناریو ناوبری رایج و نحوه آزمایش آنها می پردازد.
تست ناوبری قطعه
برای آزمایش فعل و انفعالات قطعه با NavController
خود به صورت مجزا، Navigation 2.3 و بالاتر یک TestNavHostController
را ارائه می دهد که API هایی را برای تنظیم مقصد فعلی و تأیید پشته پشته پس از عملیات NavController.navigate()
ارائه می دهد.
میتوانید با افزودن وابستگی زیر در فایل build.gradle
ماژول برنامه، مصنوع آزمایش ناوبری را به پروژه خود اضافه کنید:
شیار
dependencies { def nav_version = "2.7.7" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
کاتلین
dependencies { val nav_version = "2.7.7" androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") }
فرض کنید در حال ساخت یک بازی چیزهای بی اهمیت هستید. بازی با یک title_screen شروع می شود و وقتی کاربر روی play کلیک می کند به صفحه in_game هدایت می شود.
قطعه نشان دهنده title_screen ممکن است چیزی شبیه به این باشد:
کاتلین
class TitleScreen : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ) = inflater.inflate(R.layout.fragment_title_screen, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { view.findViewById<Button>(R.id.play_btn).setOnClickListener { view.findNavController().navigate(R.id.action_title_screen_to_in_game) } } }
جاوا
public class TitleScreen extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_title_screen, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { view.findViewById(R.id.play_btn).setOnClickListener(v -> { Navigation.findNavController(view).navigate(R.id.action_title_screen_to_in_game); }); } }
برای آزمایش اینکه وقتی کاربر روی Play کلیک می کند، برنامه به درستی کاربر را به صفحه in_game هدایت می کند، آزمایش شما باید تأیید کند که این قطعه به درستی NavController
را به صفحه R.id.in_game
منتقل می کند.
با استفاده از ترکیبی از FragmentScenario
، Espresso ، و TestNavHostController
، میتوانید شرایط لازم برای آزمایش این سناریو را دوباره ایجاد کنید، همانطور که در مثال زیر نشان داده شده است:
کاتلین
@RunWith(AndroidJUnit4::class) class TitleScreenTest { @Test fun testNavigationToInGameScreen() { // Create a TestNavHostController val navController = TestNavHostController( ApplicationProvider.getApplicationContext()) // Create a graphical FragmentScenario for the TitleScreen val titleScenario = launchFragmentInContainer<TitleScreen>() titleScenario.onFragment { fragment -> // Set the graph on the TestNavHostController navController.setGraph(R.navigation.trivia) // Make the NavController available via the findNavController() APIs Navigation.setViewNavController(fragment.requireView(), navController) } // Verify that performing a click changes the NavController’s state onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click()) assertThat(navController.currentDestination?.id).isEqualTo(R.id.in_game) } }
جاوا
@RunWith(AndroidJUnit4.class) public class TitleScreenTestJava { @Test public void testNavigationToInGameScreen() { // Create a TestNavHostController TestNavHostController navController = new TestNavHostController( ApplicationProvider.getApplicationContext()); // Create a graphical FragmentScenario for the TitleScreen FragmentScenario<TitleScreen> titleScenario = FragmentScenario.launchInContainer(TitleScreen.class); titleScenario.onFragment(fragment -> // Set the graph on the TestNavHostController navController.setGraph(R.navigation.trivia); // Make the NavController available via the findNavController() APIs Navigation.setViewNavController(fragment.requireView(), navController) ); // Verify that performing a click changes the NavController’s state onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click()); assertThat(navController.currentDestination.id).isEqualTo(R.id.in_game); } }
مثال بالا نمونه ای از TestNavHostController
را ایجاد می کند و آن را به قطعه اختصاص می دهد. سپس از Espresso برای هدایت UI استفاده می کند و تأیید می کند که عملکرد ناوبری مناسب انجام شده است.
درست مانند یک NavController
واقعی، باید setGraph
برای مقداردهی اولیه TestNavHostController
فراخوانی کنید. در این مثال، قطعه مورد آزمایش، مقصد شروع گراف ما بود. TestNavHostController
یک متد setCurrentDestination
را ارائه می دهد که به شما امکان می دهد مقصد فعلی (و به صورت اختیاری، آرگومان هایی برای آن مقصد) را تنظیم کنید تا NavController
قبل از شروع آزمایش شما در وضعیت صحیح قرار گیرد.
برخلاف نمونه NavHostController
که NavHostFragment
از آن استفاده می کند، TestNavHostController
رفتار navigate()
(مانند FragmentTransaction
که FragmentNavigator
انجام می دهد) را هنگام فراخوانی navigate()
فعال نمی کند - فقط وضعیت TestNavHostController
را به روز می کند.
NavigationUI را با FragmentScenario تست کنید
در مثال قبلی، callback ارائه شده به titleScenario.onFragment()
پس از اینکه قطعه در چرخه حیات خود به حالت RESUMED
رفت فراخوانی می شود. در این زمان، نمای قطعه قبلاً ایجاد و پیوست شده است، بنابراین ممکن است برای آزمایش درست در چرخه حیات بسیار دیر باشد. به عنوان مثال، هنگام استفاده از NavigationUI
با نماهایی در قطعه خود، مانند Toolbar
که توسط قطعه شما کنترل می شود، می توانید قبل از اینکه قطعه به حالت RESUMED
برسد، متدهای راه اندازی را با NavController
خود فراخوانی کنید. بنابراین، شما به راهی برای تنظیم TestNavHostController
خود در اوایل چرخه حیات نیاز دارید.
قطعه ای که Toolbar
خود را دارد می تواند به صورت زیر نوشته شود:
کاتلین
class TitleScreen : Fragment(R.layout.fragment_title_screen) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navController = view.findNavController() view.findViewById<Toolbar>(R.id.toolbar).setupWithNavController(navController) } }
جاوا
public class TitleScreen extends Fragment { public TitleScreen() { super(R.layout.fragment_title_screen); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { NavController navController = Navigation.findNavController(view); view.findViewById(R.id.toolbar).setupWithNavController(navController); } }
در اینجا به NavController
نیاز داریم که با فراخوانی onViewCreated()
ایجاد شده است. استفاده از رویکرد قبلی onFragment()
TestNavHostController
ما را در چرخه حیات بسیار دیر تنظیم می کند و باعث می شود که فراخوانی findNavController()
با شکست مواجه شود.
FragmentScenario
یک رابط FragmentFactory
ارائه می دهد که می تواند برای ثبت تماس های مربوط به رویدادهای چرخه حیات استفاده شود. این را می توان با Fragment.getViewLifecycleOwnerLiveData()
ترکیب کرد تا یک فراخوان دریافت کند که بلافاصله بعد از onCreateView()
، همانطور که در مثال زیر نشان داده شده است:
کاتلین
val scenario = launchFragmentInContainer { TitleScreen().also { fragment -> // In addition to returning a new instance of our Fragment, // get a callback whenever the fragment’s view is created // or destroyed so that we can set the NavController fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner -> if (viewLifecycleOwner != null) { // The fragment’s view has just been created navController.setGraph(R.navigation.trivia) Navigation.setViewNavController(fragment.requireView(), navController) } } } }
جاوا
FragmentScenario<TitleScreen> scenario = FragmentScenario.launchInContainer( TitleScreen.class, null, new FragmentFactory() { @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className, @Nullable Bundle args) { TitleScreen titleScreen = new TitleScreen(); // In addition to returning a new instance of our fragment, // get a callback whenever the fragment’s view is created // or destroyed so that we can set the NavController titleScreen.getViewLifecycleOwnerLiveData().observeForever(new Observer<LifecycleOwner>() { @Override public void onChanged(LifecycleOwner viewLifecycleOwner) { // The fragment’s view has just been created if (viewLifecycleOwner != null) { navController.setGraph(R.navigation.trivia); Navigation.setViewNavController(titleScreen.requireView(), navController); } } }); return titleScreen; } });
با استفاده از این تکنیک، NavController
قبل از onViewCreated()
در دسترس است و به قطعه اجازه میدهد از روشهای NavigationUI
بدون خرابی استفاده کند.
تست تعامل با ورودی های پشته
هنگام تعامل با ورودیهای پشته ، TestNavHostController
به شما امکان میدهد با استفاده از APIهایی که از NavHostController
به ارث میبرد، کنترلکننده را به LifecycleOwner
، ViewModelStore
و OnBackPressedDispatcher
آزمایشی خود متصل کنید.
به عنوان مثال، هنگام آزمایش قطعه ای که از ViewModel با محدوده پیمایش استفاده می کند، باید setViewModelStore
در TestNavHostController
فراخوانی کنید:
کاتلین
val navController = TestNavHostController(ApplicationProvider.getApplicationContext()) // This allows fragments to use by navGraphViewModels() navController.setViewModelStore(ViewModelStore())
جاوا
TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext()); // This allows fragments to use new ViewModelProvider() with a NavBackStackEntry navController.setViewModelStore(new ViewModelStore())
موضوعات مرتبط
- ساخت تستهای واحد ابزار دقیق - با نحوه راهاندازی مجموعه تست ابزار دقیق خود و اجرای آزمایشها در دستگاه Android آشنا شوید.
- اسپرسو - رابط کاربری برنامه خود را با اسپرسو تست کنید.
- قوانین JUnit4 با AndroidX Test - از قوانین JUnit 4 با کتابخانههای AndroidX Test استفاده کنید تا انعطافپذیری بیشتری داشته باشید و کد دیگ بخار مورد نیاز در آزمایشها را کاهش دهید.
- قطعات برنامه خود را آزمایش کنید - با
FragmentScenario
یاد بگیرید که چگونه قطعات برنامه خود را به صورت مجزا آزمایش کنید. - راه اندازی پروژه برای AndroidX Test - یاد بگیرید که چگونه کتابخانه های مورد نیاز را در فایل های پروژه برنامه خود برای استفاده از AndroidX Test اعلام کنید.