移动应用遗留系统重构(6)- 测试篇

原创 DTO咨询师黄俊彬 ThoughtWorks CAC 敏捷教练 今天

前言

 

上一篇移动应用遗留系统重构(5)- 重构方法篇我们分享了进行依赖解除的重构流程。主要为4个操作步骤,识别内聚包、解除依赖、移动、验收。同时最后也提出了一个问题,重构时如何保证功能的正确性,不会修改出新问题?

 

其实这个问题容易但又不简单容易的是把修改得功能仔细测一篇保证所有功能正常就可以了。不简单的是如何全面、高效、可重复的执行这个过程。我们很容易联想到的方案就是自动化测试。但最大的问题是,对大部分遗留系统来说都是没有任何自动化测试。而且大量的坏味道代码,可测试性低,我们也很难补充充分的自动化测试。那么我们有什么折中的策略吗?

 

测试策略

 

我们先来看看Google Android开发者官网上对于测试的介绍,将不同的类型的测试分为三类测试(即小型、中型和大型测试)。

 

图片

图片来源developer.android.com

  • 小型测试是指单元测试,用于验证应用的行为,一次验证一个类。

  • 中型测试是指集成测试,用于验证模块内堆栈级别之间的互动或相关模块之间的互动。

  • 大型测试是指端到端测试,用于验证跨越了应用的多个模块的用户操作流程。

 

前面提到对于遗留单体系统来说通常没有任何自动化测试,并且通常内部结构耦合严重,所以实施中小型的成本非常高。显然对于遗留系统,测试金字塔模型适用度较低。所以对于遗留系统,可能比较适合的策略模型如下:

 

图片

 

对于遗留单体系统,一个可行的思路是先补充中大型的测试,作为基本的冒烟测试,重构优化内部结构后再及时补充中小型测试。

 

CloudDisk示例

 

对于我们这个浓缩版的CloudDisk,界面上也比较简单。主要是有一个主界面,主界面上主要为文件、动态、用户。(后续的MV*重构篇会持续补充页面交互及逻辑)

 

图片

 

我们可以设计一组UI的测试验证基本的功能。主要的几个测试点如下:

  1. 主界面能正常运行并显示3个Fragment

  2. 3个Fragment能正常显示

  3. 点击登录按钮,能够跳转到登录页面

 

测试设计的用例如下:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@RunWith(AndroidJUnit4.class)@LargeTestpublic class SmokeTesting {
    @Test    public void should_show_fragment_list_when_activity_launch() {        //given        ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);        scenario.onActivity(activity -> {            //when            onView(withText(R.string.tab_user)).perform(click());            //then            List<Fragment> fragments = activity.getSupportFragmentManager().getFragments();            assertThat(fragments.size() == 3);            assertThat(fragments.get(0) instanceof FileFragment);            assertThat(fragments.get(1) instanceof DynamicFragment);            assertThat(fragments.get(2) instanceof UserCenterFragment);        });    }
    @Test    public void show_show_file_ui_when_click_tab_file() {        //given        ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);        scenario.onActivity(activity -> {            //when            onView(withText(R.string.tab_file)).perform(click());            //then            onView(withText("Hello file fragment")).check(matches(isDisplayed()));        });    }
    @Test    public void show_show_dynamic_ui_when_click_tab_dynamic() {        //given        ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);        scenario.onActivity(activity -> {            //when            onView(withText(R.string.tab_dynamic)).perform(click());            //then            onView(withText("Hello dynamic fragment")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));        });    }
    @Test    public void show_show_user_center_ui_when_click_tab_dynamic() {        //given        ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);        scenario.onActivity(activity -> {            //when            onView(withText(R.string.tab_user)).perform(click());            //then            onView(withText("Hello user center fragment")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));        });    }
    @Test    public void show_show_login_ui_when_click_login_button() {        //given        ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);        scenario.onActivity(activity -> {            Intents.init();            //when            onView(withId(R.id.fab)).perform(click());            //then            intended(IntentMatchers.hasComponent("com.cloud.disk.platform.login.LoginActivity"));            Intents.release();        });    }}

详细代码见Github提交

 

我们可以将用例运行在Robolectric上,提高反馈的速度,执行命令如下:

  •  
./gradlew testDebug --tests SmokeTesting
 

测试执行结果如下:

图片

当然实际的项目里情况更复杂,数据可能来自网络服务、数据库等等。我们还需要进行Mock。后续的MV*重构篇会持续补充常见坏味道示例代码及更多的自动化测试用例。

更多测试框架及设计可以参考Google官方在 Android 平台上测试应用

 

总结

 

这一篇我们介绍了常用的测试分类及遗留系统的测试策略,对于遗留单体系统,一个可行的思路是先补充中大型的测试,作为基本的冒烟测试,重构优化内部结构后再及时补充中小型测试。同时也给CloudDisk补充了一组基础的大型测试作为冒烟测试,作为后续重构的基本守护测试。

 

下一篇移动应用遗留系统重构(7)- 解耦重构演示篇(一) 我们将基于方法篇的流程开始对CloudDisk进行重构的改造,具体的解耦操作会以视频的方式展示。

 

参考资料

Github:

https://github.com/junbin1011/CloudDisk/commit/e5a9757db372a01c22d433434dad2ee5d643fa55

 

developer.android.com

 

Robolectric: 

http://robolectric.org/

 

在 Android 平台上测试应用:

https://developer.android.com/training/testing?hl=zh-cn

 

CloudDisk示例代码

https://github.com/junbin1011/CloudDisk

 

系列链接

 

移动应用遗留系统重构(1)- 开篇

移动应用遗留系统重构(2)- 架构篇

移动应用遗留系统重构(3)- 示例篇

移动应用遗留系统重构(4)- 分析篇

移动应用遗留系统重构(5)- 重构方法篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值