如何为依赖用户数据的Activity编写JUnit+Espresso测试且不公开mCurrentUser?
解决方案
我来给你几个实用的方案,既能守住代码的封装性,又能顺利搞定测试问题:
1. 给测试开个包级访问的“后门”
你完全不用把mCurrentUser改成public,只要在User类里加一个包级私有的静态方法,专门给测试用。记得把测试类和User类放在同一个包下(测试代码放在src/test/java对应的包路径里),然后添加:
// 注意:这个方法只在测试环境下使用,生产代码绝对不要调用! static void setCurrentUserForTest(User user) { mCurrentUser = user; }
这样测试类就能直接调用这个方法设置模拟用户数据,完全不用依赖loadFromDatabase()的异步回调,而且生产代码里其他类因为访问权限问题根本用不了这个方法,完美守住封装性。
2. 用依赖注入替代静态变量
如果你的项目后续有扩展计划,静态变量mCurrentUser其实是个潜在的坑(比如多线程冲突、测试隔离问题)。不如把用户数据通过依赖注入的方式传给MainActivity,比如用构造方法注入(如果用Dagger这类依赖注入框架会更方便):
public class MainActivity extends Activity { private User mCurrentUser; // 专门给测试用的构造方法 public MainActivity(User user) { mCurrentUser = user; } // 生产环境的空构造方法,系统会自动调用 public MainActivity() {} @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 生产环境下如果没有传入用户数据,再从User类加载 if(mCurrentUser == null) { User.loadFromDatabase(callback); } // 后续业务逻辑直接用mCurrentUser } }
测试的时候直接用带参数的构造方法传入模拟用户,根本不用管异步加载的事,测试隔离性也更好。
3. 等待异步回调完成再启动Activity
如果你不想改动太多生产代码,那可以调整测试规则的启动时机,等loadFromDatabase()的回调完成后再手动启动Activity。比如用CountDownLatch来等待回调:
public class MainActivityTest { // 第三个参数设为false,禁止自动启动Activity private ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class, false, false); @Before public void setUp() throws InterruptedException { // 用CountDownLatch等待回调完成 final CountDownLatch latch = new CountDownLatch(1); User.loadFromDatabase(new Callback() { @Override public void onComplete() { // 回调完成后释放信号 latch.countDown(); } }); // 最多等待5秒,防止测试无限挂起 latch.await(5, TimeUnit.SECONDS); // 回调完成后手动启动Activity activityRule.launchActivity(null); } @Test public void testMainActivityUserDisplay() { // 这里Activity已经正常加载了用户数据,可以执行Espresso测试 onView(withId(R.id.user_name)).check(matches(withText("测试用户"))); } }
这种方法不用改生产代码,但要注意设置合理的超时时间,避免测试卡住。
4. 用Mockito模拟回调(隔离外部依赖)
如果loadFromDatabase()内部还要调用服务器或数据库,你可以用Mockito模拟这个方法的行为,直接触发回调,不用真的发起网络请求或访问数据库:
@RunWith(MockitoJUnitRunner.class) public class MainActivityTest { private ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class, false, false); @Before public void setUp() { // 模拟User.loadFromDatabase方法,直接触发回调 doAnswer(invocation -> { Callback callback = invocation.getArgument(0); // 直接调用回调,模拟数据加载完成 callback.onComplete(); return null; }).when(User.class); User.loadFromDatabase(any(Callback.class)); // 调用加载方法触发模拟回调 User.loadFromDatabase(new Callback() {}); // 启动Activity activityRule.launchActivity(null); } }
这种方法适合需要完全隔离外部依赖的场景,测试速度更快也更稳定。
内容的提问来源于stack exchange,提问作者user5728398
相关产品推荐
相关产品推荐

