做为一个开发者,你可能认为你的职责就是编写代码从而完成需求。我不敢苟同,开发者的工做是经过软件来解决现实需求,编写代码只是软件开发的其中一个方面,编写可靠的软件和产出有价值的代码更加剧要。而TDD则是前辈经过经验总结出的一套切实可行的软件开发实践,TDD旨在帮助开发者编写高质量的代码。
TDD的过程能够总结为如下几个步骤:git
设想你要编写一个加法
功能,接受两个数字,返回这两个数字的和。让咱们来按照TDD的流程走一遍:
1.添加一个测试用例github
[Fact] public void Given3And1ShouldReturn4() { var result = Add(3, 1); result.Should().Be(4); }
2.执行代码,发现测试并不能经过,由于咱们尚未实现add方法
3.对代码作少许修改,让测试经过dom
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实现。ui
哪里出了问题?你也许会以为,我们实现的Add方法有问题,咱们故意犯了一些显而易见的错误从而给TDD挑毛病。可是我任然能够反驳,他之因此看起来是显而易见的错误是由于对两个数字求和这样的需求是每一个人都明白的道理,因此你才以为显而易见,试想这是一个正式的场景,你也许真的就编写了这样的代码从而让两个测试用例都能刚好经过。code
若是说咱们并非故意编写了这样的代码,那么单元测试和TDD这种实践自己可能就有一些瑕疵。对象
换个角度来讲,咱们之因此没有编写出完整的业务逻辑,是由于单元测试是用例驱动的,而有限的测试用例漏掉了不少可能性。
若是咱们对a和b分别取100个随机值,Add方法都可以经过,那么咱们几乎很难编写出上面的Add实现。ip
[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这个逻辑自己就有问题,可是由于你在测试代码里重复了这一有问题的逻辑,实际上你的测试并无发现任何问题。
若是你不在测试代码里重复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测试实际上提出了两个策略来保证测试的有效性:
在.NET领域,FsCheck用来进行Property-based测试,Property-based是从Haskell移植过来的,几乎全部的主流语言都有其移植版本。 下篇咱们将介绍如何经过FsCheck来作Property-based测试。