如何在Swift中编写可测试的代码

18
所以,我的这个问题始于我开始为简单的2行postNotificationaddObserver编写单元测试。从这个类似的问题here你可以看到,为了使其可测试,您需要添加大约20行代码,并且要远离常规编写代码的方式。
面对这个问题,这是我第一次理解单元测试和TDD之间的区别。如果您遵循TDD思维方式,那么单元测试很容易,即如果您的代码可测试。接下来,我想知道如何编写可测试的代码,但我没有找到太多指导方针,每个教程都只是直接编写单元测试。苹果自己的documentation也没有相关内容。
我的初步想法是需要追求“函数式编程”,以一种纯函数的方式编写函数。但这很耗时间,可能需要重构现有代码,甚至对于新项目也需要添加大量代码行,而且我甚至不确定这是否是正确的方法。是否有任何建议的准则或标准可以轻松编写可测试的代码?
我已经知道的: 我知道你不能写任何代码,除非有一个测试来失败它,所以基本上我必须首先编写测试,只要我得到一个错误,甚至是编译器错误,然后我将不得不切换回实际被测试的类,写下必要的内容并使我的测试代码不出错,然后切换回测试类并继续编写我的测试和修复编译错误,直到完成。然后运行测试并查看它是否检查到我想要检查的内容。
对于所有测试,我应该确保我的测试会在我预期失败的地方通过和失败,即当预期失败时测试会通过。
我不知道如何以更简单的方式使流程更顺畅。
我不是在询问如何为NSNotificationCenter编写可测试的代码,而是在询问编写可测试代码的一般指南。
1个回答

21
这是一个相当大的问题,开发者们的观点往往不同。此外,需要注意的是,使代码可测试在许多方面上并不是一个特定于Swift的问题:你能够通常和便捷地编写可测试代码的许多方法实际上都依赖于你遵循一些基本的、普遍适用的原则。通常情况下,测试驱动的设计实践间接帮助你,通过验证你已经遵循了使通过测试执行代码成为可能的做法,同时带来其他程序员生产力和可靠性方面的好处。因此,不幸的是,编写可测试的代码并不是学习一些与Xcode一起工作的机械技巧的问题,而往往是证明你已经设计和规划了程序和库,并遵循了一些良好的实践。
我将尽我所能链接到一些Swift特定资源,以展示我自己遵循的更一般的原则,以使我的代码可测试。
让您的代码可测试通常是遵循良好面向对象设计原则的副作用。 - 您可能会发现阅读有关SOLID 原则的信息很有用。 - 还可以查看这个基于 Swift 的演示,了解每个 SOLID 原则的详细信息。 - Google 代码审查员指南PDF)也是了解典型 OOD 问题及如何避免的好资源(同样巧合的是,这个与 OOD 相关的文档的副标题是“编写可测试代码”)。
遵循良好的面向对象设计本身通常是良好高层架构决策的副作用。基本上,要早期和经常地考虑计划在程序的对象图中引入的类型的种类。它们之间的角色和依赖关系是什么?在不同上下文(例如从 UI 代码 vs 单元测试执行代码)中执行代码时,您的对象图中是否存在任何难以满足或正确构造的依赖项? - 计算机科学文献中有很多关于架构设计模式的内容。四人帮仍然是值得阅读的有关此主题的书籍(尽管并非全部适用于您典型的 Swift 程序)。 - 查看此Swift 设计模式演示,了解如何在 Swift 中实现许多常见的设计模式的概述。 - 特别是如果您的代码是用于移动应用程序,则应阅读有关 VIPER 的信息,这是一种出现在 iOS 应用程序的典型架构需求中的移动应用程序导向的架构模式。 - 将设计模式与上述列出的 SOLID 原则联系起来,"单一职责"原则可能是许多大型 Cocoa 程序最明显违反的一个原则,这是由于糟糕的架构实践所致,也导致非常难以测试的代码。事实上,人们经常将在 Cocoa 中实际应用 MVC 称为"Massive View Controller"
上述提出的观点并不仅适用于Swift,我认为这些主张并不会引起太大争议。然而,Swift还提供了语言特性,可以帮助您编写可靠、无bug的代码,部分原因是这些语言特性可以帮助您使代码更易于测试。以下是一些良好的Swift实践:
  • 尽可能使用值类型:Swift非常适合编写最小化自动引用计数引用(指针)的程序。这带来了性能优势,同时也提高了编写可靠代码的机会,减少了意外依赖和数据竞争,这些在测试中很难捕获。
  • 尽可能保持不变性:Swift中的语法约定和类型系统有助于避免可变状态,这对您的代码的可测试性有负面影响(配置测试对象图可能变得困难,如果程序的可能状态空间很大,则实现真实世界可用的测试覆盖率也会变得困难)。
  • 苹果还提供了一些关于值类型和不变性的架构问题的指导,这也涉及到代码的可测试性。
  • 尽可能使用协议:要理解原因,请阅读Liskov替换原则 :-) 更严肃地说,优先使用协议而不是具体类型可以帮助您编写可测试的代码,例如允许您在测试设置中使用特定于测试的类型来满足依赖项,这些类型可以伪造或模拟某些与测试无关的资源。
  • 在合适的时候使用函数式编程技术:通常使用函数来组合功能可以帮助您编写可读性高、灵活的代码,避免可变状态。我建议阅读Chris Eidhof和其他人的《Functional Swift》作为应用Swift中的函数式模式的良好指南。

再次强调,这个问题很大,我的回答只是浅尝辄止,为了说明可测试性没有单一的解决方案——它是在设计代码时遵循多种最佳实践的结果。


1
我建议您仔细阅读我的回答,以了解为什么我不会给您提供好的和坏的例子(我给您一个提示,您正在过度简化问题) :-) 因此,我建议您进行背景阅读 - 在这个问题上,没有欺骗技能和经验的方法。 - mz2
我的问题更像是TDD是否应该像这里所提到的那样麻烦。那么当你想要进行TDD时,如何避免这种情况呢?但似乎你的答案是XCTestExpectation... - mfaani
1
我们真不应该在这个主题讨论那个问题,你特别指出你要求更一般的建议。作为一个与StackOverflow相关的普遍观点,我会建议提出更直接的问题,而不是像你这样提出 "How can I do the unit testing" 这样的问题 - 你想要测试的具体内容并不清楚。总的来说,XCTestExpectations 帮助测试异步代码,但我现在看到你正在测试视图控制器代码,最好使用 Xcode 的UI测试系统 进行测试。 - mz2
1
此外,测试驱动开发通常并不乏味...除非你正在处理未设计为进行测试的代码 :-) 在另一个问题中,问题实际上更多是一种笨拙的测试策略,而不是真正需要子类化NSNotificationCenter。您可以注册为默认NSNotificationCenter的观察者,并使用基于回调或闭包的通知观察API来测试您想要的内容,假设您知道您正在尝试观察的通知名称以及正确响应。 - mz2
不是在寻找机械的技巧。虽然我没有阅读你提供的所有链接,但我已经浏览过它们和维基百科的链接……仍然想问一下是否你知道一个创建对象图的工具。 - mfaani
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接