编写基于Property-based的单元测试

作为一个开发者,你可能认为你的职责就是编写代码从而完成需求。我不敢苟同,开发者的工作是通过软件来解决现实需求,编写代码只是软件开发的其中一个方面,编写可靠的软件和产出有价值的代码更加重要。而TDD则是前辈通过经验总结出的一套切实可行的软件开发实践,TDD旨在帮助开发者编写高质量的代码。

TDD的过程可以总结为以下几个步骤:

  1. 先添加一个测试用例
  2. 执行测试,查看这个测试的失败结果
  3. 对代码做少量修改
  4. 再次执行测试,查看测试结果
  5. 对代码进行重构,执行测试

单元测试的局限性

设想你要编写一个加法功能,接受两个数字,返回这两个数字的和。让我们来按照TDD的流程走一遍:

1.添加一个测试用例

  1. [Fact]
  2. public void Given3And1ShouldReturn4()
  3. {
  4. var result = Add(3, 1);
  5. result.Should().Be(4);
  6. }

2.执行代码,发现测试并不能通过,因为我们还没有实现add方法

3.对代码做少量修改,让测试通过

  1. public int Add(int a, int b)
  2. {
  3. if(a==3 && b ==1)
  4. {
  5. return 4;
  6. }
  7. return 0;
  8. }

4.继续编写测试

  1. [Fact]
  2. public void Given1And2ShouldReturn3()
  3. {
  4. var result = Add(1, 2);
  5. result.Should().Be(3);
  6. }

5.修改代码让测试通过

  1. public int Add(int a, int b)
  2. {
  3. if(a==3 && b ==1)
  4. {
  5. return 4;
  6. }
  7. if (a == 1 && b == 2)
  8. {
  9. return 3;
  10. }
  11. return 0;
  12. }

至此为止,你一直在遵守TDD的步骤,测试全部变成了绿色,但是你始终没有得到正确的Add实现。

哪里出了问题?你也许会觉得,咱们实现的Add方法有问题,我们故意犯了一些显而易见的错误从而给TDD挑毛病。但是我任然可以反驳,他之所以看起来是显而易见的错误是因为对两个数字求和这样的需求是每个人都明白的道理,所以你才觉得显而易见,试想这是一个正式的场景,你也许真的就编写了这样的代码从而让两个测试用例都能恰好通过。

如果说我们并不是故意编写了这样的代码,那么单元测试和TDD这种实践本身可能就有一些瑕疵。

换个角度来说,我们之所以没有编写出完整的业务逻辑,是因为单元测试是用例驱动的,而有限的测试用例漏掉了很多可能性。

如果我们对a和b分别取100个随机值,Add方法都能够通过,那么我们几乎很难编写出上面的Add实现。

  1. [Fact]
  2. public void WhenAddTwoNumberShouldGetSum()
  3. {
  4. for (int i = 0; i < 100; i++)
  5. {
  6. var a = GetRandomNumber();
  7. var b = GetRandomNumber();
  8. var result = Add(a, b);
  9. result.Should().Be(a + b);
  10. }
  11. }

要想保证这样的测试通过,你只能编写出正确的Add实现:

  1. public int Add(int a, int b)
  2. {
  3. return a + b;
  4. }

这个测试看起来不错,通过产生大量随机的输入来驱动代码实现,但是这个代码存在一个致命的问题,测试代码和被测试代码使用了相同的业务逻辑。

  1. //我们期望的数字是a + b
  2. result.Should().Be(a + b);
  3. //而被测对象也是a + b
  4. public int Add(int a, int b)
  5. {
  6. return a + b;
  7. }

如果a + b这个逻辑本身就有问题,但是因为你在测试代码里重复了这一有问题的逻辑,实际上你的测试并没有发现任何问题。

Property-based测试

如果你不在测试代码里重复a + b这个逻辑,你如何通过这100个随机输入来断言测试的准确性?什么样的断言能被用在这100个随机输入的测试用例中?

答案是断言Add这一能力的属性,某种能够适用于所有测试用例的属性。

举个例子:a + b = b + a

  1. [Fact]
  2. public void A_Add_B_Should_EqualTo_B_Add_A()
  3. {
  4. for (int i = 0; i < 100; i++)
  5. {
  6. var a = GetRandomNumber();
  7. var b = GetRandomNumber();
  8. var result1 = Add(a, b);
  9. var result2 = Add(b, a);
  10. result1.Should().Be(result2);
  11. }
  12. }

这一特性正好是加法交换律,如果只是测试交换律还是不能够保证Add方法的准确性,因为你可以把Add方法实现为a * b。

我们还可以断言起结合律,即a + b + c = a + (b + c)

  1. [Fact]
  2. public void A_Add_B_Add_C_Should_EqualTo_B_Add_C_Add_A()
  3. {
  4. for (int i = 0; i < 100; i++)
  5. {
  6. var a = GetRandomNumber();
  7. var b = GetRandomNumber();
  8. var c = GetRandomNumber();
  9. var result1 = Add(Add(a, b), c);
  10. var result2 = Add(a, Add(b, c));
  11. result1.Should().Be(result2);
  12. }
  13. }

如何实践Property-based测试

所以什么是Property-based测试?从上面的分析能够看出Property-based测试实际上提出了两个策略来保证测试的有效性:

  1. 随机产生输入值,保证足够多的测试用例
  2. 找出并断言功能具有的普遍适应性的属性

在.NET领域,FsCheck用来进行Property-based测试,Property-based是从Haskell移植过来的,几乎所有的主流语言都有其移植版本

下篇我们将介绍如何通过FsCheck来做Property-based测试。

编写基于Property-based的单元测试的更多相关文章

  1. 如何:使用 Visual Basic 编写基于 Unity3D 的计算器

    随着 .NET 全平台战略的推进,微软正在让以 C# 为先锋的 .NET 拥有跨平台特性.这个过程中一直有人想知道其它 .NET 语言对跨平台的支持有什么改进,熟悉 C# 但是喜欢用 VB 的我也不例 ...

  2. 利用反射编写私有 Private 方法的单元测试

    利用反射编写私有 Private 方法的单元测试 最近在添加一个新feature时,鉴于要给自己的代码一是增加代码的强壮性,二是增加代码测试的覆盖率.但是遇到了有些方法是 Private 的,但是在调 ...

  3. 使用 xUnit 编写 ASP.NET Core WebAPI单元测试

    本文使用xUnit对ASP.NET Core WebAPI做单元测试,使用HttpClient的同步和异步请求,下面详细介绍xUnit的使用过程: 一.创建示例项目 模板为我们自动创建了一个Value ...

  4. 转:从头开始编写基于隐含马尔可夫模型HMM的中文分词器

    http://blog.csdn.net/guixunlong/article/details/8925990 从头开始编写基于隐含马尔可夫模型HMM的中文分词器之一 - 资源篇 首先感谢52nlp的 ...

  5. unit vs2017基于nunit framework创建单元测试

    unit  vs2017基于nunit framework创建单元测试 一.简叙: 单元测试大型项目中是必备的,所以不可忽视,一个项目的成败就看是否有单元测试,对后期的扩展维护都带来了便利. 二.安装 ...

  6. 【Python】[技术博客] 如何对使用PYQT编写的GUI文件进行单元测试

    如何对使用PYQT编写的GUI文件进行单元测试 想要对PYQT编写的GUI文件进行单元测试,我们主要用到QTest QTest里面包含了一些对窗体的各种控件进行模拟操作的函数,通过QTest对窗体进行 ...

  7. 编写基于TCP的应用程序

    编写基于TCP的应用程序   这似乎是一个非常简单的话题, 就跟"是个人就能做网站"一样, 你可能也认为"是个人就能写使用TCP socket的网络程序". 不 ...

  8. [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent c ...

  9. [转载] 使用C/C++语言编写基于DSP程序的注意事项

    原文地址:『转』使用C/C++语言编写基于DSP程序的注意事项作者:skysmile   1.不影响执行速度的情况下,可以使用c或c/c++语言提供的函数库,也可以自己设计函数,这样更易于使用“裁缝师 ...

随机推荐

  1. BZOJ.4500.矩阵(差分约束 SPFA判负环 / 带权并查集)

    BZOJ 差分约束: 我是谁,差分约束是啥,这是哪 太真实了= = 插个广告:这里有差分约束详解. 记\(r_i\)为第\(i\)行整体加了多少的权值,\(c_i\)为第\(i\)列整体加了多少权值, ...

  2. Dockerfile中COPY命令的简单性

    dockerfile中的COPY命令是不会拷贝目录结构的,它只会单纯把包含的所有文件拷贝到另一个目录中去. 相关链接:https://www.cnblogs.com/sparkdev/p/957324 ...

  3. 算法第四版中 while (!StdIn.isEmpty()) 循环无法跳出问题

    在IDEA中使用Ctrl+D就可以退出console输入

  4. SSM框架mapper.xml模糊查询语句

    SSM框架mapper.xml模糊查询语句 在用SSM框架时,如果想要实现模糊查询,可以在mapper.xml文件中进行数据库语句的书写,方法有很多种,在这里我选择了两种介绍: 方法1: <se ...

  5. Redis安装及使用

    1.我们可以通过在官网下载tar.gz的安装包,或者通过wget的方式下载 进入要下载到的文件夹: wget http://download.redis.io/releases/redis-4.0.1 ...

  6. java枚举使用 总结

    补充几点: 1.枚举对象是可以用 == 比较. 2. TestEnum3反编译结果: F:\tree\Test\src\test>javap TestEnum3* Compiled from & ...

  7. js-day02-BOM和DOM

    BOM和Document对象常见属性和方法: BOM是browser object model的缩写,简称浏览器对象模型. Document 对象每个载入浏览器的 HTML 文档都会成为 Docume ...

  8. ASP.NET 4.0验证请求 System.Web.HttpRequestValidationException: A potentially dangerous Request.F

    System.Web.HttpRequestValidationException: A potentially dangerous Request.F 在使用类似eWebedtior 拷贝内容进去的 ...

  9. Java编程题: 写一个Singleton出来

    Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在. 一般Singleton模式通常有几种种形式: 第一种形式:  定义一个类,它的构造函数为private的, ...

  10. SUSE12Sp3安装配置.net core 生产环境(1)-IP,DNS,网关,SSH,GIT

    1.新增用户 sudo useradd 用户名 sudo passwd 用户名 这个时候会提示你输入密码,输入两次密码即可 2.静态 IP 设置 1.设置 IP 地址 sudo vi /etc/sys ...