পরীক্ষা নেভিগেশন

আপনার অ্যাপ্লিকেশানটি আপনার প্রত্যাশা অনুযায়ী কাজ করে কিনা তা যাচাই করার জন্য শিপ করার আগে আপনার অ্যাপের নেভিগেশন লজিক পরীক্ষা করা গুরুত্বপূর্ণ।

ন্যাভিগেশন উপাদানটি গন্তব্যের মধ্যে নেভিগেশন পরিচালনা, আর্গুমেন্ট পাস করা এবং FragmentManager এর সাথে কাজ করার সমস্ত কাজ পরিচালনা করে। এই ক্ষমতাগুলি ইতিমধ্যেই কঠোরভাবে পরীক্ষা করা হয়েছে, তাই আপনার অ্যাপে সেগুলি আবার পরীক্ষা করার প্রয়োজন নেই৷ যাইহোক, আপনার টুকরোগুলিতে থাকা অ্যাপ নির্দিষ্ট কোড এবং তাদের NavController এর মধ্যে মিথস্ক্রিয়াগুলি পরীক্ষা করা গুরুত্বপূর্ণ। এই নির্দেশিকাটি কয়েকটি সাধারণ নেভিগেশন পরিস্থিতির মধ্য দিয়ে চলে এবং কীভাবে সেগুলি পরীক্ষা করা যায়।

পরীক্ষা খণ্ড নেভিগেশন

বিচ্ছিন্নভাবে তাদের NavController সাথে ফ্র্যাগমেন্ট ইন্টারঅ্যাকশন পরীক্ষা করতে, নেভিগেশন 2.3 এবং উচ্চতর একটি TestNavHostController প্রদান করে যা বর্তমান গন্তব্য সেট করার জন্য API প্রদান করে এবং NavController.navigate() অপারেশনের পরে ব্যাক স্ট্যাক যাচাই করে।

আপনি আপনার অ্যাপ মডিউলের build.gradle ফাইলে নিম্নলিখিত নির্ভরতা যোগ করে আপনার প্রকল্পে নেভিগেশন টেস্টিং আর্টিফ্যাক্ট যোগ করতে পারেন:

dependencies {
  def nav_version = "2.8.6"

  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
dependencies {
  val nav_version = "2.8.6"

  androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
}

ধরা যাক আপনি একটি ট্রিভিয়া গেম তৈরি করছেন। গেমটি একটি টাইটেল_স্ক্রিন দিয়ে শুরু হয় এবং ব্যবহারকারী প্লে ক্লিক করলে একটি ইন_গেম স্ক্রিনে নেভিগেট হয়।

শিরোনাম_স্ক্রীনের প্রতিনিধিত্বকারী খণ্ডটি এইরকম দেখতে পারে:

কোটলিন জাভা
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);
       
});
   
}
}

ব্যবহারকারী প্লে-তে ক্লিক করলে অ্যাপটি সঠিকভাবে ব্যবহারকারীকে 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 এর একটি উদাহরণ তৈরি করে এবং এটিকে খণ্ডটিতে বরাদ্দ করে। এটি তখন UI চালাতে Espresso ব্যবহার করে এবং যাচাই করে যে উপযুক্ত নেভিগেশন ব্যবস্থা নেওয়া হয়েছে।

একটি বাস্তব NavController এর মতই, TestNavHostController আরম্ভ করার জন্য আপনাকে setGraph কল করতে হবে। এই উদাহরণে, খণ্ডটি পরীক্ষা করা হচ্ছে আমাদের গ্রাফের শুরুর গন্তব্য। TestNavHostController একটি setCurrentDestination পদ্ধতি প্রদান করে যা আপনাকে বর্তমান গন্তব্য (এবং ঐচ্ছিকভাবে, সেই গন্তব্যের জন্য আর্গুমেন্ট) সেট করতে দেয় যাতে আপনার পরীক্ষা শুরু হওয়ার আগে NavController সঠিক অবস্থায় থাকে।

একটি NavHostController উদাহরণের বিপরীতে যা একটি NavHostFragment ব্যবহার করবে, TestNavHostController অন্তর্নিহিত navigate() আচরণকে ট্রিগার করে না (যেমন FragmentTransaction যা FragmentNavigator করে) যখন আপনি navigate() কল করেন - এটি শুধুমাত্র TestNavHostController এর অবস্থা আপডেট করে।

FragmentScenario সহ NavigationUI পরীক্ষা করুন

পূর্ববর্তী উদাহরণে, 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);
   
}
}

এখানে আমাদের প্রয়োজন onViewCreated() কল করার সময় দ্বারা তৈরি করা NavControlleronFragment() এর আগের পদ্ধতি ব্যবহার করা আমাদের 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;
   
}
});

এই কৌশলটি ব্যবহার করে, onViewCreated() কল করার আগে NavController পাওয়া যায়, যা ক্র্যাশ না করেই NavigationUI পদ্ধতি ব্যবহার করার অনুমতি দেয়।

ব্যাক স্ট্যাক এন্ট্রির সাথে ইন্টারঅ্যাকশন পরীক্ষা করা

ব্যাক স্ট্যাক এন্ট্রিগুলির সাথে ইন্টারঅ্যাক্ট করার সময়, TestNavHostController আপনাকে NavHostController থেকে উত্তরাধিকারসূত্রে পাওয়া APIগুলি ব্যবহার করে আপনার নিজস্ব পরীক্ষা LifecycleOwner , ViewModelStore এবং OnBackPressedDispatcher এর সাথে কন্ট্রোলারকে সংযুক্ত করতে দেয়।

উদাহরণস্বরূপ, একটি ন্যাভিগেশন স্কোপড ViewModel ব্যবহার করে এমন একটি খণ্ড পরীক্ষা করার সময়, আপনাকে অবশ্যই TestNavHostControllersetViewModelStore কল করতে হবে:

কোটলিন জাভা
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())