You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Espresso测试中@Test方法内权限撤销操作未生效问题求助

搞定Espresso测试里权限撤销没执行的问题

问题根源拆解

我明白你现在的困境:在test4里授予了位置权限,想在test5里撤销,但运行时发现撤销方法根本没机会执行,测试流程直接结束了。而把撤销逻辑放在@BeforeClass或@AfterClass里却正常。

这主要有两个核心原因:

  • JUnit默认不保证测试方法的执行顺序,你写的test4和test5可能不是按你预期的顺序跑的,说不定test5先执行了,那自然没权限可撤销;
  • 测试方法的设计原则是原子独立,每个测试都要在干净的环境下运行,依赖前一个测试的状态本身就不符合规范,万一test4抛出异常,后续的test5直接就被跳过了。

靠谱的解决方案

1. 用@After注解自动清理(最推荐)

把权限撤销逻辑放到@After注解的方法里,这样每个测试方法执行完后,不管成功还是失败,都会自动触发撤销操作,保证下一个测试的环境是干净的:

@After
public void cleanUpPermissions() {
    revokePermission();
}

@Test
public void test4clickAllowLocation() {
    // 你的测试代码原样保留
    context = getInstrumentation().getContext();
    onView(withId(R.id.location_switch)).perform(scrollTo()).perform(click());
    onView(isRoot()).perform(waitFor(3000));
    onView(withId(R.id.second_Btn)).inRoot(isDialog()).perform(click());
    allowPermissionsJustOne("ALLOW", "Allow", "Allow only while using the app");
    PurpleLabeledSwitch purpleLabeledSwitch = new PurpleLabeledSwitch(context);
    purpleLabeledSwitch.findViewById(R.id.location_switch);
    purpleLabeledSwitch.setOn(true);
    assertTrue(purpleLabeledSwitch.isOn());
    onView(isRoot()).perform(waitFor(3000));
    onView(withId(R.id.location_image_view))
        .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
    onView(withId(R.id.location_image_view))
        .check(matches(withDrawable(R.drawable.ic_right_arrow_green)));
}

// 删掉单独的test5revokePermission方法,没必要了

2. 强制指定测试执行顺序(应急可用,不推荐)

如果你非要保留单独的撤销测试方法,那得强制JUnit按你写的顺序执行。用@FixMethodOrder注解指定按方法名排序:

import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class YourTestClassName {

    @Test
    public void test4clickAllowLocation() {
        // 测试代码...
    }

    @Test
    public void test5revokePermission() {
        UiDevice mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        mDevice.pressHome();
        revokePermission();
    }

    public void revokePermission() {
        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        String packageName = instrumentation.getTargetContext().getPackageName();
        UiAutomation uiAutomation = instrumentation.getUiAutomation();
        
        uiAutomation.executeShellCommand("pm revoke " + packageName + " android.permission.ACCESS_FINE_LOCATION");
        uiAutomation.executeShellCommand("pm revoke " + packageName + " android.permission.ACCESS_COARSE_LOCATION");
    }
}

NAME_ASCENDING会让JUnit按方法名的字母顺序执行,这样test4肯定在test5前面。但还是要提醒你:这种方式破坏了测试的独立性,一旦test4失败,test5也白搭,所以尽量别用。

3. 优化你的权限撤销方法

顺便提一句,你原来的revokePermission()代码可以简化下,避免重复获取实例,让代码更整洁:

public void revokePermission() {
    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    String packageName = instrumentation.getTargetContext().getPackageName();
    UiAutomation uiAutomation = instrumentation.getUiAutomation();
    
    uiAutomation.executeShellCommand("pm revoke " + packageName + " android.permission.ACCESS_FINE_LOCATION");
    uiAutomation.executeShellCommand("pm revoke " + packageName + " android.permission.ACCESS_COARSE_LOCATION");
}

为啥@BeforeClass/@AfterClass能正常工作?

因为这两个注解的方法是和整个测试类绑定的:@BeforeClass在所有测试方法执行前跑一次,@AfterClass在所有测试都跑完后跑一次,它们的生命周期不受单个测试方法的成败影响,所以你在里面执行撤销逻辑不会有问题。但如果你的测试类有多个测试方法,@AfterClass只会清理一次,没法保证每个测试都有干净的环境,还是@After更实用。


内容的提问来源于stack exchange,提问作者mehdi dehghani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 21:42:32