ومن المهم اختبار منطق التنقّل في تطبيقك قبل شحنه للتحقّق من عمل التطبيق على النحو المتوقَّع.
يتعامل مكون التنقل مع كل أعمال إدارة التنقل بين
والوجهات وتمرير الحججات والعمل مع
FragmentManager
وقد خضعت هذه الإمكانات لاختبارات دقيقة، لذلك لا داعي لاختبارها.
مرة أخرى في التطبيق. ولكن المهم في الاختبار هو أن التفاعلات
بين الرمز الخاص بالتطبيق في أجزائك
NavController
يستعرض هذا الدليل بعض سيناريوهات التنقل الشائعة وكيفية اختبارها.
اختبار التنقّل بين الأجزاء
لاختبار تفاعلات الأجزاء مع NavController
بشكل منفصل،
يوفر التنقل 2.3 والإصدارات الأعلى
TestNavHostController
توفّر واجهات برمجة التطبيقات لضبط الوجهة الحالية والتحقق من الجهة الخلفية
تجميع العناصر التالية
NavController.navigate()
العمليات التجارية.
يمكنك إضافة أداة اختبار التنقل إلى مشروعك عن طريق إضافة
الاعتمادية التالية في ملف build.gradle
الخاص بوحدة تطبيقك:
Groovy
dependencies { def nav_version = "2.8.0" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") }
لنفترض أنّك تعمل على إنشاء لعبة معلومات عامة. تبدأ اللعبة title_screen وتنتقل إلى شاشة in_game عندما ينقر المستخدم. اللعب.
قد يظهر الجزء الذي يمثّل title_screen على النحو التالي:
Kotlin
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) } } }
Java
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); }); } }
لاختبار أنّ التطبيق ينقل المستخدِم بشكل صحيح إلى الشاشة in_game عندما
ينقر المستخدم على تشغيل، سيحتاج الاختبار إلى التحقق من أن هذا الجزء
ينقل NavController
إلى شاشة R.id.in_game
بشكل صحيح.
باستخدام خليط من FragmentScenario
وEspresso
وTestNavHostController
، يمكنك إعادة إنشاء الشروط اللازمة لاختبار
هذا السيناريو، كما هو موضح في المثال التالي:
Kotlin
@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) } }
Java
@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 لتشغيل واجهة المستخدم والتحقق من أن
اتخاذ إجراء التنقل المناسب.
تمامًا مثل NavController
حقيقي، يجب استدعاء setGraph
لإعداد
TestNavHostController
. في هذا المثال، كان الجزء الذي يتم اختباره
وجهة بداية الرسم البياني لدينا. يوفّر TestNavHostController
setCurrentDestination
تسمح لك بتحديد الوجهة الحالية (واختياريًا،
لهذه الوجهة) بحيث يكون NavController
في
الحالة الصحيحة قبل بدء الاختبار.
على عكس المثيل NavHostController
الذي يستخدمه NavHostFragment
،
لا يؤدي TestNavHostController
إلى تشغيل navigate()
الأساسي
السلوك (مثل حالة FragmentTransaction
التي ينفذها FragmentNavigator
)
عند الاتصال بـ navigate()
- يتم فقط تحديث حالة
TestNavHostController
اختبار واجهة المستخدم للتنقل مع سيناريو جزء
في المثال السابق، تم تقديم طلب معاودة الاتصال إلى titleScenario.onFragment()
بعد انتقال الجزء خلال دورة حياته إلى
RESUMED
الولاية. وبحلول هذا الوقت، يكون قد تم إنشاء عرض الجزء وإرفاقه،
لذا فقد يكون الوقت متأخرًا من دورة الحياة لإجراء الاختبار بشكل صحيح. على سبيل المثال، عند استخدام
NavigationUI
التي تتضمّن مشاهدات في الجزء، مثل المشاهدات التي يتم التحكّم فيها من خلال Toolbar
حسب الجزء، يمكنك استدعاء طرق الإعداد باستخدام NavController
قبل
عندما يصل الجزء إلى الحالة RESUMED
وبالتالي، أنت بحاجة إلى طريقة لضبط
TestNavHostController
في وقت مبكر من دورة الحياة
يمكن كتابة الجزء الذي يملك Toolbar
الخاص به على النحو التالي:
Kotlin
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) } }
Java
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()
مباشرةً، كما هو موضح في ما يلي
مثال:
Kotlin
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) } } } }
Java
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
، يمكنك توصيل وحدة التحكّم
اختبار LifecycleOwner
وViewModelStore
وOnBackPressedDispatcher
بواسطة
باستخدام واجهات برمجة التطبيقات التي يكتسبها منها
NavHostController
فعلى سبيل المثال، عند اختبار جزء يستخدم
العرض الخاص بنموذج التنقّل في نطاق التنقّل،
يجب الاتصال
setViewModelStore
في TestNavHostController
:
Kotlin
val navController = TestNavHostController(ApplicationProvider.getApplicationContext()) // This allows fragments to use by navGraphViewModels() navController.setViewModelStore(ViewModelStore())
Java
TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext()); // This allows fragments to use new ViewModelProvider() with a NavBackStackEntry navController.setViewModelStore(new ViewModelStore())
مواضيع ذات صلة
- إنشاء اختبارات الوحدات المعملية - تعرَّف على كيفية إعداد حزمة اختبار الأدوات وإجراء الاختبارات على جهاز Android. الخاص بك.
- Espresso - اختبار واجهة المستخدم في تطبيقك مع الإسبريسو.
- قواعد JUnit4 مع اختبار AndroidX: استخدام JUnit 4 بمكتبات اختبار AndroidX لتوفير مزيد من المرونة وتقليل رمز النص النموذجي المطلوب في الاختبارات.
- اختبار أجزاء التطبيق -
تعرَّف على طريقة اختبار أجزاء التطبيقات بشكل منفصل باستخدام
FragmentScenario
. - إعداد المشروع لاختبار AndroidX - التعلّم طريقة تحديد المكتبات المطلوبة في ملفات مشروع تطبيقك لاستخدام AndroidX اختبار.