17、Launcher的测试:单元测试(JUnit)、UI自动化测试(Espresso)、性能测试(Systrace与Profiler)

做Launcher开发,最怕什么?

我最怕的是:改了一行代码,桌面滑动卡了,图标拖不动了,或者Widget加载不出来了。更可怕的是——这些问题测试没发现,直接发布到了用户手里。

所以今天咱们聊聊Launcher的测试。说白了,就是怎么保证你写的代码靠谱。我这些年踩过的坑,十有八九都是测试没做到位。嗯,咱们一个一个说。

核心观点:Launcher测试不是可选项,是必选项。桌面是用户接触最多的系统应用,稳定性要求极高。单元测试保逻辑,UI测试保交互,性能测试保体验,三者缺一不可。

17.1 单元测试:JUnit + Mockito,把逻辑锁死

单元测试,说白了就是测你的函数对不对。比如你写了个排序算法,把App按安装时间排好,那你就写个测试,扔进去几个App,看排出来的顺序对不对。

我个人习惯是:每个工具类、每个Presenter、每个ViewModel,都必须有单元测试覆盖。为什么?因为Launcher的逻辑太碎了——拖拽、排序、分页、文件夹展开,随便一个改动都可能引发连锁反应。

17.1.1 测试什么?

  • 数据层:数据库读写、数据转换、缓存策略
  • 逻辑层:排序算法、过滤规则、手势判断
  • 状态管理:编辑模式切换、文件夹展开/收起、壁纸设置

17.1.2 怎么写?

我一般用JUnit 4 + Mockito。Mockito用来模拟那些依赖——比如Context、ContentProvider、系统服务。你想想看,单元测试里你总不能真的去操作数据库吧?那太慢了,也不可控。

// 示例:测试App排序逻辑
public class AppListSorterTest {

    @Mock
    private AppListRepository mockRepository;

    private AppListSorter sorter;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        sorter = new AppListSorter(mockRepository);
    }

    @Test
    public void testSortByInstallTime() {
        // 准备数据
        List<AppInfo> apps = Arrays.asList(
            new AppInfo("AppA", 1000),
            new AppInfo("AppB", 500),
            new AppInfo("AppC", 2000)
        );

        // 执行排序
        List<AppInfo> sorted = sorter.sortByInstallTime(apps);

        // 验证结果:安装时间最早的排最前
        assertEquals("AppB", sorted.get(0).getName());
        assertEquals("AppA", sorted.get(1).getName());
        assertEquals("AppC", sorted.get(2).getName());
    }
}

我的小技巧:写单元测试时,尽量只测一个函数。如果一个测试里测了三个功能,出了问题你都不知道是哪个环节挂了。保持测试的原子性,这是我从一次惨痛教训中悟出来的。

17.2 UI自动化测试:Espresso,模拟用户操作

单元测试能保证逻辑正确,但用户不关心逻辑,用户只关心操作。点一下图标,App能不能打开?长按桌面,能不能进入编辑模式?拖拽图标到文件夹,会不会崩溃?

这些就得靠UI自动化测试了。我推荐用Espresso,它是Google官方出的,和Android Studio集成得很好。

17.2.1 测试场景

  • 点击测试:点击桌面图标,验证App是否启动
  • 拖拽测试:长按图标,拖到另一个位置,验证位置是否正确
  • 手势测试:双指缩放、滑动翻页、下拉通知栏
  • 状态恢复:旋转屏幕、按Home键再回来,桌面状态是否保持

17.2.2 怎么写?

Espresso的写法很直观。你想想看,就像在写操作说明书一样:先找到元素,再执行操作,最后验证结果。

// 示例:测试点击桌面图标打开App
@RunWith(AndroidJUnit4.class)
public class LauncherHomeScreenTest {

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

    @Test
    public void testClickIcon_opensApp() {
        // 找到第一个应用图标
        onView(withId(R.id.app_icon))
            .perform(click());

        // 验证是否跳转到目标App
        intended(hasComponent(
            hasShortClassName(".MainActivity")));
    }

    @Test
    public void testLongPress_entersEditMode() {
        // 长按桌面空白区域
        onView(withId(R.id.desktop_container))
            .perform(longClick());

        // 验证编辑模式UI出现
        onView(withId(R.id.edit_mode_panel))
            .check(matches(isDisplayed()));
    }
}

注意:UI测试很慢,别写太多。我一般只覆盖核心流程——比如启动、翻页、拖拽、编辑模式。那些边边角角的逻辑,交给单元测试去测。UI测试跑一次要几分钟,你想想看,如果每个分支都测,CI得跑多久?

17.3 性能测试:Systrace + Profiler,揪出卡顿元凶

Launcher最怕什么?卡顿。用户滑动桌面,一帧一帧地掉,那体验简直灾难。我遇到过最离谱的一次:某个第三方Widget加载时,主线程被占用了200ms,导致桌面滑动直接卡死。

性能测试,就是用来抓这种问题的。我常用的工具有两个:SystraceAndroid Profiler

17.3.1 Systrace:看系统级调用

Systrace能告诉你每一帧里,CPU在干什么。是布局渲染慢了?还是GC在作祟?还是某个Binder调用卡住了?一目了然。

# 抓取Launcher的Systrace
python systrace.py -t 10 -o launcher_trace.html \
    -a com.android.launcher3 \
    sched freq idle am wm gfx view binder_driver

抓完之后,打开HTML文件,你会看到一条条彩色条。嗯,这里要注意:重点关注掉帧的地方。如果某一帧超过了16.6ms,那它就是卡顿的元凶。点进去看,是哪个函数占用了太多时间。

17.3.2 Android Profiler:看内存和CPU

Profiler更直观,直接在Android Studio里就能用。我一般用它来查内存泄漏和CPU峰值。

  • 内存:反复进出桌面,看内存会不会一直涨。如果涨了不降,那就是泄漏了。
  • CPU:快速滑动桌面,看CPU使用率会不会飙升。如果超过50%,那就要优化了。
  • 网络:如果Launcher有联网功能(比如壁纸下载),看网络请求会不会阻塞主线程。

我的经验:性能测试一定要在真机上跑。模拟器上跑出来的数据,跟真机差太远了。我曾经在模拟器上测得好好的,结果一上真机,卡成PPT。后来发现是模拟器的GPU加速太强,掩盖了问题。

17.4 知识体系总览

说了这么多,咱们用一张图把Launcher测试的体系串起来。你想想看,测试就像一张网,从底层逻辑到上层交互,再到整体性能,层层覆盖。

Launcher测试知识体系 单元测试 JUnit + Mockito • 数据层:数据库、缓存 • 逻辑层:排序、过滤 • 状态管理:编辑模式 • 工具类:手势判断 • 原子性:一个测试一个点 • 快速反馈:秒级运行 UI自动化测试 Espresso • 点击测试:图标启动 • 拖拽测试:位置验证 • 手势测试:缩放翻页 • 状态恢复:旋转屏幕 • 核心流程覆盖 • 真机运行验证 性能测试 Systrace + Profiler • Systrace:系统级调用 • 掉帧分析:16.6ms阈值 • Profiler:内存泄漏 • CPU峰值:滑动场景 • 真机测试优先 • 反复验证稳定性 三层覆盖:逻辑正确 → 交互正常 → 性能流畅

17.5 避坑指南

最后,分享几个我踩过的坑,希望能帮你省点时间。

  • 单元测试别依赖Android框架:能用纯Java测的就别用Android的API。Mockito是你的好朋友。
  • UI测试别写太多:跑一次UI测试要几分钟,写多了CI会崩溃。我一般只覆盖核心流程,大概10-15个测试用例。
  • 性能测试别在模拟器上跑:模拟器的GPU和CPU跟真机差太多。我吃过这个亏,后来都改成真机测试了。
  • 测试数据要真实:别用假数据。我见过有人测试时用3个图标,结果上线后用户装了200个App,直接卡死。测试数据要尽量模拟真实场景。

我的习惯:每次提交代码前,先跑一遍单元测试,再跑一遍核心UI测试。性能测试每周跑一次,看看有没有退化。这样坚持下来,Launcher的稳定性确实好了很多。

好了,Launcher测试这块就聊到这儿。测试不是万能的,但不测试是万万不能的。你想想看,用户每天打开手机第一眼看到的就是桌面,如果桌面都卡,那体验得多糟糕。所以,该写的测试,一个都不能少。

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