通常,我们需要使用副作用来对代码进行单元测试,这些副作用会使用抽象(Swift中的协议)与系统的其他部分进行交互。 有很多技术可以构建这种抽象并验证我们的代码是否按预期工作:存根,监视,伪造等。 以前,我深入研究了自动生成存根占位符的方法,该占位符允许在测试用例中无缝自定义模拟函数的功能行为。 让我们通过间谍注册扩展该方法,该注册可以观察并记录在测试案例中产生的副作用。 首先需要知道的是 让我简要地介绍一下如何使用一些Swift编译器帮助来加快构建函数的存根的过程(有关详细信息,请参阅我的上一篇文章)。 假设您希望为包含某些功能的协议创建一个模拟(为了本文的缘故,将其命名为addUser(name:) )。 而且,与其使用自定义标记和/或嵌入的断言从头开始创建它,还不如说每个函数只生成一个变量作为占位符,以在该函数调用期间在内部调用。 以传统方式,您将需要手动提供该占位符函数的类型,但需要使用lazy修饰符和一些简单的辅助函数stub ,它可以由Swift类型系统来推断。 最终的解决方案看起来很简单:
使用者介面测试介面X的LLVM使用者测试,X的LLVM测试结果,测试使用者的测试范围。 Başkabir makalede国家公园gzerehoşçakalın🙂
编写测试用例的步骤? 1.失败-> 2.通过-> 3.重构 这些步骤是什么意思🤔 失败:首先编写测试用例以进行失败。 通过:然后仅通过测试用例。 REFACTOR:最后但最不重要的一点是,尝试重构意味着重组测试用例。 1.给定-> 2.当-> 3.然后 给予:您已经拥有的内容,例如API网址。 时间:您将如何处理此问题,例如获得成功/失败的响应 然后:在获得预期的输出之后,例如响应成功/失败,该怎么办 查看以下代码段以进行API调用: 导入 XCTest class UnitTestingTests:XCTestCase { var sessionUnderTest:URLSession! 覆盖 func setUp(){ 超级 .setUp() sessionUnderTest = URLSession(配置:URLSessionConfiguration.default) } func test_APIComplition(){ //给定 让 url = URL(字符串:“ https://itunes.apple.com/search?media=music&entity=song&term=abba”) 让 promise =期望(描述:“已调用复合处理程序”) var statusCode:整数? var responseError:错误? //什么时候 让 dataTask = sessionUnderTest.dataTask(with:url!){(data,response,error) in statusCode =(响应为 ?HTTPURLResponse)?. […]
R.swift及其替代品是在更短的时间内编写更安全代码的绝佳方法。 它在基于情节提要的项目中特别有效,可以节省大量工作。 如果您的项目中没有用于资源的任何代码生成器,我建议您考虑添加一个。 安装和设置后,您可以使用生成的代码访问笔尖中的视图: 还不错,不是吗? 我们的项目非常有力地保证现在仅使用现有资源。 但是我们不能确定是否使用连接到某物的插座。 在开发过程中没什么大不了的。 生产呢? 没问题。 让我们编写一些单元测试。 还有更多测试 到了某个时候,我们肯定会遇到另一端的问题-不再需要某些出口,因此将它们删除了,我们必须调整单元测试。 相当重复无聊的工作。 我们有更好的选择,也许还有更好的消磨时间的方式,而不是支持我们的测试,因此-完全放弃! 更长的选择是使用Sourcery自动生成它们。 安装非常简单,但是正如这里提到的,使用brew获得新版本目前并不容易。 我下载了最新版本并将其直接添加到项目中。 之后,就可以使用了。 在R.swift的帮助下,我们已经为我们提取了所有视图。 我们唯一需要的是一个实际的测试模板。 Sourcery当前支持Swift,Stencil和JavaScript。 我坚持使用第二个,发现它非常方便并且得到了很好的支持。 用于为我们的xib生成测试的模板如下所示: 我们要做的仅有两件事-遍历所有属性并找到我们的出口。 在拥有所有数据之后。 从我们的模板中: {method.returnType.variables%中的变量所占的百分比} {%为variable.attributes中的属性,其中attribute ==“ IBOutlet”和variable.typeName | hasSuffix:“!” %} 接下来,我们将进行由{{staticXibVar.name}}和{{variable.name}}组成的特定测试。 XCTAssertNotNil(R.nib。{{staticXibVar.name}}。firstView(owner:nil)!。{{variable.name}}) 现在,我们准备将所有测试放入XCTestCase中并添加到项目中。 在我的情况下,结果如下所示: import,class和func都在模板的内部,我跳过了它们以使其更短。 同样,我创建了第二个用于检查情节提要的模板: 查看控制器测试 核心思想是相同的,主要问题是实际了解在哪里寻找特定类型和变量。 同样,为了使R.swift能够找到您的视图控制器,您必须为其添加Storyboard ID 。 元编程对我来说是个新主意,有很多功能可以发现。 如果您有一个很好的用法示例以及一些问题,请发表评论或在LinkedIn或Facebook上与我联系 可以在此处找到示例项目。 如果您喜欢这篇文章-给它一些鼓掌👏🙂
通常,我们需要对带有副作用的代码进行单元测试,这些副作用通过抽象与Swift中的协议与系统的其他部分进行交互。 有很多方法可以验证它是否按预期工作:打桩,监视,伪造等。总的来说,工程师将这些形式称为模拟 ,即使“ test double”也是合法名称。 仅供参考,“测试双倍”的名称来自电影行业中的“特技双倍”。 在Swift中编写测试双打(例如存根或模拟)是一个无聊的过程,开发人员经常尝试使用Sourcery / SwiftyMocky之类的工具自动生成它。 但是,如果您希望进行更精细的控制,或者不想在构建过程中引入新的步骤,则可以使用一个技巧来快速(至少更快)实现双重测试。 如果您知道存根和模拟的工作原理,则可以轻松地跳过以下两章,跳至下一章或直接跳至解决方案。 首先,让我们区分存根和模拟之间的区别是什么,因为它们非常相似。 根本区别在于验证过程: 对于存根,测试用例必须手动调用一些方法以验证是否发生了预期的副作用 模拟使用预定义的配置自动进行验证 让我们用伪swift代码进行比较: 与CounterStub相反, CounterMock是在初始化期间进行预配置的,在CounterStub中,在验证步骤中,我们需要将一些addCalledTimes属性与3进行显式比较。实现这两个测试对addCalledTimes的方式是实现细节。 一般的经验法则是, 验证可以区分我们是使用存根还是模拟(或混合混合)。 让我们先不进行理论讨论,然后再回到地面。 存根实际上如何在Swift中实现? 我们想要创建一个符合协议的对象,并有机会1)验证是否已使用期望的参数调用了给定的函数,以及2)控制函数返回了值。 有许多方法可以做到这一点,最简单的方法就是引入想要跟踪到存根中的行为一样多的属性。 例如,如果要在给定的时间调用该函数,请添加一个计数器,在函数中对其进行递增,然后在测试后验证其值-如上面的代码段所示。 但是,一种替代方法是添加一个附加变量,该变量提供一个函数占位符 (具有与该函数几乎相同的签名类型),并在遵守协议时调用它。 我写这本书几乎是因为如果测试用例完全希望忽略给定的功能,我们将其设为可选只是为了方便。 在此博客文章中,我们将讨论协议功能,因为变量相对于存根而言相对简单-仅通过引入实例即可。 为了这篇文章的缘故,让我们考虑一下最简单的协议,该协议将用户名同步保存到数据库中,并返回一个布尔值来通知过程是否成功。 它的模拟看起来像这样: 对于函数addUser ,我们引入了一个变量addUserAction -每次调用addUser时addUser对其求值的函数。 然后,在测试场景中,您可以1)验证被测系统(测试代码)确实调用了该函数,并且2)模拟数据库的响应方式(无论返回true还是false ): 一般而言,通过引入与函数参数和返回类型匹配的addUserAction变量,我们让测试用例定义协议相关函数的主体。 顺便说一句,如果您不喜欢addUserAction属性的可选性,则可以继续执行等效的实现: 在上面显示的存根中,我们必须显式提供一种addUserAction变量。 对于短函数来说,这似乎是一件容易的事,但对于涉及多个参数,闭包, (re)throws , @autoclosure ,最终可能会导致您和编译器之间的较量。 为了克服这种麻烦,我们可以利用Swift的类型推断系统。 解决方案涉及一个全局函数,该函数返回与参数类型相同的nil值: 起初,它看起来像一个胡说八道的函数,但我们只会用它来拉出T类型,而不是它的值。 函数是Swift中的一等公民,因此我们可以将函数作为参数传递给asNil以获取Wrapped类型与函数类型匹配的nil实例。 在下面的示例中,我们将变量addUserAction初始化为与协议功能相同类型的nil值。 那正是我们以前必须手动写下的内容: 需要将此变量标记为 lazy 因为引用 addUser 隐式依赖于 […]
如何测试非调用完成处理程序方案 使用XCTestExpectation测试完成处理程序API幸福路径场景非常简单。 如果您的测试用例必须验证给定的处理程序是否未被调用,事情就会变得复杂。 如果在您模拟的其他事件之后同步调用它,则仍然很简单。 但是,如果延迟执行时间呢? 在本文中,我将向您演示一种解决方案,以100%确保不会调用给定的完成处理程序-快速可靠。 我们来谈谈提供完成处理程序的一些异步任务的单元测试,比如说从服务器下载JSON。 似乎很明显,我们必须验证在快乐路径方案中是否调用了完成。 XCTest提供了一个不错的API:只需创建一个期望,在完成时实现它,然后在测试结束时等待所有实现: 天真的解决方案 另一种方法是稍等片刻,并假设只要未完成完成(例如0.1s),它就永远不会执行。 它有两个明显的缺点: 您必须猜测要等待多长时间。 是0.1s还是0.01s? 如果设置不正确,很容易导致测试无效或不稳定。 它增加了单个测试的持续时间,默认情况下默认应尽可能快。 这两个缺点都可能导致危险的陷阱: 当测试花费的时间太长或不稳定时,您的队友(或将来的您)就会开始忽略它们,而迟早就放弃它们。 让我问一个问题:您何时能100%确定系统的某些外部部分永远不会调用完成? 答: 在没有人保留的情况下,不再有完成任务! 为了验证没有什么延迟执行块,我们必须确保所有其他实例都丢失对完成的引用。 好的,听起来很简单! 但是如何实施呢? 当然,我们不能定义对块的弱引用: ‘weak’ may only be applied to class and class-bound protocol types 。 因此,让我们定义简单的类ReferenceObserver ,它仅通知其deinit : 目前,我们对ReferenceObserver实例有两个引用: reference和capturedReference (已完成)。 注意:我们必须将虚拟分配添加到匿名变量 _ ,否则Swift根本不会捕获它。 如您所料,让我们摆脱助手参考(只是调用reference = nil )和voilà之一: 只有完成处理程序块会保留对ReferenceObserver并且一旦从内存中删除了完成处理程序, deinitCompletion自动满足deinitCompletion期望: 这是我们的最终测试: 我们最终得到了一个解决方案,该解决方案是: 不需要增加测试持续时间,因为在ReferenceObserver […]
我们都喜欢协议扩展,它是Swift中面向协议编程(POP)最强大的元素之一。 尽管它们具有无可置疑的好处,但在少数情况下,您应该避免使用它们。 在本文中,让我演示一个潜在的陷阱,当您试图对依赖某些协议扩展功能的代码进行单元测试时,API使用者可能会陷入此陷阱。 快速提醒:方法分派 在Swift中,我们有三种方法分配: static , vtable和message分配。 如果您不熟悉此术语,请允许我推荐Riazlab的一篇精彩文章。 简而言之,如果在多个位置(例如,父类或协议扩展)定义了相同的签名,则调度方法使用不同的技术来选择要执行的函数的具体实现。 例如,1)具有从 NSObject 继承的类 将始终使用消息调度,而2)值类型(结构,枚举)将始终使用静态调度。 好的,让我们回到危险的情况下,作为一个API创建者(我们都是API设计者,您还记得John Sundell的演讲吗?),我们遵循最佳实践并为我们的公共API提供协议抽象。 就本文而言,假设我们希望公开一个可能记录详细和错误消息的logger类,如下所示: 解决该方法的一种方法,以验证LogMock.log(_:message)是否已正确调用。 乍看起来似乎是一个合理的想法,但是这种方法存在一个固有的问题-在System类测试中,我们才刚刚开始测试由第三方开发人员编写的API中的verbose(message:)实现。 Logger.verbose(_:String)潜在的实现更改或错误可能会影响我们的测试结果。 顾名思义,单元测试应该在高度隔离的上下文中验证代码的单个单元(此处为System类)。 解决这个问题真的很简单。 作为API设计人员,您要做的就是将要在协议扩展中公开的所有函数/变量包含在主协议定义中,例如: 客户流 上面的修复程序专用于API创建者,但是即使您无权修改协议声明,也可以针对您的麻烦进行补救。 您将需要依赖自一个原始协议继承的自定义协议,并包括API设计器仅从协议扩展中公开的所有声明。 同时,在等待API设计者的修复程序时,您不再受到阻塞。 我们讨论了协议扩展,发现测试依赖协议扩展的代码可能很棘手。 幸运的是,有一个非常简单的补救方法–只需在协议定义中包含来自公共协议扩展的函数/变量定义即可。 因此,当您听到自己说:“让我们在协议扩展中实现它”时,您将知道该进行回退的时间了,并确保静态分发不会阻碍客户的单元测试。 尽管具有所有优点,但vtable分派的动态性质与静态 表相比具有一定的性能开销,而静态表不必执行表查找。 但是,在大多数情况下,差异并不明显,因此这听起来是使API可测试的合理权衡。
AdınaCardNumberTestskoyalım。 Bir kredikartıView’ıyapıpbunun快照testini yapmak istiyorum。 本AMCreditCardViewadındabir Github reposundanyararlandım。 İndiripiçindekiAMCreditCardView查看地图,查看如何设置Alalım。 Swift 5içinufak bir fixyaptım。 Böylece快照testiniökrenanak观看。 Unutma,依依 Ardındanbir测试班’ıyazıyoruz。 FBSnapshotTestCase’tentüremelibu类。 哦,XCTestCase’tentürüyorkendiiçinde。 Biz enbaştaUnit Test’li bir projeistediğimiziçinXcode bizimiçinbir taneayarladı。 Onuaçıpiçineaşağıdakikodu tamamenyapıştıralım。 导入XCTest @testable导入CardNumber 导入FBSnapshotTestCase 类CardNumberTests:FBSnapshotTestCase { 覆盖func setUp(){ super.setUp() recordMode = true } func testCardType(){ 让cardView = AMCreditCardView( 框架:CGRect(x:0,y:0,宽度:240,高度:150)) cardView.cardNumber =“ 4374123412341234” cardView.cardHoldersName =“ ERK EKIN” cardView.expirationDate =“ 06/32” […]
在编写单元测试时,开发人员通常由内而外地执行测试。 这意味着首先要编写对数据访问层的测试,然后是业务逻辑层,最后是用户界面层。 问题在于,我们离应用程序行为越远,我们与应用程序功能的隔离就越紧密。 行为驱动开发使我们可以从用户的角度而不是开发人员的角度来看应用程序。 在这篇文章中,我将使用行为驱动开发的原理来实现注册用户功能。 项目名称是“ People ”,它由一个单视图应用程序,一个单元测试项目和一个UI测试项目组成。 与其从数据库层开始,不如从我们要实现的功能/行为开始。 下面列出了一些功能: 作为用户,我应该能够成功注册一个帐户 作为用户,如果我使用现有的用户名,则应该收到错误消息 我坚信,如果您不进行测试优先开发,那么您将失去编写单元测试的许多好处。 TDD和BDD不仅允许您测试应用程序,而且还规定了应用程序的总体体系结构。 测试优先开发 甚至不用考虑在您的应用程序中编写一行代码! 我们将从编写单元测试开始。 我们将从编写UI测试开始,该测试将代表注册新用户的行为。 为了访问测试中的控件,您将需要使用可访问性标签提供一个标识符,如以下屏幕截图所示: 另外,请注意XCTest框架中没有tapAndType函数。 tapAndType函数是一种自定义扩展方法,它允许快速选择和设置UITextField的文本。 即使设置了标识符,测试仍将失败。 原因是我们尚未实现注册按钮事件的代码,并且尚未将用户带到登录屏幕。 此时,您将返回RegistrationTableViewController并实现按钮单击事件,如下所示: 由于您没有名为User的类或名为dataAccess的实例,因此这些代码均无效。 这将导致您实现User类,如下所示: 您还可以在iOS单元测试项目中编写单元测试,以确保可以创建User类的实例,如下所示: 我认为上述单元测试不会为我们实现所需功能提供任何价值。 单元测试仅仅是测试我们可以创建类的实例。 您应该始终编写单元测试来测试业务领域的复杂性,而不是语言功能。 这将引导我们进入数据访问层,我们将在下一节中介绍它。 资料存取层 为简单起见,我们将使用UserDefaults存储我们的数据。 我们将从实施单元测试开始,如下所示: 该测试甚至不会编译,因为我们的DataAccess类没有saveUser和getUsers函数。 让我们如下所示添加它们: 上面的代码进行了多次迭代和大量失败的单元测试。 即使现在,由于用户不符合NSCoding的要求,测试仍将失败。 让我们更新User类,使其符合NSCoding协议。 现在,如果您再次运行测试,则测试将通过,因为所有部分均已安装到位。 尽管数据访问测试可以通过,但最外部的测试仍将失败,因为注册后用户不会转移到“登录”屏幕。 塞格斯 在开始添加segue之前,请看一下之前实现的以下代码。 这是saveButtonClicked代码的最终版本。 原始实现如下所示: 如您所见,原始版本没有动态执行任何排序。 segue是使用Storyboard从按钮控件创建到LoginTableViewController的 。 这允许测试通过,但是后来我们转到下一个功能时测试被破坏了。 作为用户,如果我使用现有的用户名,则应该收到错误消息 现在,您将看到测试优先方法如何帮助我们设计了更好的应用程序。 打扫干净 单元测试最重要的原则之一是,它应该在完成状态后始终将其恢复为原始状态。 这意味着,如果您的单元测试将数据添加到数据库中,那么在测试结束时,它应该从数据库中删除数据。 这样可以确保在运行下一个测试时数据库处于初始状态。 […]
今天,我想讨论一下XCode中的单元测试。 我一直想涵盖这个主题,但是我从来没有真正理解过如何实现它。 我已经决定本周学习单元测试的基础知识,并希望在我的下一个应用程序中实现它。 在这个博客中,我将为您提供有关实现非常简单的单元测试的教程。 我希望这将使我的听众能够在将来的应用中学习和使用它。 什么是单元测试? 我们都是人类,当我们是人类时,我们往往会犯错误。 作为开发人员,我们的代码与我的类推代码相同,我们会犯很多错误。 编码中的苦难之一是调试是一个痛苦的过程,尤其是当我们有几百行代码时。 至少可以说,调试并非易事。 但是,在Xcode中,有一种解决方案可以帮助简化调试过程,也就是单元测试。 单元测试允许您通过以测试格式编写其他代码来及早发现错误。 如果结果不是我们期望的,我们的应用程序将尽早结束,因为我们的代码未通过测试。 想象一下遍历您的代码并尝试查找错误的时间,并意识到该错误只是字符串中的一个简单拼写错误。 如果有一些工具可以告诉您那不是很好吗? 您很幸运,因为这是单元测试的唯一目的! 我不想给您一种印象,通过使用单元测试将导致没有错误的代码,因为事实并非如此! 单元测试将在整个过程中为您提供帮助,但不能完全依靠它。 让我们开始吧! 将单元测试添加到您的项目 首次创建项目时,可以在开始时添加单元测试。 它位于底部,您只需简单地检查一下并将其包含在您的项目中