指纹认证测试:单元测试、UI测试、模拟器与真机

测试这块,说实话,是很多开发同学容易忽略的环节。我见过不少项目,指纹功能在模拟器上跑得挺好,一上真机就各种翻车。说白了,指纹认证涉及硬件、系统服务、UI交互多个层面,任何一个环节出问题,用户感知都很明显——毕竟指纹解不开锁,用户第一反应就是「这App有bug」。

今天我把指纹认证的测试策略拆开来讲。从最底层的单元测试,到UI层面的Espresso测试,再到模拟器和真机的差异,咱们一个一个过。

单元测试:用Mockito把依赖「架空」

单元测试的核心是什么?是隔离。你想想看,测试指纹认证逻辑时,你总不能每次都去按指纹吧?那效率太低了。所以我们需要用Mockito把BiometricManager、Executor这些系统依赖给mock掉。

我个人习惯是,先写一个认证接口,然后针对这个接口做单元测试。举个例子:

// 待测试的认证管理器
public class BiometricAuthManager {
    private final BiometricManager biometricManager;
    private final Executor executor;

    public BiometricAuthManager(BiometricManager manager, Executor exec) {
        this.biometricManager = manager;
        this.executor = exec;
    }

    public boolean canAuthenticate() {
        return biometricManager.canAuthenticate(
            BiometricManager.Authenticators.BIOMETRIC_STRONG
        ) == BiometricManager.BIOMETRIC_SUCCESS;
    }
}

// 单元测试
@RunWith(MockitoJUnitRunner.class)
public class BiometricAuthManagerTest {

    @Mock
    BiometricManager mockManager;

    @Mock
    Executor mockExecutor;

    @InjectMocks
    BiometricAuthManager authManager;

    @Test
    public void testCanAuthenticate_whenHardwareAvailable() {
        // 模拟硬件可用
        when(mockManager.canAuthenticate(anyInt()))
            .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);

        assertTrue(authManager.canAuthenticate());
    }

    @Test
    public void testCanAuthenticate_whenNoHardware() {
        when(mockManager.canAuthenticate(anyInt()))
            .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);

        assertFalse(authManager.canAuthenticate());
    }
}

这里有个坑,我曾经踩过——BiometricManager.canAuthenticate() 在Android 11以下和以上版本,返回码不一样。你mock的时候,最好把各个版本的返回值都覆盖到。嗯,这点很重要。

我的小技巧: 把认证结果封装成一个枚举,比如 SUCCESS、ERROR_HW_UNAVAILABLE、ERROR_NONE_ENROLLED。这样单元测试里直接断言枚举值,比断言int码可读性强多了。

UI测试:Espresso模拟指纹交互

UI测试要验证的是:用户点击「指纹认证」按钮后,系统弹不弹指纹对话框?认证成功/失败后,UI有没有正确跳转?

Espresso配合AndroidX Test库,可以做到。但有个关键点——指纹对话框是系统级的,Espresso默认抓不到。你需要用UiAutomator或者GrantPermissionRule来处理。

我一般这么写:

@RunWith(AndroidJUnit4.class)
@LargeTest
public class FingerprintUiTest {

    @Rule
    public ActivityTestRule<MainActivity> activityRule =
        new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testFingerprintDialogShown() {
        // 点击指纹认证按钮
        onView(withId(R.id.btn_fingerprint))
            .perform(click());

        // 验证系统指纹对话框出现
        onView(withText("指纹验证"))
            .check(matches(isDisplayed()));
    }

    @Test
    public void testAuthSuccessNavigatesToHome() {
        onView(withId(R.id.btn_fingerprint)).perform(click());

        // 模拟指纹认证成功(需要真机或模拟器支持)
        // 这里用UiAutomator模拟系统指纹事件
        UiDevice device = UiDevice.getInstance(
            InstrumentationRegistry.getInstrumentation()
        );
        device.executeShellCommand(
            "input keyevent 66"  // 模拟指纹匹配成功
        );

        onView(withId(R.id.home_screen))
            .check(matches(isDisplayed()));
    }
}
注意: Espresso测试指纹时,千万别在模拟器上跑「模拟指纹成功」的shell命令——不同模拟器版本,keyevent码不一样。我曾在API 29的模拟器上跑通过,换到API 30就失效了。建议用UiAutomatorperformActionAndWait()来做,更稳定。

模拟器测试:能测什么,不能测什么

模拟器测试指纹,说白了就是「半真半假」。Android模拟器从API 28开始支持模拟指纹传感器,但仅限于模拟指纹录入和认证的流程,真正的指纹数据是不存在的。

能测的:

  • 指纹对话框的UI展示和消失逻辑
  • 认证成功/失败的回调路径
  • 多次失败后的降级策略(比如弹出PIN码输入)
  • 指纹取消操作的UI响应

不能测的:

  • 指纹识别的准确率和速度
  • 不同手指、不同角度、湿手/干手场景
  • 指纹传感器在低电量或高温下的表现
  • 系统级指纹服务的异常崩溃恢复

我记得有一次,模拟器上所有测试都通过了,结果真机上发现——某些国产手机在指纹认证失败后,系统会弹出一个「重试」按钮,而模拟器上根本没有这个按钮。这就是模拟器和真机的差异。

真机测试:必须覆盖的5个场景

真机测试,没什么捷径。我列一个checklist,你照着测就行:

测试场景 操作步骤 预期结果
首次认证 打开App,点击指纹认证 弹出系统指纹对话框,认证成功进入主页
认证失败 用未录入的手指按压 显示「指纹不匹配」,可重试
多次失败后降级 连续失败5次 弹出PIN码/图案解锁界面
取消认证 点击指纹对话框外的区域或按返回键 对话框关闭,回到原界面
锁屏后恢复 认证过程中锁屏,再解锁 指纹对话框正常显示或自动重试
真机测试的黄金法则: 至少覆盖3台不同厂商的手机(华为、小米、三星各一台),且系统版本覆盖Android 10、11、12、13。我见过太多只在Pixel上测过的App,到了国产手机上指纹弹窗样式都变了。

SVG:指纹测试知识体系图

指纹认证测试体系 单元测试 Mockito 模拟依赖 覆盖成功/失败/异常 版本兼容性测试 UI测试 Espresso + UiAutomator 对话框显示/消失 认证结果UI跳转 模拟器测试 流程验证(UI/回调) 无法测准确率/速度 真机测试 多厂商/多系统版本 5个核心场景全覆盖

真机测试的注意事项

最后,我总结几条真机测试的实战经验,都是血泪教训:

  • 指纹录入状态: 测试前确保手机已经录入了至少一个指纹。我见过测试同学拿着没录指纹的手机测了半天,一直报错,还以为是bug。
  • 系统指纹设置: 有些手机(比如华为)在「设置-安全」里有个「指纹动画」开关,关掉后指纹对话框样式会变。记得测一下这个开关。
  • 多用户场景: 如果App支持多用户登录,每个用户的指纹数据是独立的。切换用户后,指纹认证应该重新绑定。
  • 后台进程杀死: 认证过程中把App切到后台,然后杀掉进程,再打开App——指纹状态应该正确恢复,不能出现「已认证但界面没刷新」的情况。
  • 电量与性能: 低电量模式下,有些手机会禁用指纹传感器。我曾在小米11上遇到过,电量低于5%时指纹认证直接返回错误,但系统没有给出明确提示。
特别提醒: 不要在测试机上开启「开发者选项」中的「不保留活动」——这会导致指纹认证的Activity被销毁重建,测试结果不准确。我曾经因为这个设置,排查了一整天的bug,最后发现是测试机设置的问题。

好了,指纹认证测试这块,从单元到UI,从模拟器到真机,基本就这些了。测试没有银弹,多跑几台真机,多覆盖几个边界场景,比什么都强。

公众号:蓝海资料掘金营,微信deep3321