Android中构建快速可靠的UI测试
作者:网络转载 发布时间:[ 2016/7/26 14:24:57 ] 推荐标签:UI测试 测试用例
创建JUnit,并且设定TestComponent
Creating a JUnit rule that sets the TestComponent
为了确保在每次测试前TestComponent被设置到Application类中,我们可以创建JUnit 4 的 TestRule
public class TestComponentRule implements TestRule {
private final TestComponent mTestComponent;
private final Context mContext;
public TestComponentRule(Context context) {
mContext = context;
MyApplication application = (MyApplication) context.getApplicationContext();
mTestComponent = DaggerTestComponent.builder()
.applicationTestModule(new ApplicationTestModule(application))
.build();
}
public DataManager getMockDataManager() {
return mTestComponent.dataManager();
}
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
MyApplication application = (MyApplication) context.getApplicationContext();
// Set the TestComponent before the test runs
application.setComponent(mTestComponent);
base.evaluate();
// Clears the component once the tets finishes so it would use the default one.
application.setComponent(null);
}
};
}
}
TestComponentRule将会创建TestComponent的实例对象,这也会覆写apply方法并返回一个新的 Statement,新的Statement会:
1 设定TestComponent给Application类的component对象。
2调用基类的Statement 的evaluate()方法(这是在test的时候执行)
3 设置Application的component字段为空,也让其恢复到初始状态。我们能够通过这种方式预防测试用例之间的相互影响
通过上面的代码我们可以通过getMockDataManager()方法获取模拟的DataManager对象。这也允许我们能够给得到DataManager对象并且stub它的方法。需要注意的是,这只有TestApplicationComponent的provideDataManger方法使用@Singleton注解的时候有效。如果它没有被指定为单例的,那么我们通过getMockDataManager方法得到的实例对象将会不同于应用使用的实例对象。因此,我们也不可能stub它。
编写测试用例
Writing the tests
现在我们有Dagger正确的配置,并且TestComponentRule也可以使用了,我们还有一件事要做,那是编写测试用例。我们使用 Espresso编写UI测试。它并不是完美的但是它是一个快速可靠的Android测试框架。在编写测试用例之前我们需要一个app去测试。假如我们有一个非常简单的app,从REST API 中加载用户名,并且展示到RecyclerView上面。那么DataManger将会是下面这个样子:
public DataManager {
// Loads usernames from a REST API using a Retrofit
public Single<List<String>> loadUsernames() {
return mUsersService.getUsernames();
}
}
loadUsername()方法使用Retrofit和Rxjava 去加载REST API 的数据。它返回的是Single 对象,并且发送一串字符串。 我们也需要一个Activity展示用户名usernames到RecyclerView上面,我们假设这个Activity叫做UsernamesActivity。如果你遵循MVP模式你也会有相应的presenter但为了直观理解,这里不做presenter操作。
现在我们想要测试这个简单的 Activity有至少三个情况需要测试:
1如果API返回一个有效的用户名列表数据,那么它们会被展示到列表上面。
2 如果API返回空的数据,那么界面会显示“空的列表”
3 如果API 请求失败,那么界面会显示“加载用户名失败”
下面依次展示三个测试:
@Test
public void usernamesDisplay() {
// Stub the DataManager with a list of three usernames
List<String> expectedUsernames = Arrays.asList("Joe", "Jemma", "Matt");
when(component.getMockDataManager().loadUsernames())
.thenReturn(Single.just(expectedUsernames));
// Start the Activity
main.launchActivity(null);
// Check that the three usernames are displayed
for (Sting username:expectedUsernames) {
onView(withText(username))
.check(matches(isDisplayed()));
}
}
@Test
public void emptyMessageDisplays() {
// Stub an empty list
when(component.getMockDataManager().loadUsernames())
.thenReturn(Single.just(Collections.emptyList()));
// Start the Activity
main.launchActivity(null);
// Check the empty list message displays
onView(withText("Empty list"))
.check(matches(isDisplayed()));
}
@Test
public void errorMessageDisplays() {
// Stub with a Single that emits and error
when(component.getMockDataManager().loadUsernames())
.thenReturn(Single.error(new RuntimeException()));
// Start the Activity
main.launchActivity(null);
// Check the error message displays
onView(withText("Error loading usernames"))
.check(matches(isDisplayed()));
}
}
通过上面的代码,我们使用TestComponentRule 和android 官方测试框架提供的ActivityTestRule。ActivityTestRule会让我们从测试中启动UsernamesActivity 。注意我们使用 RuleChain 来确保 TestComponentRule总是在ActivityTestRule前运行。这也是确保TestComponent在任何Activity运行之前在Application类中设定好。
你可能注意到了三个测试用例遵循同样的构建方式:
1 通过when (xxx).thenReturn(yyy)设置前置条件。这是通过stub loadUsernames()方法实现的。例如,第一个测试的前置条件是有一个有效的用户名列表。
2 通过main.launchActivity(null)运行activity。
3 通过check(matches(isDisplayed()));检查视图的展示,并且展示相应前置条件期望的值。
这是一个非常有效的解决方案,它允许你测试不同的场景,因为你对整个application的初始状态拥有的控制权。如果你不使用mock来编写上面的三个用例,几乎不可能达到这样的效果因为真实的API接口总会返回同样的数据。
如果你想要查看使用这个测试方法的完整实例,你可以在github查看项目ribot Android boilerplate 或者 ribot app.
当然这个解决方案也有一些瑕疵。首先在每个test之前都会stub显得非常繁琐。复杂的界面可能需要在每个测试之前有5-10个stub。将一些stub移到初始化setup()方法中是有用的但经常不同的测试需要不同的stub。第二个问题是UI测试和潜在的实现存在着耦合,也意味着如果你重构DataManager,那么你也需要修改stub。
虽然这样,我们也在ribot 的几个应用中应用了这个UI测试方法,事实证明这中方法也是有好处的。例如,我们近的一个Android应用中有250个UI测试能够在三分钟之内运行成功。其中也有380个Model层和Presenter层的单元测试。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。

sales@spasvo.com