您的位置:首页 > 产品设计 > UI/UE

基于TDD的UI端到端可测试性改进

2017-12-08 11:13 381 查看

解决思路

UI端到端是指界面输入数据,运行业务逻辑,业务逻辑从配置文件、数据库(关系型或NoSQL)或者内存中取得数据,基于数据进行算法运算,然后界面收到业务逻辑结果,更新相关控件实现结果数据的展示。



UI端到端的测试,是指能够贯穿上述过程的测试。端到端测试的优势是,其测试用例是基于功能场景的,这样可以得到相对稳定的测试用例集,避免局限于类单元接口测试,从而能够规避测试用例难以维护的缺点。因此它既可以作为对UI界面的单元测试,也可以作为对UI界面的验收测试。

那么如何设计GUI的架构,使得上述的端到端测试能够实施?

主要从两个方面来考虑。

实现UI与业务逻辑的分离

实现业务逻辑与数据库的分离

一、UI与业务逻辑的分离。

要实现业务逻辑的可测试性,最简单的一点就是把数据相关的操作提炼出来,形成业务逻辑API。然后测试可以针对业务逻辑API进行。



但有些情况下,尤其是UI界面比较复杂的时候,比如某些控件上的值需要进行校验,控件之间的关系相互关联,甚至为了优化界面显示效率,要实施复杂算法。所有这些,从严格意义上来说,不是纯粹的UI问题。这些逻辑关系需要提炼出来,形成界面展现层。从而, UI界面变成简单的控件操作或者字符操作,其简单到不需要进行严格测试,最终能够达到更换控件、更换界面布局不影响界面展现层及业务逻辑层的目的。换句话说,最理想化的情况是,我们可以抛弃到UI界面,换成字符界面,换成Web界面,仍然可以达到不影响界面展现层及业务逻辑层的实现。

界面展现层(Presenter)的职责变成为:响应UI的刺激事件,通过UI接口从UI获取数据,调用业务逻辑API完成逻辑运算,通过UI接口刷新UI。但具体由哪个UI来实现UI API,Presenter并不需要知道;具体由哪个Business来实现Business rule API,Presenter也并不知道。这样以来,Presenter就完成了UI接口与Business rule接口层面的交互关系,从而完全把UI和Business rule进行了解耦。UI和Business rule需要从外部注入到Presenter中。

这样以来,我们需要对界面展现层(Presenter)进行测试,其测试重点变成测试UI和业务逻辑之间的交互关系。同时,还需要对业务逻辑部分进行测试,其测试重点为业务逻辑的正确性和性能等。



对于Presenter的测试来讲,如果对UI实现UI Stub,来感知Presenter对UI的刷新。对Business rule实现Business rules Stub,对业务逻辑部分进行模拟,就可以快速有效的验证交互逻辑的正确性,以及界面逻辑的正确性。即如下图所示:



二、业务逻辑与数据库的分离

数据库是另外一个严重影响快速反馈的重要因素。操作大型数据库,比如Oracle、SQL Server等成本非常高,在代码单元阶段很难构造出实际环境,即使能够构造出来,连接数据库,注入数据,删除数据等操作都非常耗时。测试的最重要原则一直,就是测试用例之间要正交,只有正交,才能帮助程序员能够快速定位错误。数据库中数据的正交性,不是难于实现,就是耗时太长。

业务逻辑如果和数据库严重耦合,那么业务逻辑也变得难于测试。

为了实现业务逻辑和数据的解耦,可以考虑在业务逻辑和数据库之间建立API,在单元测试阶段,可以在内存中构造测试数据来代替真实数据库,从而使得测试效率大大提升。如下图所示:



结合上述分析,我们采用有效手段,来把各种可能的变化点,通过API进行隔离,可以大大提升UI界面的端到端可测试性。针对UI、Presenter和Business rule的测试,可以形成测试总线。测试总线不仅能够加速故障定位,而且可以随时可回归测试。因为隔离了具体UI、具体数据库、具体Business中依赖的第三方库,全部在内存中进行操作,它本质上就是完全自动化的。

在有了上述思路之后,我们采用测试驱动开发的方式来完成上述各个组件的开发,这在实现层面上,又进一步使得代码更加简洁、优化,更能够从业务场景出发来构建测试用例集。测试驱动开发的小步快速的节奏,对于比较繁琐的界面类开发,也具备更好的适应性。

实践情况

示例界面的具体需求是:

给定一个时间范围,和对象列表,查询出若干测试指标的值,这些值有的是集合类型,有的是数值类型。这些值需要显示在一个表格里面。能够支持回放功能,回放时能够播放、暂停、复位。要能够显示播放时的具体时间。对象的测试指标值,在回放的某个时刻是没有数据上报的,则不显示该对象行。要能够支持对对象、时间范围、测试指标的选择。

下面给出回放界面的关键实施步骤及代码片段。

一、深入了解需求,确定界面功能

首先,我们要做最有价值的事情,价值来源于客户。因此需要和客户深入沟通,了解我们软件功能的服务对象、在什么场景下,使用什么样的功能,来解决客户的什么问题,以及解决问题时需要面临哪些约束(性能约束、安全性约束等)。

了解了这些需求以后,我们可以针对某个界面,输出其功能列表,类似于这样:

1)初始。选择某些条件后,回放界面能够显示对象的起始时间处的测试指标值,播放可以开始,时间显示初始时间。

2)播放。测试指标值按照播放时间顺序显示。此时暂停功能处于可用状态。时间显示播放时间。

3)暂停。暂停后Timer失效。时间显示暂停时时间。

4)暂停后播放。从暂停处开始播放。

5)播放后复位。回复到初始状态。

6)暂停后复位。回复到初始状态。

7)播放到结束时,再次播放,从初始位置重新播放。

二、针对功能列表,按照业务价值及风险情况决定测试用例选择顺序。

只有初始状态完成后,播放才能进行控制。因此选择【初始】用例作为第一个用例。用这个用例驱动出Presenter、UI API、Business API(因为要取得回放用数据)。

第一个用例的功能,包含了获取数据,此时可以驱动出DataBase API。当然,这里可以做设计决策,比如如果觉得获取数据非常简单,可以直接构造出数据,不用考虑DataBase API。

实际上,第一个用例还是比较复杂,但由于其能够贯穿从UI到DataBase的全部流程,能够驱动整个框架的建立,因此是有其价值的。

可以考虑将其分为两部分:一部分为播放控制状态的测试,一部分为表格数据正确性的测试。

总之,选取的原则是:具备较高业务价值,同时能够对框架建立有驱动作用的优先选择。

三、编码

编码时严格按照测试驱动开发(TDD)的三步军规进行:

1)编写测试,测试不通过;

2)快速编写代码,使测试通过;

3)重构,优化代码结构

下面给出测试总线中各部分的具体代码实例。

首先给出基于GooglTest测试环境的准备:



1)界面初始化

测试代码:



上图给出了测试数据的情况,涵盖了所有的数据用例。本测试用例检查了条件选择之后view的状态情况,包括时长,开始时间,IE选择情况,值情况,播放状态。

UI模拟层代码:



为便于测试,UI模拟层代码将所有数据公开,测试代码可以直接访问状态。真实产品代码中这些数据成员都不可见。

UI API见上图中的virtual部分,不再详述。UI API提供了Presenter和UI之间的交互接口。在这里需要注意的是,如果界面非常复杂,那么UI API的接口会非常巨大,这时需要进行Presenter和UI之间的协议定义,尽量从业务角度来对接口进行归类处理,采用诸如结构、文本协议等方式进行Presenter和UI之间数据的传递,从而有效减少UI API带来的接口膨胀。

Business模拟层代码:



该代码直接在内存中构造了数据,并返回给Presenter。数据用例设计见测试代码处。

Presente实现代码:



Form界面实现代码:



我们可以明显看到,在界面层的代码中,没有任何的逻辑存在,只是进行了控件状态的更新。这种将界面层进行的薄化处理,使得界面的维护成本大大降低,也增加了界面实现的巨大灵活性,更换控件、更换界面实现对Presenter、Business各层的代码没有任何影响。同时,界面的简单化,使得对真实界面显示的测试降到最低,也大大降低了界面集成后的测试时间,从而极大提升了研发效率。

2)播放部分

为简化起见,下面只给出播放部分的测试代码实例,从测试用例可以看出代码的演进过程,并且测试用例的设计也充分体现了业务需求。



效果评估

代码复杂度检查情况如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ui 测试 tdd 数据库