单元测试准则

发表于:2014-03-03来源:豆瓣作者:@行知-追寻技术之美点击数: 标签:单元测试
单元测试准则摘要:单元测试曾一直都是阿里的痛,在进入阿里的这段日子里,单元测试逐步成熟起来,也开始带来了一些收益,这些方面都是很不错的一些思路。后面我打算仔细的研究和整理这一过程,希望能提供一个比较可行的方案供大家参考。

  摘要:单元测试曾一直都是阿里的痛,在进入阿里的这段日子里,单元测试逐步成熟起来,也开始带来了一些收益,这些方面都是很不错的一些思路。后面我打算仔细的研究和整理这一过程,希望能提供一个比较可行的方案供大家参考。

  网上一直流传的一份挺不错的单元测试准则,先保存下来。

  1. 保持单元测试小巧, 快速

  理论上, 任何代码 Check-in 之前都应该把所有测试套件完整的跑一遍. 所以保持测试代码轻快能减少开发迭代周期.

  2. 单元测试应该是全自动/非交互式的

  测试套件通常是定期执行的, 执行过程也必须是完全自动化才有意义. 输出结果需要人工检查的测试不是一个好的单元测试.

  3. 让单元测试很容易跑起来

  对开发环境进行配置, 最好是敲一条命令或是点击一个按钮就能把单个测试用例和测试套件跑起来.

  4. 对测试进行评估

  对执行的测试进行覆盖率分析, 以便得到精确的代码执行覆盖率, 调查哪些代码未被执行.

  5. 立即修正失败的测试

  每个开发人员都应该保证新 Check-in 的测试用例能够跑成功, 并且当有代码 Check-in 现有测试用例也都能跑通过.

  6. 把测试维持在单元级别

  单元测试即类 (Class) 的测试. 一个 “测试类” 应该只对应于一个 “被测类”, 并且对 “被测类” 行为的测试环境应该是隔离的. 必须谨慎的避免使用单元测试框架来测试整个程序的工作流, 这样的测试即低效又难维护. 工作流测试 (译注: 指跨模块/类的数据流测试) 有它自己的地盘, 但它绝不是单元测试, 必须单独设置和执行.

  7. 由简入繁

  再简单的测试也远远胜过完全没有测试. 一个简单的 “测试类” 会促使建立 “被测类” 基本的测试骨架, 可以对构建环境, 单元测试环境, 执行环境以及覆盖率分析工具等有效性进行检查, 同时也确保 “被测类” 能够整合并被调用.

  下面便是单元测试版的 Hello, world! :

  void testDefaultConstruction()

  {

  Foo foo = new Foo();

  assertNotNull(foo);

  }

  8. 保持测试的独立性

  为了保证测试稳定可靠且便于维护, 测试用例之间决不能有相互依赖, 也不能依赖执行的先后次序.

  9. Keep tests close to the class being tested

  [译注: 我未翻译该规则, 个人认为本条规则值得商榷, 大部分 C++ 和 Python 库均把测试代码从功能代码目录中独立出来, 通常是创建一个和 src 目录同级的 tests 目录, 被测模块/类名之前也常常 不加 Test 前缀. 这么做保证功能代码和测试代码隔离, 目录结构清晰, 并且发布源码的时候更容易排除测试用例.]

  If the class to test is Foo the test class should be called FooTest (not TestFoo) and kept in the same package (directory) as Foo. Keeping test classes in separate directory trees makes them harder to access and maintain.

  Make sure the build environment is configured so that the test classes doesn’t make its way into production libraries or executables.

  10. 合理的命名测试用例

  确保每个测试方法只测试 “被测类” 的一个明确特性, 并且相应的给测试方法命名. 典型的命名俗定是 test[what], 比如 testSaveAs(),testAddListener(), testDeleteProperty() 等.

  11. 只测试公有接口

  单元测试可以被定义为 通过类的公有 API 对类进行进行测试. 一些测试工具允许测试一个类的私有成员, 但这种做法应该避免, 它让测试变得繁琐而且更难维护. 如果有私有成员确实需要进行直接测试, 可以考虑把它重构到工具类的公有方法中. 但要注意这么做是为了改善设计, 而不是帮助测试.

  12. 看成是黑盒

  从在第三方使用者的角度, 测试类是否满足规定的需求. 并设法让它出问题 (译注: 原文 tear it apart, 本意 “将它撕碎”, 我的理解是崩溃, 出问题, 不能正确工作).

  13. 看成是白盒

  毕竟被测试类是程序员自写自测的, 应该在最复杂的逻辑部分多花些精力测试.

  14. 芝麻函数也要测试

  通常建议所有重要的函数都应该被测试到, 一些芝麻方法, 如简单的 setter 和 getter 都可以忽略. 但是仍然有充分的理由支持测试芝麻函数:

  芝麻 很难定义. 对于不同的人有不同的理解.

  从黑盒测试的观点看, 是无法知道哪些代码是普通的.

  即便是再芝麻的函数, 也可能包含错误, 通常是 “复制粘贴” 代码的后果:

  private double weight_;

  private double x_, y_;

  public void setWeight(int weight)

  {

  weight = weight_; // error

  }

  public double getX()

  {

  return x_;

  }

  public double getY()

  {

  return x_; // error

  }

  因此建议测试所有方法. 毕竟芝麻函数也容易测试.

  15. 先关注执行覆盖率

  区别对待 执行覆盖率 和 实际测试覆盖率. 测试的最初目标应该是确保较高的执行覆盖率. 这样能保证代码在 某些 参数输入时能有效执行. 一旦执行覆盖率就绪, 就应该开始改进测试覆盖率了. 注意, 实际的测试覆盖率很难衡量 (而且往往趋近于 0%).

  思考以下公有方法:

  void setLength(double length);

  调用 setLength(1.0) 你可能会得到 100% 的执行覆盖率. 要达到 100% 的实际测试覆盖率, 有多少个 double 浮点数这个方法就必须被调用多少次, 并且要一一验证行为的正确性. 这无疑是不可能的任务.

  16. 覆盖边界值

  确保参数边界值均被覆盖. 对于数字, 测试负数, 0, 正数, 最小值, 最大值, NaN (非数字), 无穷大等. 对于字符串, 测试空字符串, 单字符, 非 ASCII 字符串, 多字节字符串等. 对于集合类型, 测试空, 1, 第一个, 最后一个等. 对于日期, 测试 1月1号, 2月29号, 12月31号等. 被测试的类本身也会暗示一些特定情况下的边界值. 基本要点是尽可能彻底的测试这些边界值, 因为它们都是主要 “疑犯”.

原文转自:http://www.wangyuxiong.com/archives/51625