提交 f2467a6f 编写于 作者: W wizardforcel

2022-01-08 19:02:35

上级 2b5edfbe
# 简介
# 零、简介
这本书是关于 FakeItEasy 的,一个由[Patrik hagne](https://github.com/patrik-hagne)编写的开源嘲讽框架。FakeItEasy 用于在单元测试时嘲讽依赖关系。
也许你用过其他的嘲讽框架,你在想是什么让 FakeItEasy 与众不同。或者,您以前从未使用过嘲讽框架,但您一直在编写“存根”类,这些类为您的单元测试实现接口,您有兴趣了解嘲讽框架如何提供帮助。
也许你用过其他的嘲讽框架,你在想是什么让 FakeItEasy 与众不同。或者,您以前从未使用过嘲讽框架,但您一直在编写“”类,这些类为您的单元测试实现接口,您有兴趣了解嘲讽框架如何提供帮助。
FakeItEasy 区别于其他嘲讽框架的地方在于它更容易使用。事实上,有些人甚至不用“模拟”这个词来描述框架——他们说这是一个“伪造”的框架。流畅的界面与易于理解的应用编程接口相结合,使其成为单元测试中使用的最快的模拟框架。
......@@ -12,9 +12,9 @@ FakeItEasy 区别于其他嘲讽框架的地方在于它更容易使用。事实
我说的浪费代码是什么意思?
你是否曾经为了测试的明确目的而必须对一个带有依赖关系的类进行单元测试,并且必须编写一个实现该接口的类来“排除”行为?如果您已经这样做了,您就知道这些“被剔除”的类代表了大量的“废弃”代码。它们还代表了重构存根类正在实现的接口时的摩擦,因为除了更改实现接口的真实类的实现之外,现在还有一堆“存根”类也需要更新。
你是否曾经为了测试的明确目的而必须对一个带有依赖关系的类进行单元测试,并且必须编写一个实现该接口的类来“排除”行为?如果您已经这样做了,您就知道这些“被剔除”的类代表了大量的“废弃”代码。它们还代表了重构桩类正在实现的接口时的摩擦,因为除了更改实现接口的真实类的实现之外,现在还有一堆“桩”类也需要更新。
如果你对上一段中的任何一项回答“是”,那么 FakeItEasy 就是你的嘲讽框架。您将学会在单元测试中使用 FakeItEasy,而不需要编写那些浪费时间、扔掉的存根类。
如果你对上一段中的任何一项回答“是”,那么 FakeItEasy 就是你的嘲讽框架。您将学会在单元测试中使用 FakeItEasy,而不需要编写那些浪费时间、扔掉的类。
对于那些看完这本书有兴趣从 Moq(另一个流行的开源嘲讽框架)迁移到 fakeitesy 的人,我强烈推荐 Daniel Marbach 的[博文](http://www.planetgeek.ch/2013/07/18/migration-from-moq-to-fakeiteasy-with-resharper-search-patterns/),里面谈到了用 ReSharper Search Patterns 从 MOQ 迁移到 fakeitesy。
......
# 第 1 章安装 FakeItEasy
# 一、安装 FakeItEasy
## 简介
......
# 第 2 章单元测试、IoC 和存根
# 二、单元测试、IoC 和桩
## 无故障单元测试
本章将引导我们尝试测试一个不可测试的类,重构以允许测试该类,为测试创建一个“存根”类,最后,使用 FakeItEasy 代替存根类。
本章将引导我们尝试测试一个不可测试的类,重构以允许测试该类,为测试创建一个“桩”类,最后,使用 FakeItEasy 代替桩类。
### 设置舞台
......@@ -141,11 +141,11 @@
你可以看到我在哪里插入了“???"进入`CustomerService`的构造器。我需要通过一个`ICustomerRepository`的实现,由于我不能使用真正的`CustomerRepository`,我不得不写一个实现接口的类。
我得写一些叫做存根的东西。
我得写一些叫做的东西。
### 拯救树桩(某种)
编写存根类足够简单;我需要写一个实现`ICustomerRepository`的新类:
编写类足够简单;我需要写一个实现`ICustomerRepository`的新类:
```cs
public class CustomerRepositoryStub : ICustomerRepository
......@@ -158,9 +158,9 @@
```
代码清单 9:为我们的单元测试实现 ICustomerRepository 的存根
代码清单 9:为我们的单元测试实现 ICustomerRepository 的
既然存根已经写好了,我们可以在单元测试中使用它:
既然已经写好了,我们可以在单元测试中使用它:
```cs
[TestFixture]
......@@ -185,7 +185,7 @@
```
代码清单 10:向客户服务的构造函数提供一个新的客户位置存根
代码清单 10:向客户服务的构造函数提供一个新的客户位置
注意我如何创建一个新的`CustomerRepositoryStub`类,并将其传递给`CustomerService.`的构造函数
......@@ -201,21 +201,21 @@
让我们在这一点上后退一步,再看看`CustomerRepositoryStub`课。这是我为一个非常简单的接口编写的一个类,它只在一个单元测试中使用。
如果我期待一个不同的`Id``FirstName``LastName`对同一个 SUT 进行不同的测试呢?我必须写更多的存根类。
如果我期待一个不同的`Id``FirstName``LastName`对同一个 SUT 进行不同的测试呢?我必须写更多的类。
如果我必须实现一个有 20 种方法的接口,并且这些方法需要根据 SUT 的不同设置而改变,会怎么样?我的存根类变大了。
如果我必须实现一个有 20 种方法的接口,并且这些方法需要根据 SUT 的不同设置而改变,会怎么样?我的类变大了。
如果我必须测试由`Customer`上返回的数据所指示的添加到`CustomerService`类的条件语句驱动的多个场景,会怎么样?我必须写更多的存根类。
如果我必须测试由`Customer`上返回的数据所指示的添加到`CustomerService`类的条件语句驱动的多个场景,会怎么样?我必须写更多的类。
在我目前工作的项目中,我们的系统中有将近 14,000 个单元测试。如果我为大部分测试编写存根类,如果不是所有测试的话,我们谈论的是至少 14,000 个额外的类**仅仅是为了单元测试而编写的。**
在我目前工作的项目中,我们的系统中有将近 14,000 个单元测试。如果我为大部分测试编写类,如果不是所有测试的话,我们谈论的是至少 14,000 个额外的类**仅仅是为了单元测试而编写的。**
现在变化来了。界面定义发生变化。你不仅要改变真正的类实现接口(在我们的例子中,`CustomerRepository`),还要改变你为单元测试写的所有存根类!
现在变化来了。界面定义发生变化。你不仅要改变真正的类实现接口(在我们的例子中,`CustomerRepository`),还要改变你为单元测试写的所有类!
还有更好的办法。
## 用 FakeItEasy 进行单元测试
让我们重温一下`CustomerServiceTests`类,但是这次,我们将使用 FakeItEasy,而不是为了单元测试而编写一个存根类来实现一个接口。
让我们重温一下`CustomerServiceTests`类,但是这次,我们将使用 FakeItEasy,而不是为了单元测试而编写一个类来实现一个接口。
下面是使用 FakeItEasy 的单元测试:
......@@ -263,7 +263,7 @@
代码清单 12:创建一个赝品
这就是你如何在 FakeItEasy 中创建一个“赝品”。与其他嘲笑框架不同,在其他框架中,您必须区分创建“存根”、“间谍”或“嘲笑”,FakeItEasy 将一切都视为赝品。在这种情况下,创建我们的假货是非常简单的;我们要求 FakeItEasy 为我们制造一个`ICustomerRepository`的假货
这就是你如何在 FakeItEasy 中创建一个“赝品”。与其他嘲笑框架不同,在其他框架中,您必须区分创建“桩”、“间谍”或“嘲笑”,FakeItEasy 将一切都视为赝品。在这种情况下,创建我们的赝品是非常简单的;我们要求 FakeItEasy 为我们创建一个`ICustomerRepository`的赝品
现在我们已经创建了我们的伪,我们在伪上配置调用,允许我们在我们的 SUT 中测试某个场景。我们只是断言,我们希望返回一个具有匹配标识的客户,作为我们传递给`GetCustomerBy`方法的标识:
......@@ -274,15 +274,15 @@
代码清单 13:配置对 false 的调用并指定返回值
`A.CallTo`允许我们配置假货,这样我们就可以开始告诉它该怎么做。我们用假的方法调用`GetCustomerBy`方法,通过我们在单元测试中设置的`customerId`,返回一个特定的客户(也是在我们的单元测试中设置的)。`A.CallTo`允许我们设置我们的期望,即当进行这个调用时,在这个单元测试中应该发生什么。
`A.CallTo`允许我们配置赝品,这样我们就可以开始告诉它该怎么做。我们用假的方法调用`GetCustomerBy`方法,通过我们在单元测试中设置的`customerId`,返回一个特定的客户(也是在我们的单元测试中设置的)。`A.CallTo`允许我们设置我们的期望,即当进行这个调用时,在这个单元测试中应该发生什么。
如果我们试图将不同的`customerId`传递给`GetCustomerBy`方法,当我们在测试方法中断言匹配的客户标识时,我们的单元测试将失败。
最后,我们用`Returns`指定假货的行为。使用`Returns`允许我们控制给定配置下我们配置的假货会返回什么。在这种情况下,我们指定在我们的单元测试中设置的客户应该由配置的调用返回。如果我们在 FakeItEasy 调用的`Returns`部分返回一个不同的客户,我们的单元测试将会失败。
最后,我们用`Returns`指定赝品的行为。使用`Returns`允许我们控制给定配置下我们配置的赝品会返回什么。在这种情况下,我们指定在我们的单元测试中设置的客户应该由配置的调用返回。如果我们在 FakeItEasy 调用的`Returns`部分返回一个不同的客户,我们的单元测试将会失败。
如您所见,FakeItEasy 不仅允许我们断言调用`GetCustomerBy`需要特定的`customerId`,还允许我们断言返回了特定的客户。
最后,我们把假货传给`CustomerService`的构造者:
最后,我们把赝品传给`CustomerService`的构造者:
```cs
var sut = new CustomerService(aFakeCustomerRepository);
......@@ -295,8 +295,8 @@
剩下的单元测试和以前一样,我们的测试通过了。
通过在我们的单元测试中添加几行代码,我们引入了 FakeItEasy,设置了对 fake 的方法、参数和返回值的调用预期,并且消除了编写存根类的需要。
通过在我们的单元测试中添加几行代码,我们引入了 FakeItEasy,设置了对 fake 的方法、参数和返回值的调用预期,并且消除了编写类的需要。
## 总结
在这一章中,我们从不可测试的代码开始,在引入依赖注入以允许我们的类进行单元测试后,重构为使用存根,并以对 FakeItEasy 的最终重构结束,该重构消除了编写单元测试存根类的需要。接下来,我们将看看管理如何使用 FakeItEasy 的规则。
\ No newline at end of file
在这一章中,我们从不可测试的代码开始,在引入依赖注入以允许我们的类进行单元测试后,重构为使用桩,并以对 FakeItEasy 的最终重构结束,该重构消除了编写单元测试桩类的需要。接下来,我们将看看管理如何使用 FakeItEasy 的规则。
\ No newline at end of file
# 第三章介绍 FakeItEasy
# 三、FakeItEasy 简介
在开始编写单元测试和使用 FakeItEasy 之前,让我们先对框架有一个扎实的了解。本章将重点介绍什么是可以伪造的,如何创建不同的伪造,如何向伪造馈送构造函数参数,哪些成员可以被 FakeItEasy 覆盖,以及创建伪造时的默认行为“开箱即用”。
......@@ -70,7 +70,7 @@
### with argumentersfor 构造函数
以下是如何使用`WithArgumentsForConstructor` `:`将参数传递给你的假货
以下是如何使用`WithArgumentsForConstructor` `:`将参数传递给你的赝品
```cs
public class AClassWithArguments
......@@ -126,7 +126,7 @@
## 默认假冒行为
这是我们在制造假货时“开箱即用”的行为。在本书的后面,我们将从第 5 章“配置对赝品的调用”开始,了解更多关于赝品的行为现在,下面两个部分将概述当你简单地创建一个新的赝品时你得到的行为。
这是我们在创建赝品时“开箱即用”的行为。在本书的后面,我们将从第 5 章“配置对赝品的调用”开始,了解更多关于赝品的行为现在,下面两个部分将概述当你简单地创建一个新的赝品时你得到的行为。
### 不可重写成员不能伪造
......@@ -237,4 +237,4 @@
## 总结
在这一章中,我们已经讨论了管理 FakeItEasy 如何工作的更高层次的“规则”,并查看了说明如何使用它以及如何不使用它的示例代码。接下来,我们将通过学习如何创建假货来开始学习使用 FakeItEasy。
\ No newline at end of file
在这一章中,我们已经讨论了管理 FakeItEasy 如何工作的更高层次的“规则”,并查看了说明如何使用它以及如何不使用它的示例代码。接下来,我们将通过学习如何创建赝品来开始学习使用 FakeItEasy。
\ No newline at end of file
# 第四章制造假货
# 四、创建赝品
这将是本书到目前为止最短的一章,主要是因为 FakeItEasy 让我们非常容易制造假货
这将是本书到目前为止最短的一章,主要是因为 FakeItEasy 让我们非常容易创建赝品
## 如何制造假货
## 如何创建赝品
制造赝品是非常简单的(我们已经在本书的前几节看到了这一点)。使用`A.Fake<T>`来创建你的假货。你可以创建一个假的接口或类。
创建赝品是非常简单的(我们已经在本书的前几节看到了这一点)。使用`A.Fake<T>`来创建你的赝品。你可以创建一个假的接口或类。
伪造界面:
......@@ -51,4 +51,4 @@
代码清单 22:创建一个伪类
遵循第 3 章中概述的规则,制造赝品非常简单。但是现在我们已经造出了一个赝品,我们到底能用它做什么呢?
\ No newline at end of file
遵循第 3 章中概述的规则,创建赝品非常简单。但是现在我们已经造出了一个赝品,我们到底能用它做什么呢?
\ No newline at end of file
# 第 5 章将呼叫配置为假呼叫
# 五、将调用配置为伪调用
## A .叫声
......@@ -13,7 +13,7 @@
代码清单 23:调用方法
代码清单 23 中的`theFake`是我们配置的一个伪代码。从那里我们应该能够指定哪个成员在假货上打电话(`aFakesMember`)。如果你看一下当你输入`A.CallTo`时呈现给你的智能感知,你会看到许多重载;让我们来探索这些过载。
代码清单 23 中的`theFake`是我们配置的一个伪代码。从那里我们应该能够指定哪个成员在赝品上打电话(`aFakesMember`)。如果你看一下当你输入`A.CallTo`时呈现给你的智能感知,你会看到许多重载;让我们来探索这些过载。
### 探索过载
......@@ -83,7 +83,7 @@
请注意,这两种方法的配置是相同的,但是如果您在每个配置的方法的`CallTo`定义中按 F12,您会看到不同。
当我们建立到`SendMail`呼叫时,您提供到`A.CallTo``Expression<Action>`,当您建立到`GetEmailServerAddress`的呼叫时,您提供到`A.CallTo``Expression<Func<T>>`
当我们建立到`SendMail`调用时,您提供到`A.CallTo``Expression<Action>`,当您建立到`GetEmailServerAddress`的调用时,您提供到`A.CallTo``Expression<Func<T>>`
| ![](img/note.png) | 注意:这是因为 SendMail 不返回值,而 GetEmailServerAddress 返回值,这是。NET 框架。更多关于动作<t>和功能<t>的区别可以在[这里](http://stackoverflow.com/questions/4317479/func-vs-action-vs-predicate)找到。</t></t> |
......@@ -143,10 +143,10 @@ FakeItEasy 是怎么做到的?如果你再看一下`CallTo`,你会看到三
| ![](img/tip.png) | 提示:学习表达式树的一个很好的入门资源是斯科特·艾伦关于 LINQ 基础的 [PluralSite 教程](http://www.pluralsight.com/courses/linq-fundamentals)。 |
谢天谢地,FakeItEasy 完成了所有繁重的工作,并编写了负责解析表达式的代码,为我们消费者提供了一个流畅的应用编程接口。我们需要做的就是指定假货,然后使用 FakeItEasy 的指导,对其进行配置。
谢天谢地,FakeItEasy 完成了所有繁重的工作,并编写了负责解析表达式的代码,为我们消费者提供了一个流畅的应用编程接口。我们需要做的就是指定赝品,然后使用 FakeItEasy 的指导,对其进行配置。
## 总结
在这一节中,我们学习了如何配置假货,探索了`A.CallTo`的重载,并展示了假货上不同类型成员的一些配置。
在这一节中,我们学习了如何配置赝品,探索了`A.CallTo`的重载,并展示了赝品上不同类型成员的一些配置。
为了让假货有用,我们想要控制一些东西。我们现在知道如何配置赝品,但我们还不能对配置好的赝品做太多——这就是 FakeItEasy 的真正力量所在。我们将在下一章“指定假货的行为”中学习如何做到这一点
\ No newline at end of file
为了让赝品有用,我们想要控制一些东西。我们现在知道如何配置赝品,但我们还不能对配置好的赝品做太多——这就是 FakeItEasy 的真正力量所在。我们将在下一章“指定赝品的行为”中学习如何做到这一点
\ No newline at end of file
# 第六章指定假货的行为
# 六、指定赝品的行为
为了让任何配置的赝品有用,我们需要控制一些东西。通过指定配置的赝品的行为,我们将能够在我们的 SUT 测试所有可能的执行路径,因为我们通过使用 FakeItEasy 控制注入到 SUT 的依赖项。
## 返回值
返回值将是最常见的行为之一,你会想要控制你的假货。让我们探索一些常用的方法来指定返回值。
返回值将是最常见的行为之一,你会想要控制你的赝品。让我们探索一些常用的方法来指定返回值。
### 返回
`Returns`调用可能是指定假货行为最广泛使用的调用。让我们更改第 5 章中介绍的`ISendEmail`界面。首先,让我们让它定义一个方法,`GetEmailServerAddress`:
`Returns`调用可能是指定赝品行为最广泛使用的调用。让我们更改第 5 章中介绍的`ISendEmail`界面。首先,让我们让它定义一个方法,`GetEmailServerAddress`:
```cs
public interface ISendEmail
......@@ -51,7 +51,7 @@
代码清单 32:向 ISendEmail 接口添加 GetAllCcRecipients
这里是`Returns`呼叫:
这里是`Returns`调用:
```cs
A.CallTo(() => emailSender.GetAllCcRecipients()).Returns(new List<string> {
......@@ -197,11 +197,11 @@
我们在测试设置中提前提供了两个 GUIDs,然后在对伪造的`IProvideNewGuids`依赖项的`ReturnsNextFromSequence`调用中使用这些值。当`CustomerService`类循环遍历`ICustomerRepository`返回的每个客户时,将依次用每个 GUID 创建一个新的`Email`。然后`Email`将通过`ISendEmail`发送到正确的客户电子邮件地址。
| ![](img/note.png) | 注意:到目前为止,我们只讨论了在测试设置中使用 FakeItEasy。您可能想知道这个单元测试的断言在哪里。现在,因为这一章的重点是指定假货的行为,所以我们在测试设置中关注这一点;我们还没有准备好进入断言。我们将在第 7 章中学习使用 FakeItEasy 的断言。 |
| ![](img/note.png) | 注意:到目前为止,我们只讨论了在测试设置中使用 FakeItEasy。您可能想知道这个单元测试的断言在哪里。现在,因为这一章的重点是指定赝品的行为,所以我们在测试设置中关注这一点;我们还没有准备好进入断言。我们将在第 7 章中学习使用 FakeItEasy 的断言。 |
### 无数次返回
`ReturnsLazily`是一个非常强大的方法,当`ReturnsNextFromSequence`不能为你提供你需要的东西时,你可以用它来配置假货的行为。`ReturnsLazily`在您试图测试在对 SUT 的同一个调用中返回不同对象的代码时非常有用。
`ReturnsLazily`是一个非常强大的方法,当`ReturnsNextFromSequence`不能为你提供你需要的东西时,你可以用它来配置赝品的行为。`ReturnsLazily`在您试图测试在对 SUT 的同一个调用中返回不同对象的代码时非常有用。
假设我们希望在给定此接口定义的情况下获得一个 CSV 字符串形式的客户名称列表:
......@@ -397,7 +397,7 @@
对未配置的赝品的调用不会导致任何事情发生。有两种方法可以解决这个问题。
一是明确告诉 FakeItEasy,假货什么都不应该做:
一是明确告诉 FakeItEasy,赝品什么都不应该做:
```cs
[TestFixture]
......@@ -445,11 +445,11 @@
| ![](img/note.png) | 注意:如果在我们的单元测试中,依赖关系的伪造实例没有被提供给依赖它的类,并且如果该依赖关系的任何成员将从单元测试中被调用,FakeItEasy 将抛出一个异常。 |
你唯一需要明确调用`DoNothing`的时候是如果你使用的是严格的假货,我们将在下一节讨论。
你唯一需要明确调用`DoNothing`的时候是如果你使用的是严格的赝品,我们将在下一节讨论。
## 严格
简单来说,在任何创建的假货上指定`Strict`会强制您对其进行配置。对未配置成员的任何调用都会引发异常。让我们看一个简单的例子。
简单来说,在任何创建的赝品上指定`Strict`会强制您对其进行配置。对未配置成员的任何调用都会引发异常。让我们看一个简单的例子。
```cs
public interface IDoSomething
......@@ -462,7 +462,7 @@
代码清单 49:接口
我们来看看如何为`IDoSomething`配置一个严格的假货:
我们来看看如何为`IDoSomething`配置一个严格的赝品:
```cs
[TestFixture]
......@@ -483,7 +483,7 @@
代码清单 50:配置一个严格意义上的假 idooth
在代码清单 50 中,我们使用`x => x.Strict()`创建了一个严格的`IDoSomething`赝品。我们在假货上配置了对`DoSomething`会员的呼叫。当我们查看 SUT 的`DoSomethingElse`方法时,您会看到我们正在对赝品调用`DoSomethingElse`方法,该方法在测试设置中尚未配置:
在代码清单 50 中,我们使用`x => x.Strict()`创建了一个严格的`IDoSomething`赝品。我们在赝品上配置了对`DoSomething`会员的调用。当我们查看 SUT 的`DoSomethingElse`方法时,您会看到我们正在对赝品调用`DoSomethingElse`方法,该方法在测试设置中尚未配置:
```cs
public class AClassThatNeedsToDoSomething
......@@ -629,7 +629,7 @@
代码清单 57:客户列表现在是一个输出参数
`CustomerService`班也变了。它不再使用 try/catch 块;它正在从`ICustomerRepository`呼叫`GetAllCustomers`,每次`Customer`返回,我们都会发送一封电子邮件:
`CustomerService`班也变了。它不再使用 try/catch 块;它正在从`ICustomerRepository`调用`GetAllCustomers`,每次`Customer`返回,我们都会发送一封电子邮件:
```cs
public class CustomerService
......@@ -783,7 +783,7 @@
让我们稍微谈谈这个单元测试设置。首先,我们做常规的设置项目,像创建我们的赝品,设置一些测试数据,创建 SUT,最后,调用 SUT 的方法,`GetLastAndFirstNamesAsCsv`
这里不同的是我们假货的配置。这里我们看到`Invokes`的介绍。`Invokes`需要一点合理的解释,所以当我们设置我们的假货行为来使用`Invokes`时,请耐心听我给你讲解发生了什么。
这里不同的是我们赝品的配置。这里我们看到`Invokes`的介绍。`Invokes`需要一点合理的解释,所以当我们设置我们的赝品行为来使用`Invokes`时,请耐心听我给你讲解发生了什么。
当我们使用`Invokes`时,我们在“劫持”我们的山寨上的方法来执行定制代码。如果当我们的光标在 IDE 中的`Invokes`上时,点击 **F12** ,我们会看到这个定义:
......@@ -900,8 +900,8 @@
## 总结
在这一章中,我们已经看到了许多不同的方法来说明一个假货的行为。我们从最常用的行为“返回”开始。从那里,我们研究了使用不同形式的 Returns 的其他场景,然后以如何处理异常以及`out``ref`参数结束。
在这一章中,我们已经看到了许多不同的方法来说明一个赝品的行为。我们从最常用的行为“返回”开始。从那里,我们研究了使用不同形式的 Returns 的其他场景,然后以如何处理异常以及`out``ref`参数结束。
虽然到目前为止我们已经介绍了一点,但是我们还没有看到 FakeItEasy 使用的整个工具集。我们的单元测试只覆盖了测试设置或非常基本的断言,我们的 fake 成员还没有处理参数。
为了最大限度地利用 FakeItEasy,我们不仅需要指定行为,还需要处理对那些假货成员的争论。我们将在下一章探讨断言。
\ No newline at end of file
为了最大限度地利用 FakeItEasy,我们不仅需要指定行为,还需要处理对那些赝品成员的争论。我们将在下一章探讨断言。
\ No newline at end of file
# 第七章断言
# 七、断言
到目前为止,我们一直专注于在 NUnit 单元测试的设置中使用 FakeItEasy。我们没有看到任何针对我们任何设置的断言。
......
# 第八章争论
# 八、参数
到目前为止,我们已经看到了如何创建一个赝品,如何配置它,如何指定行为,以及如何使用 FakeItEasy 进行断言。通过到目前为止的所有示例,我们一直在使用一个`ISendEmail`接口,该接口公开没有参数的成员。
......@@ -322,7 +322,7 @@
### 那个。比赛
当断言一定发生了什么事情时,测试一个采用基元类型的方法是相当简单的——但是当我们在假货上处理方法时,我们并不总是那么幸运地处理基元类型。
当断言一定发生了什么事情时,测试一个采用基元类型的方法是相当简单的——但是当我们在赝品上处理方法时,我们并不总是那么幸运地处理基元类型。
FakeItEasy 提供了一种方法来约束传递给伪方法的参数。为了演示这个功能,是时候再次更改`ISendEmail`了。`SendMail`现在将取一个`Email`对象,而不是取四根弦。
......@@ -607,7 +607,7 @@ FakeItEasy 提供了一种方法来约束传递给伪方法的参数。为了演
代码清单 101:测试客户服务类的单元测试设置
在这里,我们创建了我们的赝品,创建了我们的 SUT,然后设置了一个要使用的客户列表。然后,我们将客户列表传递给 SUT 上的`GetLastAndFirstNamesAsCsv`。请注意,我们没有在测试设置中定义我们的假货行为。我们只是创建它并将它传递给 SUT 的构造函数。
在这里,我们创建了我们的赝品,创建了我们的 SUT,然后设置了一个要使用的客户列表。然后,我们将客户列表传递给 SUT 上的`GetLastAndFirstNamesAsCsv`。请注意,我们没有在测试设置中定义我们的赝品行为。我们只是创建它并将它传递给 SUT 的构造函数。
接下来,让我们根据到目前为止所做的设置,探索测试类中的四个测试。
......@@ -650,7 +650,7 @@ FakeItEasy 提供了一种方法来约束传递给伪方法的参数。为了演
* `SetsCorrectHeader`:在这个测试中,我们使用`That.IsSameSequenceAs`根据硬编码`IEnumerable<string>`来建立断言,这也存在于 SUT 的`GetLastAndFirstNamesAsCsv`方法中。在这种情况下,在我们的单元测试中使用硬编码值是可以接受的,因为我们在 SUT 测试的方法也使用硬编码`IEnumerable<string>`
* `AddsCorrectRows`:我们使用与用于`SetsCorrectHeader`相同的方法,除了在这种情况下,我们必须断言`AddRow`方法是通过使用`That.IsSameSequenceAs`为列表中的每个客户调用适当的客户姓氏和名字值。为此,我们使用在`customers`变量中设置的数据,然后在测试中通过索引访问该数据。有一种更好的方法来编写这个特定的单元测试,我们将在剩下的测试方法概述之后再看。
* `AddRowsIsCalledForEachCustomer`:这个测试方法没有使用`That.IsSameSequenceAs`,但是我想把它包括进去,因为我们没有任何地方断言我们的假货上的`AddRow`方法是为了我们传递给`GetLastAndFirstNamesAsCsv`的客户数量而调用的。这个测试负责覆盖那个断言。
* `AddRowsIsCalledForEachCustomer`:这个测试方法没有使用`That.IsSameSequenceAs`,但是我想把它包括进去,因为我们没有任何地方断言我们的赝品上的`AddRow`方法是为了我们传递给`GetLastAndFirstNamesAsCsv`的客户数量而调用的。这个测试负责覆盖那个断言。
* `CsvIsBuilt`:为了拿回 CSV 字符串,我们需要断言调用了这个方法。如果没有,我们将从`GetLastAndFirstNamesAsCsv`返回的只是一个空字符串。
如前所述,让我们重温`AddsCorrectRows`单元测试。我提到过,有更好的方法写这个。基于测试设置和需要做出的断言,有一种方法可以让 SUT 的测试设置(输入)驱动断言。花一点时间看看测试设置和单元测试,看看你是否能搞清楚。
......@@ -673,7 +673,7 @@ FakeItEasy 提供了一种方法来约束传递给伪方法的参数。为了演
代码清单 103:改进的 AddsCorrectRows 单元测试
在这里,我们让客户列表驱动针对每个`AddRow`呼叫的断言。这消除了我们之前的多线路呼叫,在这种呼叫中,我们对客户列表使用了硬编码索引。通过进行这一更改,我们已经允许我们的测试输入 100%地驱动断言,并且提高了测试的准确性和可维护性。
在这里,我们让客户列表驱动针对每个`AddRow`调用的断言。这消除了我们之前的多线路调用,在这种调用中,我们对客户列表使用了硬编码索引。通过进行这一更改,我们已经允许我们的测试输入 100%地驱动断言,并且提高了测试的准确性和可维护性。
### 处理对象
......
# 第九章装病的 SUT
# 九、伪造 SUT
在这本书的大部分时间里,我们一直在从为注入到 SUT 中的依赖项创建假动作的角度来探索 FakeItEasy 的 API。我们已经学习了如何创建、配置和断言对假动作依赖项的调用,以便我们可以测试我们的 SUT。
......@@ -14,7 +14,7 @@
话虽如此,让我们来看看如何在这一章中伪造 SUT,SUT 是继承自一个抽象类。
## 制造假货
## 创建赝品
我们就是这样伪造我们的 SUT 的:
......@@ -49,7 +49,7 @@
注意我突出显示的区域:
`“…allowing for further configuration of any call to the specified faked object.”`
因此,即使我们在伪造 SUT,我们仍然在伪造的 SUT 中执行代码,除了我们通过 FakeItEasy 配置的任何调用。这是直接伪造 SUT 的一个重要区别,而不是制造 SUT 依赖的附属国的假象。
因此,即使我们在伪造 SUT,我们仍然在伪造的 SUT 中执行代码,除了我们通过 FakeItEasy 配置的任何调用。这是直接伪造 SUT 的一个重要区别,而不是创建 SUT 依赖的附属国的假象。
话虽如此,让我们通过重新设计一些与发送电子邮件相关的支持类来假造 SUT。
......@@ -120,7 +120,7 @@
* 它包含一个名为`GetFromEmailAddress` `.`的受保护虚拟方法
* 该方法包装对`ConfigurationManager`的调用,该调用负责从配置中检索“发件人”电子邮件地址
`EmailBase`并没有给我们带来比本书大部分时间都在使用的`ISendEmail`假货更多的功能,除了一点:它不需要呼叫者将“发件人”电子邮件地址传递到`SendMail`方法中。在本例中,让我们假设在我们向客户发送电子邮件的任何时候都将使用相同的“发件人”地址。
`EmailBase`并没有给我们带来比本书大部分时间都在使用的`ISendEmail`赝品更多的功能,除了一点:它不需要调用者将“发件人”电子邮件地址传递到`SendMail`方法中。在本例中,让我们假设在我们向客户发送电子邮件的任何时候都将使用相同的“发件人”地址。
最后,这里是`ICustomerRepository`界面:
......@@ -214,9 +214,9 @@
伪造 SUT 向我们介绍了我们还没有见过的新的应用编程接口调用。让我们深入探讨每一个差异。
### 制造假货
### 创建赝品
首先,让我们看一下我们用来制造假货的代码:
首先,让我们看一下我们用来创建赝品的代码:
```cs
var sut = A.Fake<AdminEmailService>(x => x.WithArgumentsForConstructor(() =>
......@@ -278,7 +278,7 @@
`WithArgumentsForConstructor`有多个过载。当你自己用`IFakeOptionsBuilder`打假时,请随意探索其他你可以调用的选项。
### 配置假货
### 配置赝品
现在我们已经创建了一个 SUT 的赝品,我们使用以下代码对其进行配置:
......@@ -290,7 +290,7 @@
代码清单 119:配置伪造的 SUT
由于在`AdminEmailService`中唯一发生的事情是呼叫`ICustomerRepository`以获得已经下了 100 个或更多订单的客户,并且由于我们已经在设置代码的前面处理了配置那些假货,我们剩下要做的就是配置受保护的虚拟方法,`GetFromEmailAddress`
由于在`AdminEmailService`中唯一发生的事情是调用`ICustomerRepository`以获得已经下了 100 个或更多订单的客户,并且由于我们已经在设置代码的前面处理了配置那些赝品,我们剩下要做的就是配置受保护的虚拟方法,`GetFromEmailAddress`
`Where(x => x.Method.Name == "GetFromEmailAddress")`
......
# 第十章 MVC 与 FakeItEasy
# 十、MVC 与 FakeItEasy
## 简介
......@@ -113,7 +113,7 @@ HttpContext 是巨大的。如果你需要伪造的不仅仅是对 HttpContext
## 用 FakeItEasy 设置 ControllerContext
现在我们已经对系统中的“基础”类有了坚实的理解。Web,让我们看看如何使用 FakeItEasy 来设置我们在创建`ControllerContext`时需要的假货
现在我们已经对系统中的“基础”类有了坚实的理解。Web,让我们看看如何使用 FakeItEasy 来设置我们在创建`ControllerContext`时需要的赝品
首先,让我们构建一个非常简单的 MVC 控制器,并将其称为`HomeController`:
......@@ -162,17 +162,17 @@ HttpContext 是巨大的。如果你需要伪造的不仅仅是对 HttpContext
更新我们的 SUT 就像往常一样——直接更新它,然后将任何依赖传递给它的构造函数。由于`HomeController`没有依赖关系,在这个例子中创建 SUT 非常简单。
在下一行,你可以看到我们正在制造一个`HttpContextBase`类的赝品:
在下一行,你可以看到我们正在创建一个`HttpContextBase`类的赝品:
`var httpContext = A.Fake<HttpContextBase>();`
在我们制造了`HttpContextBase`的赝品之后,我们制造`HttpResponseBase` :
在我们创建了`HttpContextBase`的赝品之后,我们创建`HttpResponseBase` :
`httpResponse = A.Fake<HttpResponseBase>();`的赝品
一旦这两个赝品都被创建,我们现在可以配置调用赝品`HttpContextBase``Response`属性将返回赝品`HttpResponseBase`
现在我们终于可以开始创建我们的`ControllerContext`类了。我们这样做是通过传入一个新的`RequestContext`,并将伪造的`HttpContextBase`、一个新的`RouteData`以及该上下文将用于的控制器传入`RequestContext`的构造器。
为了完成测试设置,我们将 SUT 的`ControllerContext`属性分配给新创建的`ControllerContext`对象,我们将配置的假货传递给该对象。完成后,我们在 SUT 上调用`WriteToResponse`动作方法
为了完成测试设置,我们将 SUT 的`ControllerContext`属性分配给新创建的`ControllerContext`对象,我们将配置的赝品传递给该对象。完成后,我们在 SUT 上调用`WriteToResponse`动作方法
## 单元测试
......@@ -244,7 +244,7 @@ HttpContext 是巨大的。如果你需要伪造的不仅仅是对 HttpContext
代码清单 140:测试设置包括一个伪造的 HttpSessionStateBase
我们正在创建一个`HttpSessionStateBase`的赝品,然后将该赝品设置为在调用赝品`HttpContextBase`上的`Session`属性时返回。通过建立这个假货“链条”,我们现在可以完全控制控制器中`HttpContext`发出的最常见的呼叫
我们正在创建一个`HttpSessionStateBase`的赝品,然后将该赝品设置为在调用赝品`HttpContextBase`上的`Session`属性时返回。通过建立这个赝品“链条”,我们现在可以完全控制控制器中`HttpContext`发出的最常见的调用
下面的测试断言我们将客户的电子邮件地址添加到了`Session`:
......@@ -370,7 +370,7 @@ HttpContext 是巨大的。如果你需要伪造的不仅仅是对 HttpContext
在我们的 SUT 上设置`ControllerContext`之后,我们创建一个`IPrincipal`的赝品,创建一个`GenericIdentity`,然后当我们伪造的`IPrincipal`上的`Identity`属性被调用时,返回该身份。最后一行代码配置我们的`ControllerContext`上的`User`属性。`HttpContext`财产归还我们伪造的`IPrincipal`
最后,现在我们已经创建并配置了我们的假货,我们称之为 SUT 的`SendCustomerEmail`方法,传递给它一个电子邮件地址。
最后,现在我们已经创建并配置了我们的赝品,我们称之为 SUT 的`SendCustomerEmail`方法,传递给它一个电子邮件地址。
下面是单元测试方法:
......@@ -479,9 +479,9 @@ HttpContext 是巨大的。如果你需要伪造的不仅仅是对 HttpContext
让我们从本章到目前为止看到的伪造`UrlHelper`的设置中探索一些差异:
* 我们在制造`HttpRequestBase`的赝品。`HttpRequestBase`具有在控制器`Request.Url.Scheme`中用于该代码的`Url`属性。为了确保在我们运行单元测试时`Request`不为空,我们创建了一个它的赝品。
* 我们在创建`HttpRequestBase`的赝品。`HttpRequestBase`具有在控制器`Request.Url.Scheme`中用于该代码的`Url`属性。为了确保在我们运行单元测试时`Request`不为空,我们创建了一个它的赝品。
* 一旦我们创建了一个假的 HttpRequestBase,我们就配置对`sut.ControllerContext.RequestContext.HttpContext.Request.Url`的调用,以返回我们将要构建的 URL。我们通过用以下代码行填充`fakeUri`变量来做到这一点:`var fakeUri = new Uri(uri);`
* 接下来,我们用字符串填充`fakeUri`变量。格式化呼叫。然后我们创造了一个假的`UrlHelper`。再次,对控制器中`Url.Action`的调用使用`UrlHelper`。一旦我们创建了我们的赝品,我们就使用我们的`uri`变量对其进行配置以返回正确的动作。
* 接下来,我们用字符串填充`fakeUri`变量。格式化调用。然后我们创造了一个假的`UrlHelper`。再次,对控制器中`Url.Action`的调用使用`UrlHelper`。一旦我们创建了我们的赝品,我们就使用我们的`uri`变量对其进行配置以返回正确的动作。
测试设置的其余部分与本章前面的示例非常相似。
......
# 收盘时
# 十一、总结
在这本书里,我们学到了很多关于 FakeItEasy 的知识。至此,对于您将会遇到的一些最常见的场景,您应该已经习惯在单元测试中使用 FakeItEasy 了。对于您可能还有的任何问题,或者对于本书没有涉及的其他场景,请查看以下链接:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册