Tag: 单元测试

单元测试RxSwift应用程序

我今天要谈论的主题是RxSwift应用程序的单元测试。 这是我的系列文章“如何在MVVM中使用RxSwift”的最后一部分,在该系列中,我们通过实现Friends应用程序学习了如何使用RxSwift。 剩下的唯一事情就是对应用程序进行单元测试。 RxSwift应用程序的单元测试与普通swift应用程序的单元测试非常相似。 再一次,最大的变化是我们与观察者一起处理所有的回调和数据更新。 在这篇文章中,我们将看到如何: 处理可观察对象并订阅事件。 模拟网络层以进行单元测试。 在单元测试中处理数据验证。 我们将测试的所有代码都在视图模型内部,因此您还将学习如何对视图模型进行单元测试。 如果您对单元测试的概念不太熟悉,建议您阅读我以前有关单元测试视图模型的文章。 您将获得单元测试的所有基本信息,并友好地提醒您为什么应该始终对应用程序进行单元测试! 😄 我们将通过对称为Friends的应用程序进行单元测试来学习这些知识。 Friends应用程序是我使用MVVM模式实现的应用程序。 使用“朋友”应用,您可以下载朋友列表,并使用UITableView将其显示给用户。 您还可以使用该应用创建,更新和删除朋友。 这是一个简单的应用程序,具有足够的功能,可以处理开发iPhone应用程序时遇到的许多基本问题。 我首先编写了不带RxSwift的Friends应用程序,然后想看看如果我使用RxSwift会更改多少代码。 如果您想进一步了解纯MVVM应用程序的实现,请查看我有关Swift应用程序的MVVM模式的文章。 如果您想了解我们今天测试的应用程序是如何实现的,请查看这篇文章:如何在MVVM中使用RxSwift。 所有代码都可以从GitHub下载。 只要记得签出RxSwift分支即可。 但是现在,让我们开始吧! 我们将从检查如何对FriendsTableViewViewModel进行单元测试FriendsTableViewViewModel 。 这是处理向用户显示朋友列表的类。 由于FriendsTableViewViewModel发出网络请求,因此我们要做的第一件事是模拟网络层以进行测试。 通常,我们不希望单元测试发出网络请求,因为: 从服务器获取答案可能需要一些时间,这会使测试运行缓慢。 由于网络或服务器,测试可能会失败,这使得无法可靠地验证结果。 通过模拟网络层,我们可以为当前测试用例返回合适的答案。 AppServerClient是处理应用程序中所有网络的类。 如果打开该类,我们将看到它具有一个名为getFriends的函数。 这是我们要在第一个测试中覆盖的功能。 它下载朋友列表,然后我们向用户显示该列表。 函数定义如下所示: 我们可以定义一个协议,该协议具有与AppServerClient相同的所有功能定义。 然后,我们可以使AppServerClient和模拟实现都符合该协议。 但是由于我们还没有可用的协议,因此我们将使用良好的旧继承。 MockAppServerClient继承自AppServerClient而我们已经覆盖了getFriendsFunction 。 我们在函数内部所做的第一件事是,我们创建了一个从函数返回的Observable 。 我们为create函数传递一个块,并将Switch用于名为getFriendsResult的变量。 getFriendsResult是用于定义网络请求的不同结果的变量。 成功的情况下,它包含一个朋友列表;失败的情况下,它包含一个错误。 稍后,我们将检查如何为测试定义值。 在switch语句中,我们定义了.success和.failure情况, .onNext订阅者发出带有朋友列表的.onError或带有错误值的.onError 。 我们已将getFriendsResult定义为Optional因为我们不想为模拟类定义初始化程序。 这就是为什么我们还需要在函数中定义.none大小写的原因。 最后,我们将Observable.create需要的虚拟一次性对象作为返回值返回。 RxSwift应用程序单元测试的第一步已经完成! […]

在Swift中编写更好的单元测试:第二部分

问题 在本系列的第一部分中,我定义了我认为是“良好”的单元测试。 这是我最终得到的定义: 如果我们可以同意一个单元测试(或者实际上是任何一个测试)是由一些设置 ,我们正在测试的动作以及关于该动作效果的断言组成的,那么我可以这么简单地说,一种“好的”单元测试可以使这三个组件中的每一个都清晰可见。 您可能还记得,我们以如下所示的测试结束了这篇文章: var sut,其他:用户! func test_equals_allPropertiesMatch_isTrue(){ (sut,other)=(.create(),.create()) XCTAssertEqual(ut,其他) XCTAssertEqual(other,sut) } func test_equals_nameDiffers_isFalse(){ (其他)=(.create(name:“ Jo”),.create()) XCTAssertNotEqual(sut,其他) XCTAssertNotEqual(other,sut) } 我们压缩了设置,以便测试主体仅包含对测试场景重要的信息。 例如,当重要的是两个用户对象的名称不同时,我们在设置中仅包括该信息。 这是提高测试总体可读性的好方法。 但是,我们还有更多可以做的事情。 您可能查看了上述测试,并认为“设置还可以,但是为什么在这里却有两个断言呢?”很好的问题! 我很高兴您关注这个问题。 请记住,以上测试涵盖了User类型上的==函数。 在不给您带来全部数学负担的情况下,平等是“等价关系”的一个示例,而关于等价关系的重要内容之一就是对称性的概念。 简而言之,如果我有两个某种类型的实例a和b ,则永远不会出现a == b为true但b == a为false的情况。 如果这是可能的,那么我们对平等的定义是有缺陷的。 因此,有必要验证我们的==的自定义定义是对称的。 但是,仅看那里的两个断言,就完全不清楚这是意图。 这些测试只是在断言一件事。 经过一段时间和倾向,其他工程师可能会弄清楚为什么我们添加第二个断言,但是我们绝对可以做得更好。 但是…如何? 解决方案 让我们开始做我们可能想到的最简单的事情。 我们有两行代码,而我们只想有一行代码。 解决方案? 一个功能! func assertSymmetricallyEqual(_ sut:用户,_其他:用户){ XCTAssertEqual(ut,其他) XCTAssertEqual(other,sut) } (注意:这里我们专注于测试是否相等,但是对于不平等,您可以做完全相同的事情)。 现在我们的测试变为: var […]

在Swift中编写更好的单元测试:第一部分

问题 瞧,我们是这里的朋友,所以我觉得我很坦白:我在职业生涯中写了一些非常糟糕的单元测试。 20行怪物,具有多个模拟和断言以及异步期望。 您在书中看到的这类标题为“如何修复以前工作过的白痴留下的混乱”的书。我还必须在写完单元测试和不太那么说的代码后维护该代码。更好。 可以这么说,我现在将编写“好的”单元测试作为优先事项。 在开始之前,我将定义我认为是“良好”的单元测试。 如果我们可以同意一个单元测试(或者实际上是任何一个测试)是由一些设置 ,我们正在测试的动作以及关于该动作效果的断言组成的,那么我可以这么简单地说,一种“好的”单元测试可以使这三个组件中的每一个都清晰可见。 (此外:这可能与您对“好的”单元测试的定义有所不同,而这只是我们俩都必须忍受的。) 在一些博客文章中,我将向您展示我们在Clue所做的一些工作,以确保我们始终试图编写“良好”的单元测试。 在本文中,我们将看一个简单的技巧,我们可以使用它来最小化单元测试的设置部分,同时保持清晰度。 假设我们要在一个简单的Swift结构上对相等方法进行单元测试。 struct用户:Equatable { 命名:字符串 让电子邮件:字符串 让需求验证:布尔 静态函数==(lhs:用户,rhs:用户)-> Bool { 返回lhs.name == rhs.name && lhs.email == rhs.email && lhs.needsVerification == rhs.needsVerification } } 我们可以(无限)有许多不同的参数组合可以传递给此方法进行测试。 显然,我们无法对所有这些进行测试,因此我们必须选择一些代表总体趋势的案例。 对该方法进行单元测试的有效方法(实际上,我通常会使用测试驱动的开发方法编写这种方法)是从两个User的所有属性均相等的情况开始,然后测试每个属性不同时会发生什么。 这给了我们这样的测试套件: func test_equals_allPropertiesMatch_isTrue(){ let sut = User(名称:“”,电子邮件:“”,needsVerification:false) 让其他=用户(名称:“”,电子邮件:“”,需要验证:false) XCTAssertEqual(ut,其他) XCTAssertEqual(other,sut) } func test_equals_nameDiffers_isFalse(){ let sut = User(名称:“ Jo”,电子邮件:“”,需要验证:false) 让其他=用户(名称:“”,电子邮件:“”,需要验证:false) […]

使用iOS项目从Jenkins Jobs提取测试结果

文件,文件,文件。 文档是大型项目中的工件,以确保符合软件开发生命周期(SDLC)。 实际上,所需的文件之一就是单元测试报告。 在这里,我将带您进行2分钟的简短导览,以设置您的詹金(Jenkin)工作,以便生成测试结果报告。 如果您不知道如何为您的项目设置詹金斯工作,则可以在这里查看我的上一篇文章。 我们将使用Jenkins的插件调用Test Results Analyzer。 这给了我们4种漂亮的测试结果可视化效果: 表 线 馅饼 酒吧 转到->配置 。 转到构建后操作 。 选择添加构建后操作->发布JUnit测试结果报告。 输入test-reports / *。xml并忽略其余字段。 (包括红色消息) 现在,触发一个构建以运行该作业,等待它执行所有测试用例,现在您可以从根文件夹中检索.xml文件。 您还将在作业的主页上看到一个新图像。 轻按一下“ 测试结果分析器”即可立即显示可视化效果! 虽然这可能不是我们可以生成的最理想的测试结果报告,但是它为我们提供了足够的基本信息,可以为利益相关者提供简要概述执行哪种测试用例的情况(假设我们已经以TDD和BDD方式很好地命名了测试用例) )。

不要依赖临时状态

上一次,我们讨论了构建iOS单元测试的四个简单规则。 我真的很想把所有要做的和不要做的事情放在一起写在一篇文章中,但是阅读起来实在太大了。 所以这是硬币的另一面。 关于您应避免的所有事情的系列文章。 信不信由你,完全有可能编写弊大于利的单元测试 。 在本文中,我们将研究一种依靠临时状态来弄乱单元测试的特殊方法。 什么是临时状态? 如果在使用您的应用过程中可能发生变化,那么就定义而言,这是暂时的或偶然的。 每个应用程序都会在内存或磁盘上存储某种临时状态。 许多应用程序从服务器检索临时状态。 每次您从某个可以写入的位置访问数据时,您都在访问临时状态 。 数据会随着时间变化。 今天得到的可能与明天得到的不一样。 Facebook上的用户可能会说他今天喜欢滑雪,但是明天他会说他喜欢滑雪。 他的爱好是暂时的。 您不能依靠它们随着时间的推移而变得相同。 如果您从服务器上检索他的业余爱好数据,则无法确定会得到什么。 单元测试绝不应依赖临时状态 。 它使他们变得善变。 如果有一天运行它们,它们将提供一个结果。 改天运行它们,它们会产生不同的效果。 单元测试应该是确定性的 这样,我的意思是每次您运行它们时,它们都应始终提供一致的结果。 确定给定单元测试是成功还是失败的事情不应是可能改变的外部事实 。 它应该是您的应用程序所依赖的核心逻辑。 如果单元测试依赖于用户默认设置,本地文件或服务器的响应,则它们不再是确定性的。 这是因为它们的成功或失败不仅仅取决于逻辑本身。 结果,您将无法非常有效地将单元测试用作诊断工具。 您如何知道代码或某些临时状态是否导致给定测试通过或失败? 你不会的 可能永远是。 这种无法确定测试成功或失败原因的能力有时称为脆弱性 。 不稳定测试是由于与被测试代码不直接相关的原因而给出不同结果的测试 。 您绝对想避免这种情况。 这是片状测试的实际外观 假设您有一个应用程序,其中登录用户可以访问某些功能列表。 您可以通过以下方式实现访问授予机制。 如何修复片状单元测试 问题在于缺乏抽象。 shouldGrantAccessToFeatureWith(id 🙂需要知道用户是否已登录,可以通过直接转到用户默认值来进行登录。 如果将相同的逻辑重构为可以被子类覆盖的函数该怎么办? 在编写单元测试时,请始终问自己,该测试采用什么逻辑? 测试逻辑,然后抽象其他所有内容。 下一步是什么? 我目前正在阅读一系列文章,详细介绍在为iOS编写单元测试时应避免的一些体系结构方面的内容。 下一篇文章深入探讨了避免副作用。 目前,您可以了解我的其他一些单元测试内容 我们应该在iOS应用程序中进行哪些单元测试? […]

Swift中与日期相关的当前代码的确定性单元测试

自从我在Envelope框架上发表我的上一篇文章以来已经有一段时间了-Alamofire周围的薄包装使编写网络代码的单元测试变得轻而易举。 可能一开始这篇文章太大了,但是无论如何我都希望听到更多的反馈。今天,我将分享我几年前一直在使用的一个非常简单的技巧,该技巧简化了另一个方面编写单元测试的方法:测试使用当前日期/时间的代码。 问题 因此,想象一下您正在编写一种方法,如果记录已过期,则请求更新记录: 在这里, entityManager是一个对象,负责通过id请求和存储对记录的更新。 如何对这种功能进行单元测试? 看到问题了吗? 我们需要一个nonExpiredRecord ,即条件为record.lastUpdateTime <= dateOfExpiration为false的Record实例。 因此, RecordFixture.nonExpiredRecord()应该生成一个带有lastUpdateTime更新为当前时间的记录! 想象一下,当Record是一个struct时,会是什么样子–您将必须复制该结构的所有字段,并用当前日期更新一个字段。 甚至更糟的是,当这样的设备来自例如保存的网络响应时,其模式可能会随着时间而变化,支持这样的测试代码变得很痛苦,并且是CI失败的根源。 即使夹具的结构正确,但如果经过了实际时间,在调试器中单步执行功能也可能导致将条件评估为错误的结果。 解决方案 冻结时间。 /明显的模式开启/单元测试应该是确定性的。 甚至那些处理当前时间的事件。 /关闭明显的模式/ 想象一下,在单元测试套件下运行时调用Date()总是会返回,例如, 1 January 2016 12:00GMT ? 然后为总是“未过期”的记录创建测试夹具将是微不足道的,不是吗? 那么,如何在单元测试中覆盖Date() ,使其返回预定义的日期呢? 不管是好是坏,这是不可能直接实现的—复杂的方法实现现在已经成为过去。 我们可以做的是: 提供Date()的替代方法,该替代方法将在正常程序执行下返回当前日期,并具有单元测试套件覆盖其行为的能力; 在测试套件中,将其覆盖以始终返回预定义的日期; 禁止使用lint规则或git pre-commit钩子,或同时使用这两种方法在源代码中使用Date()初始值设定项。 让我们一步一步地做。 1.提供获取当前日期的替代方法。 2.覆盖测试套件下的当前日期行为。 如果未使用Quick ,则可以通过重写单元测试套件类的class func load()函数来完成相同的操作。 因此,现在,在测试套件中运行时,主应用程序模块中调用Date.current的代码将始终返回Date.mockDate的值,因此测试治具现在可以是常量(例如,从保存的JSON加载),也可以构造使用已知的模拟日期: 3.禁止在代码中使用Date() 。 让我们在git pre-commit钩子脚本中添加一个部分,并使用简单的regexp查找所有出现在舞台区域的Date()模式的出现: 骇客入侵!

更好的Swift测试设置

在编写测试时,我们必须以良好的代码和质量来进行测试,使其变得更加干净,简单和快速,对吗? 但是有时候很难做到。 我们开始在测试中看到一些可重复的代码,设置和期望值。 今天,我将向您展示提示/技巧 ,以在没有框架的情况下为您的测试创建可重用,更简洁的设置。 让我们开始展示一个使用swift默认测试库的示例。 XCTest。 初试班 该类将以不同的方式测试UserRepo存储库类中名为findAdminsBy的函数。 上面的测试代码易于理解和重用,对吗? 具有一些帮助程序功能,安装程序在每次测试之前运行并重用变量。 是的,很简单并且可重用。 但是,让我们列出一些“问题”: 我们只测试UserRepo的一个功能,即findAdminsBy : –如果要测试UserRepo的另一个功能将增加一些复杂性; –要测试另一个功能,我们需要更改setUp功能或将其删除并将其放入期望功能中。 什么不好,因为这将是一个冗长的考验; 需要更改默认设置时,作为第一个功能测试。 在期望函数内添加了一个设置; 每个期望函数都在调用可重复代码: result = userRepo.findAdminsBy(email: email) 如有必要,添加新参数,我们需要在所有期望函数中进行更改。 那么,我们如何才能为上面的列表改进并创建一些解决方案? 我们将使用闭包概念用作设置并执行可重用的动作。 为了更好地了解闭包 ,这里是官方文档。 首先,我们需要创建两件事来帮助我们的测试。 #1 —动作结构 该Struct将有助于保持关闭状态直到被调用为止。 在闭包内部,将包含您需要保留的操作。 例如: result = userRepo.findAdminsBy(email: email) 。 用法将在稍后显示。 #2 —执行功能 该功能将有助于通过一些设置配置来运行/执行操作 。 波纹管是Executer.swift文件: 第二级考试-重构 现在,让我们使用这两个助手实现一个新的Test类。 我们可以看到的第一件事是操作和设置特定于findAdminsBy函数。 如果我们需要测试UserRepo的其他功能,只需使用自己的设置添加其他操作。 该代码现在更加灵活,可重用和更简洁。 该测试继续简单易懂。 如果您像我一样参加BDD测试,那么我将向您介绍测试框架Quick。 这将帮助您以BDD样式进行测试。 […]

Swift中网络单元测试的完整指南

面对现实吧,编写测试在iOS开发中并不那么流行(至少与为后端编写测试相比)。 我曾经是一个单独的开发人员,但最初并未受过本地“测试驱动”开发人员的培训。 因此,我花了很多时间研究如何编写测试以及如何编写可测试的代码。 这就是为什么我要写这篇文章。 我想分享我在Swift中进行测试时发现的东西。 希望我的见解可以节省您在丛林中奔波的时间。 在本文中,我们将讨论测试101的开始: 依赖注入 。 想象一下,您正在编写测试。 如果您的测试目标(SUT,被测系统)以某种方式与现实世界相关,例如网络和CoreData,则编写测试代码会更加复杂。 基本上,我们不希望我们的测试代码依赖于现实世界中的事物。 SUT不应依赖于其他复杂系统,因此我们能够更快,时间不变和环境不变地对其进行测试。 此外,重要的是我们的测试代码不要“污染”生产环境。 “污染”是什么意思? 这意味着我们的测试代码将一些测试内容写入数据库,将一些测试数据提交至生产服务器,等等。这就是存在依赖项注入的原因。 让我们从一个例子开始。 给定一个应该在生产环境中通过Internet执行的类。 Internet部分称为该类的依赖项。 如上所述,当我们运行测试时,该类的Internet部分必须能够用模拟或伪造环境代替。 换句话说,该类的依赖关系必须是“可注入的”。 依赖注入使我们的系统更加灵活。 我们可以在生产代码中“注入”真实的网络环境。 同时,我们还可以“注入”模拟网络环境来运行测试代码,而无需访问互联网。 TL; DR 在本文中,我们将讨论: 如何使用依赖注入技术设计对象 如何在Swift中使用Protocol设计模拟对象 如何测试对象使用的数据以及如何测试对象的行为 依赖注入(DI) 开始吧! 现在,我们将实现一个名为HttpClient的类。 HttpClient应该满足以下要求: HttpClient应该使用与分配的URL相同的URL提交请求。 HttpClient应该提交请求。 因此,这是我们的HttpClient的第一个实现: 似乎HttpClient可以提交“ GET”请求,并通过闭包“ callback”传递返回的值。 HttpClient的用法 问题是:我们如何测试它? 我们如何确保代码满足上面列出的要求? 直观地,我们可以执行代码,为HttpClient分配一个URL,然后在控制台中观察结果。 但是,这样做意味着我们每次实现HttpClient时都必须连接到Internet。 如果测试URL在生产服务器上,那似乎更糟:您的测试运行在一定程度上确实影响了性能,并且您的测试数据已提交到真实世界。 如前所述,我们必须使HttpClient“可测试”。 让我们看一下URLSession。 URLSession是HttpClient的一种“环境”,它是Internet的网关。 还记得我们所说的“可测试”代码吗? 我们必须使Internet组件可更换。 因此,我们编辑HttpClient: 我们更换 与 然后我们添加一个新变量: […]

Swift BowlingGame TDD单元测试

让我们在本教程中说明“测试驱动开发”。著名的TDD示例之一是保龄球游戏,它的游戏规则涉及真正需要单元测试的一对逻辑。 保龄球游戏有以下四个规则,我们的单元测试就是基于此。 游戏包括十帧,每位玩家都有两次机会在每一帧中敲钉子。 备用:如果玩家在同一帧中两次尝试敲10个针,此当前帧将获得奖励分数。 奖金是下一次玩家掷出的敲击针的数量,例如,第三帧获得奖金5,导致第三帧获得罢工奖金,我在下面做一些注释,红色注释表示备用奖金。 打击:如果玩家在第一次尝试中击中10个针脚,当前帧将获得打击加成,这将增加接下来两轮的得分,例如,第五帧将获得打击加成,橙色以下表示打击加成。 在第十帧中滚动“备用”或“罢工”的玩家将获得额外的加值,以再次滚动,但在第十帧中滚动不超过三倍,因为蓝色六分以下是此奖金。 让我们开始编码,启动Xcode创建一个新项目,别忘了单击“ Include Unit Tests”复选框。 步骤0:创建一个类游戏,以处理保龄球游戏逻辑。 打开TDD_BowlingGameTests.swift,此文件是我们编写单元测试的地方,每个函数必须以“ test”为标题才能运行单元测试。 在TDD中,如果结果失败,则必须编写单元测试并在开发之前对其进行测试,然后开始修改功能,直到通过单元测试。 步骤1:一名玩家掷出20次却没有敲门,预期得分为零。 显然,保龄球游戏必须具有函数“ roll”和“ score”,才能掷出保龄球并计算得分,因此我们编写了单元测试,第一个测试条件是玩家掷出20次并不敲任何东西,预期分数将为零。 //第29行:如果game.score()== 0为true,则通过单元测试。 XCTAssertEqual(game.score(),0) 当然,结果失败了,让我们修改Game中的功能。 让score()返回0怎么样。 好了,首先进行单元测试是为了测试玩家掷出20个球而不敲任何针脚的情况,经过修改的功能通过单元测试后,我们应该对其进行git commit。 git commit -am“传递20个球而不会敲任何针” 步骤2:一名玩家掷出20个球,每滚动2个针,预期得分为40。 通过第一个测试条件后,我们可以将另一个条件写为第32〜38行,该条件将是敲2个引脚,每次滚动然后运行测试。 如您所见,我们失败了,现在我们应该修改函数,添加一个属性来存储得分会有所帮助。 好的,我们的Game类可以在修改后通过第二个条件,同时通过第一个条件,我们应该对其进行git commit。 这是编写单元测试的好处,无论您进行了什么修改,都可以对其进行测试,并确保立即没有错。 git commit -am“传递敲击每个滚动2个针” 第三步:编写干净的代码 正如您在测试用例中看到的那样,第25、33行创建了游戏副本的实例,第26、34行中的for-in循环也被复制,这是不好的编码风格,让我们对其进行修改并再次运行测试。 修改代码使其更整洁,并且单元测试无误通过后,我们应该对其进行git commit。 git commit -am“优化代码” 步骤4:玩家获得备用奖金 让我们测试一个玩家掷出1,4,5,5,6,0…的分数必须如下计算:第1帧(1 + 4),第2帧(5 + 5 + 6),第3帧(6 +0),因此总分必须为27,才能在第二帧获得备用奖金。 显然,我们遇到了失败,我们的游戏逻辑未处理备用条件,让我们对其进行了修改,因为我们需要考虑备用条件,因此由于总得分不再起作用,因此使用商店属性totalScore来计算总引脚数,我们需要的是数组以重新编码玩家滚动的每个掷骰,并处理备用条件,因此我们如下修改了Game。 […]

使用SwiftyMocky生成模拟并简化Swift中的单元测试

简单的5点指导可将您的单元测试带入一个全新的水平。 总体来说,Swift世界最近发生了很多事情,这种趋势并没有错过对开发的一部分进行测试的机会。 尽管良好的测试实践正在传播,但对旨在帮助该过程的工具的需求也在增长。 我很高兴成为一个团队的成员之一,该团队致力于开发一种名为SwiftyMocky的解决方案。 1.模拟是测试双 一般而言,在谈论单元测试和测试时,有一整套特殊目的的对象,称为“测试双打”。 在本文中,我将仅简要介绍它们,因为需要理解SwiftyMocky背后的整个概念。 Test Doubles背后的故事很简单。 由于缺乏更好的词汇,经典的测试方法通常依赖于状态验证。 我们为sut及其依赖项创建初始状态,然后验证执行测试后的状态是否符合我们的期望。 尽管如此,并不是在每个测试用例中我们都在测试sut ,我们可以使用实际的实现作为其依赖项。 数据库操作或网络调用就是一个很好的例子。 我们真的不想在这里进行真正的通话,因此sut尝试进行的了解通常足以确保有效性。 另一个很好的例子(来自Martin Fowler的帖子)是发送电子邮件-很难从测试角度进行验证。 这就是我们的特殊情况对象出现的地方。 基于Gerard Meszaros提出的词汇(我个人觉得非常有用),我将其分为以下几类: 虚拟 :完全没有实现,该对象的整体唯一目的是满足依赖关系要求。 伪造的 :简单但有效的实现,通常采用捷径。 例如,伪存储将宁愿使用某些内存数据结构(如数组或字典),而不是包装核心数据操作。 在提供与具体实现相同的功能的同时,它使状态验证更加容易。 存根 :包含预定义的响应和答案,通常严格针对特定测试用例定义。 Spy :存根,不仅返回预定义的答案,还记录调用的方法。 模拟 :预先设定的期望值,可以验证sut的行为是否符合预期。 在本文的其余部分,我们将重点介绍Mock ,因为它可以处理正确测试所需的大多数内容。 请注意,虽然上述单词在测试双精度类型之间严格区分,但是在大多数工具中, Mock 都是扩展的,具有 Stub 和 Spy的 组合功能 。 在 SwiftyMocky中, 我们执行相同的操作,因此,每当引用 Mock时 ,我们的意思是同时提供 Stub 和 Spy 功能的对象。 尽管拥有Mock对象的想法很明确,并且收益不能被夸大,但这给我们带来了一个问题。 必须编写模拟实现,如果有适当的层分离,则在大型项目中这可能会成为相当大的开销。 在具有适当反映的语言中,有许多库和框架可以在运行时创建Mock ,使您可以选择一种最适合您的测试风格的库。 […]