背景
最近在重构一个框架的代码,在重构的过程中就在想一个问题,如何做单元测试,当然,这个问题不是如何使用JUnit
的问题,而且如何使做单元测试更充分,如果你有时间写几千个测试用例,这个也许不是问题,但当你的系统足够庞大,而你的人力和时间有限的情况下,如何最大限度的完成单元测试。
天上是不会掉馅饼的,有的话也只会砸到天才,砸不到普通人,我们都是普通人,不是天才,据说天才们已经想到方案了睡觉的时候,程序能不能自动查 bug,让你在睡觉的时候自动帮你查bug。如果没有完美的方案,有没有退而求其次的方案。这个就是今天要讨论的主题。
单元测试主要解决两个问题
- 系统错误
- 业务错误
系统错误是指,你的代码会不会报500,业务错误是指程序处理的结果符不符合预期,在单元测试中,系统错误由单元测试框架自动处理,框架会抛出exception,业务错误由开发人员指定,通过Assert方式实现。
我不指望不写一行代码就能解决我业务错误,但我觉得可以通过自动化但方式解决系统错误。实际上,如果我们的系统能够做到没有系统错误,已经是很健壮了。
实现
迷宫
如果把我们的代码比作一座迷宫,每个函数都是迷宫中的一个通道,迷宫由入口和出口,那么只要我们在迷宫中逛的时间足够长,走过的路径足够多,就能把整个迷宫都走一遍。用到单元测试上,就是只要单元测试程序在你的代码里长时间的瞎转悠,就能把你代码都走过一遍,也就是我们所说的代码覆盖率,随机参数+随机路径,只要这两个随机样本足够大,理论上就能达到100%的代码覆盖率并且完成各种场景参数测试。而这个刚好是自动化最擅长的,因此理论上是可以实现的。
脚本
如果我们希望实现以上构想,我们需要一个能够描述单元测试的语言,至今还未见过专门的单元测试语言,你可以用json,但json可读性和书写都很麻烦,我一直觉得markdown是最好的标记格式,我们可以用markdown描述我们的单元测试信息,程序通过读取描述文件进行自动化单元测试,我大概列了一个描述文件的样例。
# [config] <li>casecount:100</li> <li>level:10</li> # [object]MpaasUser # [class]MpaasQuery <h2>eq</h2> <li>position:first</li> <li>rand:0%</li> <li>times:1-100</li>field
- type:string
- value:rand/static
- valueType:raw/normal
不仅程序容易处理,可读性也很强,在markdown编辑器下效果是这样的。

demo
写了一个demo,运行的结果如下:
public void case1_mpaasquery{ query.conjuctionOr() .distinct("string") .ne("string", "string") .view("string") .orderBy("string", "string") .include("string") .in("string", "string") .conjuctionOr() .unSelect("string") .exclude("string") .table("string") .groupBegin() .ne("string", "string") .in("string", "string") .notIn("string", "string") .exclude("string") .distinct("string") .rowid("string") .gt("string", "string") .update("string") .addClause("string", "string", "string") .eqNocase("string", "string") .clone() .update("string") .in("string", "string") .isNull("string") .distinct("string") .isNull("string") .orderBy("string", "string") .update("string") .setVar("string", "string") .pageBegin(100) .addRowIdClause("string", "string", "string") .notLike("string", "string") .notIn("string", "string") .rowid("string", "string", "string") .sql("string") .hashCode() .isNull("string") .groupBegin() .countSql("string") .eq("string", "string") .eqNocase("string", "string") .isNull("string") .and() .notLike("string", "string") .isNull("string") .rowid("string") .and() .update("string") .likeNocase("string", "string") .select("string") .sql("string") .countSql("string") .groupBy("string") .conjuctionAnd() .like("string", "string") .eqNocase("string", "string") .lt("string", "string") .page(100) .table("string") .groupBegin() .countSql("string") .countSql("string") .orderBy("string", "string") .setVar("string", "string") .ne("string", "string") .sql("string") .exclude("string") .notifyAll() .eq("string", "string") .update("string", "string") .groupBy("string") .select("string") .gteq("string", "string") .isNull("string") .lt("string", "string") .pageSize(100) .like("string", "string") .gt("string", "string") .getClass() .isNotNull("string") .setVar("string", "string") .conjuctionOr() .sql("string") .update("string") .bind("string") .notifyAll() .rowid("string", "string", "string") .notifyAll() .or() .ne("string", "string") .update("string") .toString() .notNull("string") .table("string") .page(100) .doPageQuery(100, 100, "string"); }
程序自动生成测试的脚本,你可以指定level控制深度,上面这个demo在语法上是没问题的,已经达到想要的效果,下一步就是控制好参数,让函数里填进正确的参数。
no comment untill now