【问题标题】:Android - Independent Fragment UI testing toolAndroid - 独立的 Fragment UI 测试工具
【发布时间】:2016-02-12 08:13:16
【问题描述】:

我一直在寻找一种方法来单独测试我的 Fragments 的 UI(即,独立于其他 Fragment 和 Activity),但我找不到方法。

特别是,假设我有 Fragment A、Fragment B 和 Fragment C。进入 Fragment C 的唯一方法(应用程序)是首先通过 Fragment A 和 Fragment B。我正在寻找一种直接测试片段 C 的方法(可能通过模拟它的依赖项,如果存在的话),而不必通过片段 A 和 B。

到目前为止我研究过的工具:

  • monkey:仅用于通过命令行生成伪随机事件。不是我想要的。

  • monkeyrunner:它可以运行 Python 程序以将事件流发送到我的 Android 应用程序,但它不能使用这些脚本直接针对特定的 Fragment。

  • Espresso:白盒测试工具。这接近我想要的,但它仍然需要在到达 Fragment C 之前通过 Fragment A 和 B(即,您需要启动您的应用,然后测试将从那里运行)。

  • UI Automator:黑盒测试工具。这也很接近,但同样,它需要在测试我想要的片段(片段 C)之前通过之前的片段。

有没有办法直接测试 Fragmen 的 UIt?

【问题讨论】:

    标签: android android-testing android-espresso android-uiautomator


    【解决方案1】:

    我使用@thaussma's idea 开发了FragmentTestRule 一个Andorid 库。它允许您单独测试您的Fragments。

    你只需要添加这个:

    @Rule
    public FragmentTestRule<?, FragmentWithoutActivityDependency> fragmentTestRule =
        FragmentTestRule.create(FragmentWithoutActivityDependency.class);
    

    More information here.

    【讨论】:

      【解决方案2】:

      如果您正在使用导航架构组件并且在应用程序中使用单个活动架构,则可以通过在测试开始时深度链接到目标片段(使用适当的参数)来快速测试每个片段.

      例如:

      @Rule
      @JvmField
      var activityRule = ActivityTestRule(MainActivity::class.java)
      
      protected fun launchFragment(destinationId: Int,
                                   argBundle: Bundle? = null) {
          val launchFragmentIntent = buildLaunchFragmentIntent(destinationId, argBundle)
          activityRule.launchActivity(launchFragmentIntent)
      }
      
      private fun buildLaunchFragmentIntent(destinationId: Int, argBundle: Bundle?): Intent =
              NavDeepLinkBuilder(InstrumentationRegistry.getInstrumentation().targetContext)
                      .setGraph(R.navigation.navigation)
                      .setComponentName(MainActivity::class.java)
                      .setDestination(destinationId)
                      .setArguments(argBundle)
                      .createTaskStackBuilder().intents[0]
      

      destinationId 是导航图中的片段目标 ID。这是一个在您准备好启动片段后将执行的调用示例:

      launchFragment(R.id.target_fragment, targetBundle())
      
      private fun targetBundle(): Bundle? {
          val bundle = Bundle()
          bundle.putString(ARGUMENT_ID, "Argument needed by fragment")
          return bundle
      }
      

      这样做会直接启动片段。如果您的测试有效,那么这证明当片段被深度链接到时您的应用程序不会崩溃。如果进程被系统杀死并尝试重建堆栈并重新启动片段,它还可以确保应用程序保持稳定。

      【讨论】:

        【解决方案3】:

        我正在使用自定义 FragmentTestRule 和 Espresso 来单独测试我的每个 Fragments

        我有一个专用的TestActivity,它在我的应用程序中显示经过测试的Fragments。在我的情况下,Activity 仅存在于debug 变体中,因为我的仪器测试针对debug 运行。

        TL;DR 使用 @brais-gabin 提供的很棒的 FragmentTestRule 库。

        1.在 src/debug/java/your/package/TestActivity.java 中创建一个 TestActivity,并在其中添加测试的 Fragment 的内容视图:

        @VisibleForTesting
        public class TestActivity extends AppCompatActivity {
            @Override
            protected void onCreate(@Nullable final Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                FrameLayout frameLayout = new FrameLayout(this);
                frameLayout.setId(R.id.container);
                setContentView(frameLayout);
            }
        }
        

        2。为debug 变体创建一个AndroidManifest.xml 并声明TestActivity。这是在测试时启动TestActivity 所必需的。将此 Manifest 添加到 src/debug/AndroidManifest.xml 中的 debug 变体中:

        <?xml version="1.0" encoding="utf-8"?>
        <manifest xmlns:android="http://schemas.android.com/apk/res/android">
            <application>           
                <activity android:name="your.package.TestActivity"/>
            </application>
        </manifest>
        

        3.在androidTest 变体src/androidTest/java/your/test/package/FragmentTestRule.java 中创建FragmentTestRule

        public class FragmentTestRule<F extends Fragment> extends ActivityTestRule<TestActivity> {
        
            private final Class<F> mFragmentClass;
            private F mFragment;
        
            public FragmentTestRule(final Class<F> fragmentClass) {
                super(TestActivity.class, true, false);
                mFragmentClass = fragmentClass;
            }
        
            @Override
            protected void afterActivityLaunched() {
                super.afterActivityLaunched();
        
                getActivity().runOnUiThread(() -> {
                    try {
                        //Instantiate and insert the fragment into the container layout
                        FragmentManager manager = getActivity().getSupportFragmentManager();
                        FragmentTransaction transaction = manager.beginTransaction();
                        mFragment = mFragmentClass.newInstance();
                        transaction.replace(R.id.container, mFragment);
                        transaction.commit();
                    } catch (InstantiationException | IllegalAccessException e) {
                        Assert.fail(String.format("%s: Could not insert %s into TestActivity: %s",
                                getClass().getSimpleName(),
                                mFragmentClass.getSimpleName(),
                                e.getMessage()));
                    }
                });
            }
            public F getFragment(){
                return mFragment;
            }
        }
        

        4.然后你可以单独测试Fragments

        public class MyFragmentTest {
        
            @Rule
            public FragmentTestRule<MyFragment> mFragmentTestRule = new FragmentTestRule<>(MyFragment.class);
        
            @Test
            public void fragment_can_be_instantiated() {
        
                // Launch the activity to make the fragment visible 
                mFragmentTestRule.launchActivity(null);
        
                // Then use Espresso to test the Fragment
                onView(withId(R.id.an_id_in_the_fragment)).check(matches(isDisplayed()));
            }
        }
        

        【讨论】:

        • 唯一的“小” 事情是您的测试代码与生产/运行时代码(至少在调试版本中)混合在一起,这很容易很快就会变得混乱向套件添加更多测试:(如果不存在更好的解决方案,可能的解决方法是在调试构建中仅保留清单条目并在 androidTest 中保留类(TestActivity、Rule 等)
        • @thaussma 谢谢,这个解决方案也解决了我的问题。我只是想知道在您的项目中添加TestActivity 只是为了测试目的是一个好习惯吗?
        • @Kavita_p:通常您希望避免在生产代码中出现任何仅用于测试的依赖项。将TestActivity 添加到生产中会破坏此规则。我认为在这种情况下,收益超过了(小)风险。它简单明了。我认为@Ewoks 的评论夸大了一个活动的影响,然后暗示这可能会导致在生产中添加许多其他测试依赖项。这总是一个坏主意......
        【解决方案4】:

        您可以使用Robotium。这是用于android UI 测试的。

        【讨论】:

        • 但是 Robotium 和 UI Automator 和 Espresso 有同样的问题。为了测试 Fragment C,我需要先通过 Fragment A 和 B。我想要一个可以让我直接测试 Fragment C 的工具。
        • 因为我一直在使用robotium,它可以解决任何类型的UI测试问题。我不知道你的要求的细节,所以请查看robotium API robotium.googlecode.com/svn/doc/com/robotium/solo/Solo.html
        • @Tiago 我希望像 waitForFragmentByTag、waitForFragmentById 这样的 API 可以帮助到你。祝你好运。
        猜你喜欢
        • 1970-01-01
        • 2013-07-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-12
        • 2010-11-27
        • 2015-04-26
        • 2012-02-15
        相关资源
        最近更新 更多