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

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

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

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

单元测试的局限性

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

1.添加一个测试用例

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

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

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

public int Add(int a, int b)
{
if(a==3 && b ==1)
{
return 4;
} return 0;
}

4.继续编写测试

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

5.修改代码让测试通过

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

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

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

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

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

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

[Fact]
public void WhenAddTwoNumberShouldGetSum()
{
for (int i = 0; i < 100; i++)
{
var a = GetRandomNumber();
var b = GetRandomNumber(); var result = Add(a, b); result.Should().Be(a + b);
}
}

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

public int Add(int a, int b)
{
return a + b;
}

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

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

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

Property-based测试

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

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

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

[Fact]
public void A_Add_B_Should_EqualTo_B_Add_A()
{
for (int i = 0; i < 100; i++)
{
var a = GetRandomNumber();
var b = GetRandomNumber(); var result1 = Add(a, b);
var result2 = Add(b, a); result1.Should().Be(result2);
} }

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

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

[Fact]
public void A_Add_B_Add_C_Should_EqualTo_B_Add_C_Add_A()
{
for (int i = 0; i < 100; i++)
{
var a = GetRandomNumber();
var b = GetRandomNumber();
var c = GetRandomNumber(); var result1 = Add(Add(a, b), c);
var result2 = Add(a, Add(b, c)); result1.Should().Be(result2);
}
}

如何实践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. 1.3 正则表达式和python语言-1.3.8 创建字符集([ ])

    1.3.8 创建字符集([ ]) (2018-05-0815:24:00) 下面的示例将说明对于 r2d2|c3po 的限制将比[cr][23][dp][o2]更为严格 import re # 下面的 ...

  2. CentOS下SVN服务的启动与关闭

    CentOS下SVN服务的启动与关闭 操作系统:CentOS 6.5  SVN版本:1.8.11 启动SVN服务:  svnserve -d -r /home/svn /home/svn 为版本库的根 ...

  3. vscode设置中文语言

    https://jingyan.baidu.com/article/7e44095377c9d12fc1e2ef5b.html

  4. Windows环境下,本地Oracle创建dblink连接远程mysql

    前言 我的情况是,本地安装了oracle(安装完成后带有SQL Developer,不需要再安装instantclient),创建dblink去连接远程的mysql.有些朋友可能是 本地使用PL\SQ ...

  5. python学习相关知识点

    1.ndarray中,添加行或列:https://blog.csdn.net/lishuandao/article/details/52444288 2.numpy中ndarray数组拼接方法介绍:h ...

  6. 微信小程序开发---视图层(View)

    WXML WXML能力: 数据绑定 列表渲染 条件渲染 模板 事件 数据绑定 数据绑定使用 Mustache 语法(双大括号)将变量包起来,可作用于内容,组件属性(需要在双引号之内),控制属性(需要在 ...

  7. C# yield return 和 yield break

    yield关键字用于遍历循环中,yield return用于返回IEnumerable<T>,yield break用于终止循环遍历. 以下对比了使用yield return与不使用yie ...

  8. 对matplotlib库的运用

    1.matplotlib库的运用效果图 绘制基本的三角函数                                                                        ...

  9. java 加解密

    import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingExc ...

  10. 解决 Visual Studio 点击添加引用无反应的问题

    如遇到vs2010 点击添加引用无反应,主要是因为windows系统推送的更新问题,把windows系统推送的更新都更新一遍就好了. 如果已经安装VS后 Windows系统推荐的更新.360推荐的安全 ...