前端TDD
前端单元测试浅谈
在TDD
中,要求开发者编写良好的测试。从<<测试驱动开发的艺术>>一书中,探索了如何真正开展的TDD工作,然而实际开展确实有一些困难。当然阅读其他开源项目的测试也能给予一些帮助。
就前端而言,单元测试可操作性相比集成测试等各种测试写起来更容易。所以可以从单测来开始TDD之路。
简单的区分单元测试
- 需要访问数据库的测试不是单测
- 需要访问网络的测试不是单测
- 需要访问文件系统的测试不是单测
良好的代码片段要规避哪些
良好的代码片段是单测的重点,那脏代码又是些什么呢?
- 条件逻辑过多
- 构造函数逻辑过多
- 全局变量过多
- 静态方法过多
- 不必要的代码、逻辑
- 外部依赖过多
TDD的要求
- 测试要快。快速运行,快速编写。 运行要足够快,否则会降低效率。比如说代码片段要求10行,超过10行就可以考虑进行分拆。每条测试运行在30ms左右。每个代码片段开发在几十秒至几分钟内(当然也有例外)
- 不可忽略失败的测试 保证测试必定通过
- 必须要重构
TDD的逻辑是
红灯(测试挂) -> 绿灯(测试通过) -> 重构(优化代码)
,在第二步的时候允许烂代码,甚至返回硬编码
如何做
- 需求分析,思考实现,进行需求分拆(分拆成测试点,而不是子任务)。此时是需要考虑如何使用,考虑如何“使用”产品代码,是一个实例方法还是一个类方法,是从构造函数传参还是从方法调用传参,方法的命名,返回值等。这时其实就是在做设计,而且设计以代码来体现。此时测试为红
- 实现代码让测试用例通过,此时为绿
- 重构,然后让测试通过
- 最终达到如下状态:
- 每个概念都被清晰的表达
- 不允许重复代码
- 不允许脏代码
- 通过测试
TDD的工作流
最终目标如下:
- 一个父单元,依赖两个子单元实现逻辑功能
- 两个子单元,每个都有单元测试描述他们的各自职责
- 父单元的测试,描述了两个子单元的交互
其流程可描述为:
-
1,接手一个新的特性需求 此时可以根据需求描述,制定集成测试的Todo-List,然后来做集成测试的编写(建议集成、单元测试分开进行)
-
2,分解需求,分成Todo-List(需求拆分、拆分成细小的单元),这个过程需要思考怎么将需求进行拆解以及归纳(如合并到同一个单元)
-
3,为每个Todo-List制定Test-List
-
4,按照Test-List编写单元测试,此时不需要立刻解决问题,延迟逻辑实现的编写时间 使用小型、单功能的单元
-
5,编写代码让上述Test-List通过
-
6,在保证测试通过的情况下优化、重构代码逻辑
-
7,对Todo-List中的每个Todo都进行3,4,5,6这几步操作
// 流程图 新需求 (对应步骤1) | | +------+--------+--------+ (步骤2) 需求1 需求2 需求3 需求4 | | +------+---------+--------+ (步骤3) 测试点1 测试点2 测试点3 测试点4 | | 编写代码 (步骤4) | | 测试通过 (步骤5) | | 重构代码 (步骤6)
举例:
需求为:做个todolist page,并使用localStorage来进行数据存储(即不需要网络的处理)参考项目
我们对以上的需求进行拆解,可大致得到如下子需求,此过程认为是单元拆解(也就是我们自身的Todo-List) 子需求:
- 1, 标题部分文字为todos
- 2, 输入框新增事项
- 3, 正文区为列表区
- 4, 列表默认列出所有事项
- 5, 列表可进行分类列出
- 6, 每项可以标记为已完成、或者删除
- 7, 操作区分别展示事项总数、展示的分类、清空操作
- 8, 页面载入时从localStorage中载入
- 9, 每项的操作同步更新至localStorage中
上述子需求是在初步拆解后,此时还需要规整下,规整后按照单元来:
- Header单元
- 1, 标题部分文字为todos
- Input单元
- 2, 输入框新增事项
- List单元
- 3, 正文区为列表区
- 4, 列表默认列出所有事项
- 5, 列表可进行分类列出
- ListItem单元
- 6, 每项可以标记为已完成、或者删除
- ListControl单元
- 7, 操作区分别展示事项总数、展示的分类、清空操作
- LocalStorage单元
- 8, 页面载入时从localStorage中载入
- 9, 每项的操作同步更新至localStorage中
将上述的增加测试用例:
-
Header单元
-
1, 标题部分文字为todos
a, 标题为todos
-
-
Input单元
-
2, 输入框新增事项
a, 用户输入"job",按下Enter, 则返回"job" b, 用户不输入,按下Enter,此时不返回
-
-
List单元
-
3, 正文区为列表区
a, 无数据时,则显示为空 b, 有10条数据,则显示10条记录
-
4, 列表默认列出所有事项
a, 初始有10条数据,全部为未完成,显示10条数据 b, 有10条数据,5条为已完成,则显示10条数据 c, 共10条数据,全部为已完成,则显示10条数据
-
5, 列表可进行分类列出
a, 分类为All,则展示所有事项 10条数据,全部为已完成,则显示10条数 10条数据,3条为已完成,则显示10条数据 10条数据,全部为未完成,则显示10条数据 b, 分类为Active,则展示未完成事项 10条数据,全部为已完成,则展示0条数据 10条数据,全部为未完成,则展示10条数据 10条数据,3条为已完成,则显示7条数据 c, 分类为Complete, 则展示已完成事项 10条数据,全部为已完成,则展示10条数据 10条数据,全部为未完成,则展示0条数据 10条数据,3条为已完成,则展示3条数据
-
-
ListItem单元
-
6, 每项可以标记为已完成、或者删除
a, 点击删除,则触发删除该项操作 b, 已完成标记,则触发该项完成操作
-
-
ListControl单元
-
7, 操作区分别展示事项总数、展示的分类、清空操作
a, 展示当前事项数量,如4 b, 展示当前的分类,如All, Active, Complete c, 清空操作触发清空操作 d, 分类切换触发分类改变操作 点击All, 触发分类All回调 点击Active,触发分类All回调 点击Complete,触发Complete回调
-
-
LocalStorage单元
-
8, 页面载入时从localStorage中载入
a, localStorage无数据,获取的数据为空列表 b, localStorage数据为10条,则获取10条数据
-
9, 每项的操作同步更新至localStorage中
a, localStorage原有0条,增加一条,则为1条 b, localStorage原有10条,增加一条,则为11条 c, localStorage原有10条,删除后,为0条 d, localStorage原有0条,删除后,为0条
-
参考