安卓 - 独立的碎片UI测试工具

22
我一直在寻找一种方法来单独测试我的碎片的UI(即独立于其他碎片和活动),但我找不到这样的方法。特别是,假设我有A、B和C三个碎片。通过应用程序,去到碎片C的唯一方式是首先经过碎片A和碎片B。我正在寻找一种直接测试Fragment C的方法(如果有依赖关系,则可以模拟其依赖项),而无需通过碎片A和B。
到目前为止,我调查过的工具有:
- monkey: 仅用于通过命令行生成伪随机事件。不是我想要的。 - monkeyrunner: 它可以运行Python程序向我的Android应用程序发送事件流,但它不能直接使用这些脚本定位特定的Fragment。
- Espresso:白盒测试工具。这已经接近我想要的,但它仍然需要通过碎片A和B才能到达碎片C(即,您需要启动应用程序,然后测试将从那里运行)。
- UI Automator:黑盒测试工具。这也接近,但它也需要在测试我想要的碎片(碎片C)之前通过以前的碎片。
有没有一种直接测试碎片UI的方法?
4个回答

56

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

我有一个专门的 TestActivity,用于展示我应用中被测试的 Fragments。在我的情况下,Activity 只存在于 debug 变体中,因为我的 instrumentation 测试运行在 debug 上。

TL;DR 使用由 @brais-gabin 制作的很棒的 FragmentTestRule 库。

1. 创建一个 TestActivity 文件位于 src/debug/java/your/package/TestActivity.java 中,并设置一个内容视图,在该视图中添加要测试的 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 所必需的。将此清单添加到 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()));
    }
}

1
唯一的“小”问题是,你的测试代码与生产/运行时代码混合在一起(至少在调试构建中),随着你添加更多测试到套件中,这很容易且非常快地变得混乱:( 如果没有更好的解决方案,可能的解决方法是在调试构建中只保留清单条目,并将类(TestActivity、Rule等)保留在androidTest中。 - Ewoks
1
@thaussma 谢谢,这个解决方案也解决了我的问题。我只是想知道,为了测试目的,在项目中添加 TestActivity 是否是一个好的做法? - Kavita Patil
通常情况下,您希望避免在生产代码中添加任何仅限于测试的依赖项。将“TestActivity”添加到生产环境中会违反此规则。我认为在这种情况下,好处超过了(小)风险。它很简单和明确。我认为@Ewoks的评论夸大了一个Activity的影响,然后暗示这可能导致在生产中添加许多其他测试依赖项。这总是一个坏主意... - thaussma

4
我开发了一个名为 FragmentTestRule 的 Android 库,使用了 @thaussma 的想法。它允许你在隔离环境中测试你的 Fragment
你只需要添加这个:
@Rule
public FragmentTestRule<?, FragmentWithoutActivityDependency> fragmentTestRule =
    FragmentTestRule.create(FragmentWithoutActivityDependency.class);

这里有更多信息


3
如果您正在使用导航架构组件并在您的应用中使用单一活动架构,您可以通过在测试开始时深度链接到目标片段(带有适当的参数)来快速测试每个片段。
例如:
@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
}

通过这种方式进行操作将直接启动片段。如果您的测试成功,则证明当深度链接到片段时,您的应用程序不会崩溃。它还确保了如果该进程被系统杀死并尝试重建堆栈并重新启动片段,应用程序将保持稳定。


2
您可以使用Robotium,这是用于Android UI测试的工具。

2
但是Robotium和UI Automator、Espresso有相同的问题。为了测试Fragment C,我需要先通过Fragment A和B。我希望有一个工具可以让我直接测试Fragment C。 - Bitcoin Cash - ADA enthusiast
我一直在使用Robotium,它可以解决任何类型的UI测试问题。我不知道您的具体要求,所以请查看Robotium API http://robotium.googlecode.com/svn/doc/com/robotium/solo/Solo.html。 - Almett
@Tiago 希望像 waitForFragmentByTag、waitForFragmentById 这样的 API 能对你有所帮助。祝你好运。 - Almett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接