指纹认证测试:单元测试、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的时候,最好把各个版本的返回值都覆盖到。嗯,这点很重要。
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()));
}
}
UiAutomator的performActionAndWait()来做,更稳定。
模拟器测试:能测什么,不能测什么
模拟器测试指纹,说白了就是「半真半假」。Android模拟器从API 28开始支持模拟指纹传感器,但仅限于模拟指纹录入和认证的流程,真正的指纹数据是不存在的。
能测的:
- 指纹对话框的UI展示和消失逻辑
- 认证成功/失败的回调路径
- 多次失败后的降级策略(比如弹出PIN码输入)
- 指纹取消操作的UI响应
不能测的:
- 指纹识别的准确率和速度
- 不同手指、不同角度、湿手/干手场景
- 指纹传感器在低电量或高温下的表现
- 系统级指纹服务的异常崩溃恢复
我记得有一次,模拟器上所有测试都通过了,结果真机上发现——某些国产手机在指纹认证失败后,系统会弹出一个「重试」按钮,而模拟器上根本没有这个按钮。这就是模拟器和真机的差异。
真机测试:必须覆盖的5个场景
真机测试,没什么捷径。我列一个checklist,你照着测就行:
| 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 首次认证 | 打开App,点击指纹认证 | 弹出系统指纹对话框,认证成功进入主页 |
| 认证失败 | 用未录入的手指按压 | 显示「指纹不匹配」,可重试 |
| 多次失败后降级 | 连续失败5次 | 弹出PIN码/图案解锁界面 |
| 取消认证 | 点击指纹对话框外的区域或按返回键 | 对话框关闭,回到原界面 |
| 锁屏后恢复 | 认证过程中锁屏,再解锁 | 指纹对话框正常显示或自动重试 |
SVG:指纹测试知识体系图
真机测试的注意事项
最后,我总结几条真机测试的实战经验,都是血泪教训:
- 指纹录入状态: 测试前确保手机已经录入了至少一个指纹。我见过测试同学拿着没录指纹的手机测了半天,一直报错,还以为是bug。
- 系统指纹设置: 有些手机(比如华为)在「设置-安全」里有个「指纹动画」开关,关掉后指纹对话框样式会变。记得测一下这个开关。
- 多用户场景: 如果App支持多用户登录,每个用户的指纹数据是独立的。切换用户后,指纹认证应该重新绑定。
- 后台进程杀死: 认证过程中把App切到后台,然后杀掉进程,再打开App——指纹状态应该正确恢复,不能出现「已认证但界面没刷新」的情况。
- 电量与性能: 低电量模式下,有些手机会禁用指纹传感器。我曾在小米11上遇到过,电量低于5%时指纹认证直接返回错误,但系统没有给出明确提示。
好了,指纹认证测试这块,从单元到UI,从模拟器到真机,基本就这些了。测试没有银弹,多跑几台真机,多覆盖几个边界场景,比什么都强。