Android-PickerView UI自动化测试:使用UI Automator进行端到端测试
1. 测试环境搭建
1.1 依赖配置
在app/build.gradle中添加UI Automator依赖:
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
// UI Automator测试框架
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
// AndroidX测试支持库
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
}
1.2 测试工程结构
app/src/androidTest/java/com/bigkoo/pickerviewdemo/
├── TimePickerTest.java // 时间选择器测试类
├── OptionsPickerTest.java // 选项选择器测试类
└── BaseTest.java // 测试基类(包含共用方法)
2. 核心测试场景设计
2.1 测试用例矩阵
| 模块名称 | 测试类型 | 关键操作点 | 验证指标 |
|---|---|---|---|
| 时间选择器 | 功能测试 | 日期切换、时间调整、确认选择 | 选择结果正确显示、回调正常触发 |
| 省市区选择器 | 功能测试 | 省市联动、三级选择、确认按钮 | 选择结果匹配预期、联动效果正确 |
| 自定义选择器 | UI兼容性测试 | 主题切换、字体大小调整、夜间模式 | 界面元素无重叠、文字清晰可辨 |
| 农历选择器 | 边界测试 | 闰月切换、节气显示、公农历转换 | 日期计算准确、切换无崩溃 |
2.2 测试流程图
3. 测试代码实现
3.1 基础测试类封装
public class BaseTest {
protected UiDevice mDevice;
protected Context mContext;
protected String PACKAGE_NAME = "com.bigkoo.pickerviewdemo";
protected int LAUNCH_TIMEOUT = 5000;
@Before
public void setUp() throws Exception {
// 初始化UI Automator设备对象
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
// 启动应用
mDevice.pressHome();
String launcherPackage = mDevice.getLauncherPackageName();
mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
// 启动测试应用
Intent intent = mContext.getPackageManager()
.getLaunchIntentForPackage(PACKAGE_NAME);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
// 等待应用启动
mDevice.wait(Until.hasObject(By.pkg(PACKAGE_NAME).depth(0)), LAUNCH_TIMEOUT);
}
// 截图辅助方法
protected void takeScreenshot(String name) {
File path = new File(Environment.getExternalStorageDirectory(), "PickerViewTest");
if (!path.exists()) path.mkdirs();
String fileName = name + "_" + System.currentTimeMillis() + ".png";
File file = new File(path, fileName);
mDevice.takeScreenshot(file);
Log.d("TestScreenshot", "Saved to: " + file.getAbsolutePath());
}
}
3.2 时间选择器测试用例
@RunWith(AndroidJUnit4.class)
public class TimePickerTest extends BaseTest {
private UiObject timeButton;
private UiObject confirmButton;
@Before
public void initElements() throws UiObjectNotFoundException {
// 获取时间选择器按钮
timeButton = mDevice.findObject(new UiSelector()
.text("时间选择器")
.className("android.widget.Button"));
// 预定义确认按钮(在弹窗中)
confirmButton = mDevice.findObject(new UiSelector()
.resourceId("com.bigkoo.pickerviewdemo:id/tv_finish")
.className("android.widget.TextView"));
}
@Test
public void testTimePickerSelection() throws UiObjectNotFoundException {
// 点击时间选择器按钮
timeButton.click();
// 等待弹窗出现
mDevice.wait(Until.hasObject(By.res("com.bigkoo.pickerviewdemo:id/timepicker")), 2000);
// 获取时间选择器滚轮
UiScrollable hourScroll = new UiScrollable(new UiSelector()
.resourceId("com.bigkoo.pickerviewdemo:id/wheelview_h")
.className("android.widget.ScrollView"));
// 滑动选择小时(从当前位置向后滑动2个单位)
hourScroll.flingForward();
hourScroll.flingForward();
// 点击确认按钮
confirmButton.click();
// 验证结果显示
UiObject resultText = mDevice.findObject(new UiSelector()
.resourceId("com.bigkoo.pickerviewdemo:id/btn_Time")
.className("android.widget.Button"));
// 断言结果不为空
assertTrue("时间选择结果为空", resultText.getText().length() > 0);
// 截图保存
takeScreenshot("time_picker_result");
}
@Test
public void testLunarCalendarSwitch() throws UiObjectNotFoundException {
// 点击农历选择器按钮
mDevice.findObject(new UiSelector().text("农历时间选择")).click();
// 等待弹窗出现
mDevice.wait(Until.hasObject(By.res("com.bigkoo.pickerviewdemo:id/cb_lunar")), 2000);
// 切换到农历模式
UiObject lunarCheckBox = mDevice.findObject(new UiSelector()
.resourceId("com.bigkoo.pickerviewdemo:id/cb_lunar")
.className("android.widget.CheckBox"));
lunarCheckBox.click();
// 验证农历元素是否显示
UiObject lunarText = mDevice.findObject(new UiSelector()
.textContains("农历")
.className("android.widget.TextView"));
assertTrue("农历模式切换失败", lunarText.exists());
takeScreenshot("lunar_calendar_mode");
}
}
3.3 省市区选择器测试用例
@RunWith(AndroidJUnit4.class)
public class OptionsPickerTest extends BaseTest {
@Test
public void testProvinceCityLinkage() throws UiObjectNotFoundException {
// 点击省市区选择器按钮
mDevice.findObject(new UiSelector().text("省市区选择")).click();
// 等待弹窗加载完成
mDevice.wait(Until.hasObject(By.res("com.bigkoo.pickerviewdemo:id/optionspicker")), 3000);
// 获取省、市、区三个滚轮
UiScrollable provinceScroll = new UiScrollable(new UiSelector()
.index(0)
.className("android.widget.ScrollView"));
UiScrollable cityScroll = new UiScrollable(new UiSelector()
.index(1)
.className("android.widget.ScrollView"));
// 选择广东省(向下滑动3次)
for (int i = 0; i < 3; i++) {
provinceScroll.flingForward();
}
// 等待市级数据加载
Thread.sleep(500);
// 选择深圳市(向下滑动2次)
for (int i = 0; i < 2; i++) {
cityScroll.flingForward();
}
// 点击确认按钮
mDevice.findObject(new UiSelector().text("确认")).click();
// 验证结果
UiObject resultButton = mDevice.findObject(new UiSelector()
.resourceId("com.bigkoo.pickerviewdemo:id/btn_Options"));
String resultText = resultButton.getText();
assertTrue("选择结果不匹配", resultText.contains("广东") && resultText.contains("深圳"));
takeScreenshot("province_city_result");
}
}
4. 测试执行与报告
4.1 命令行执行测试
# 克隆项目
git clone https://gitcode.com/gh_mirrors/an/Android-PickerView
# 进入项目目录
cd Android-PickerView
# 执行所有测试用例
./gradlew connectedAndroidTest
# 执行指定测试类
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.bigkoo.pickerviewdemo.TimePickerTest
4.2 测试报告解析
测试报告默认生成路径:
app/build/reports/androidTests/connected/index.html
报告包含以下关键信息:
- 测试用例执行总数
- 通过/失败/跳过的测试数
- 每个测试用例的执行时间
- 失败用例的详细堆栈跟踪
- 截图附件链接
4.3 持续集成配置
在项目根目录创建.github/workflows/android-test.yml:
name: Android UI Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Run UI Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedAndroidTest
- name: Upload Test Report
uses: actions/upload-artifact@v3
if: always()
with:
name: test-report
path: app/build/reports/androidTests/connected/
5. 常见问题与解决方案
5.1 测试稳定性问题
| 问题描述 | 解决方案 | 实施代码 |
|---|---|---|
| 弹窗加载超时 | 增加显式等待 | mDevice.wait(Until.hasObject(...), 5000); |
| 滑动操作不稳定 | 使用精准滚动代替快速滑动 | scrollToBeginning(10); scrollForward(3); |
| 元素识别失败 | 优先使用resourceId定位 | By.res("com.bigkoo.pickerviewdemo:id/btn_Time") |
5.2 复杂场景处理
处理日期范围选择的示例代码:
public void testDateRangeSelection() throws UiObjectNotFoundException {
// 启动日期范围选择器
mDevice.findObject(new UiSelector().text("日期范围选择")).click();
// 选择开始日期(今天)
selectDate(0, 0, 0); // 年偏移,月偏移,日偏移
// 选择结束日期(今天+7天)
mDevice.findObject(new UiSelector().text("结束日期")).click();
selectDate(0, 0, 7);
// 验证日期范围
UiObject resultText = mDevice.findObject(new UiSelector().resourceId("com.bigkoo.pickerviewdemo:id/tv_date_range"));
String[] dates = resultText.getText().split(" - ");
// 验证日期格式
assertTrue("日期格式不正确", dates[0].matches("\\d{4}-\\d{2}-\\d{2}"));
assertTrue("日期格式不正确", dates[1].matches("\\d{4}-\\d{2}-\\d{2}"));
}
// 日期选择辅助方法
private void selectDate(int yearOffset, int monthOffset, int dayOffset) throws UiObjectNotFoundException {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, yearOffset);
calendar.add(Calendar.MONTH, monthOffset);
calendar.add(Calendar.DAY_OF_MONTH, dayOffset);
// 选择年
mDevice.findObject(new UiSelector().text(String.valueOf(calendar.get(Calendar.YEAR)))).click();
// 选择月
mDevice.findObject(new UiSelector().text(String.valueOf(calendar.get(Calendar.MONTH) + 1))).click();
// 选择日
mDevice.findObject(new UiSelector().text(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)))).click();
}
6. 测试优化策略
6.1 测试效率提升
- 测试用例并行执行
android {
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
animationsDisabled true // 禁用动画加速测试
}
}
- 关键路径优先测试
// 使用@FixMethodOrder指定测试顺序
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PriorityTest {
@Test
public void test01_BasicFunction() {} // 基础功能优先测试
@Test
public void test02_AdvancedFunction() {} // 高级功能随后测试
}
6.2 测试覆盖率提升
| 测试类型 | 覆盖目标 | 实施方法 |
|---|---|---|
| 功能覆盖 | 100%核心功能 | 基于功能点矩阵设计用例 |
| 界面覆盖 | 100%可见UI元素 | 使用Hierarchy Viewer分析界面 |
| 场景覆盖 | 80%用户场景 | 基于用户行为分析设计场景 |
7. 总结与展望
Android-PickerView作为一款功能丰富的选择器控件,其UI自动化测试需要覆盖时间选择、选项联动、自定义样式等核心场景。通过UI Automator框架,我们可以实现对这些场景的自动化验证,确保控件在不同设备和系统版本上的稳定性和一致性。
未来测试优化方向:
- 引入视觉回归测试(Visual Regression Testing)
- 实现AI驱动的智能测试用例生成
- 构建测试数据工厂,支持动态测试数据生成
完整测试代码可在项目的androidTest目录下找到,建议结合持续集成系统定期执行,确保控件功能的长期稳定。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



