Swift 代码的可测试性

简介: 本文讲的是Swift 代码的可测试性,我知道如何编写具有可测试性的 C++ 和 Objective-C ,但是 Swift 在这方面又是怎么做的呢?
本文讲的是Swift 代码的可测试性,

我知道如何编写具有可测试性的 C++ 和 Objective-C ,但是 Swift 在这方面又是怎么做的呢?

一种编程语言的特性和整体感觉可以对我们如何表述代码产生巨大的影响。相信你们中的大多数人都已经知道这一点,因为你们很早就开始学习 Swift,并且已经领先于我,我非常乐于去追赶你们。Swift 的特性就像新的玩具一样!但是这些特性又是如何影响可测试性的呢?

编写可测试的代码

透露一个秘密:下面的书本链接是个附带链接,如果你买了其中的任何东西,我可以赚取一定的提成,对你来说不造成任何额外费用。

单元测试最大的挑战就是编写可测试的代码。这通常就意味这第一次重新学习如何编写代码!这本书 Working Effectively with Legacy Code 提供了许多技巧来帮助在编写代码时不用考虑其可测试性。这些技术比如说‘子类和方法重载’在处理遗留代码时可以做的很好,所以我也开始在实施 TDD 的过程中使用它们。

但是有一天,我问自己,‘为什么我一直在写遗留代码’

换句话说,这些处理遗留代码的技术是一种权宜之计。难道没有不求助于变通方案便可提高可测试性的方法?

于是我找到了 Dependency Injection。依赖注入的其中一个目的就是提供对它正在测试的代码提供一个测试完整的控制。

基于不同的语言特性可以使依赖注入变得更加简单。构造器注入在 DI 中是一种更好的形式。Swift 默认的参数值可以使构造器注入更加简单。(但是如果你有一个 Swift 闭包属性?并且有一个默认闭包?请看下文!)

回顾:在 Objective-C 下 Marvel API 的验证

我第一次学习单元测试和 TDD 是在写 C++ 的时候。而当我开始转而使用 Objective-C,这简直是一股清流!生产代码和测试代码都变得更加易读和易写。

现在我再重新做我的(令人可悲的是还没完成)TDD sample app,这一次是用 Swift 语言。漫威浏览器将会成为一个简单的 app 在漫威宇宙中探索动漫角色。

这其中的一大部分已经知道如何与 Marvel API 交换信息。这开始于spike solution in Objective-C。为了把 spike 变为符合 TDD 的代码,我必须处理两个会使单元测试变得棘手的东西。

  • 时间戳
  • MD5 哈希

相比于把所有问题都事先解决,我首先尝试了子类和方法重载这个方法。也就是说我把这两个东西的作用域绑定在方法上,然后创建了一个特殊的子类,这个子类只用于重载这两个方法。

这是一种处理遗留代码的技术,但是在开始时这任然是一个不错的方法。所谓的诀窍不是在这个方面。

我们如何设计可以被替代的东西?我想到了使用策略模式,但是我决定使用代码块来代替。我把这些块变做属性。就是这个想法打开了属性注入的大门。

我们如何可以提供一个默认的代码块?当然,我可以在初始化程序中做到这点。但是这会使初始化程序变得到处都是,我使用了惰性属性-没有推迟它们的初始化,而是把它们移出了初始化程序。

测试依赖于时间的代码

在开始使用 Swift 时,一些原先在 Objective-C 的代码实现一直困扰着我。为了保持代码简单,时间戳是作为一个惰性属性实现的,它记录了第一次访问的时间。多个对这个实例的调用都会得到相同的结果。我尝试使用工厂模式来隐藏这个。

J. B. Rainsberger 对于这个问题有篇很好的文章。这篇文章实际上是关于一个更笼统的问题:使你的抽象层级正确,但是这次的案例是依赖于时间的代码。在Beyond Mock Objects中,他描述了一个例子,这个例子在一个阶段会需要不同的实例:

我也发现这有点奇怪。我在一个方面简化了依赖,而在另一个方面使依赖变得更加复杂:客户端对于每个请求都必须初始化一个新的控制器,或者换句话说,控制器有请求作用域。这听起来是错误的。

我鼓励你去研究下这篇文章。基本上,与其在问题中有一个方法来决定时间戳,我们可以简单地把时间戳作为参数传递。这是方法注入的一个经典用法。

在 Objective-C 中可以使用级联方法来实现方法注入。接口应该十分清晰:

- (NSString *)URLParameters;
- (NSString *)URLParametersWithTimestamp:(NSString *)timestamp;

第一个方法调用了第二个方法,提供了一个默认值:

- (NSString *)URLParameters
{
    return [self URLParametersWithTimestamp:[self timestamp]];
}

Swift 使它变得更简单。我们可以简单的使用默认参数值而不是使用级联方法。

	func urlParameters(
    timestamp: String = MarvelAuthentication.timestamp(),
    /* more to come here */) -> String

其中一个复杂的地方是:Swift 并不允许我们调用另一个实例方法来获取默认的值。所以如果你可以,使它作为一个类型方法实现。(如果不行,我们总是可以依靠级联方法)

Swift 默认的属性值

当我在 Objective-C 中使用属性注入,我通常会为属性设定默认的值。我们可以在初始化程序中建立小的属性。但是有时候你并不想在初始化程序中的代码是和程序无关的。或者有的时候它并不小-它是一个代码块。在这些时候,我会使用 Objective-C 中的惰性属性用法。

这是我如何实现计算 MD5 哈希值的代码块:

- (NSString *(^)(NSString *))calculateMD5
{
    if (!_calculateMD5)
    {
        _calculateMD5 = ^(NSString *str){
            /* Actual body goes here */
        };
    }
    return _calculateMD5;
}

但是 Swift 允许我们在属性定义的地方设置默认的属性值。这是一个有默认值的闭包属性:

	var md5: (String) -> String = { str in
    /* Actual body goes here */
}

哇,这也简单太多了吧!

闭包实验

这是我主要测试现在看起来的样子,这还是不错的:

func testUrlParameters_ShouldHaveTimestampPublicKeyAndHashedConcatenation() {
			sut.privateKey = "Private"
			sut.publicKey = "Public"
			sut.md5 = { str in return "MD5" + str + "MD5" }

			let params = sut.urlParameters(timestamp: "Timestamp")

			XCTAssertEqual(params, "&ts=Timestamp&apikey=Public&hash=MD5TimestampPrivatePublicMD5")
	}

正如你可以看到的,我覆盖了 MD5 的闭包用于保持测试的可理解性。这个测试表明产生的 URL 参数是对的。你可以看到如何使用哈希工作的。

但是随后我想到,为什么要覆盖一个闭包属性呢?为什么不把 MD5 算法当作一个参数传进去呢?如果我们把它作为最后一个参数,那么我们就可以使用尾部闭包语法:

func testUrlParameters_ShouldHaveTimestampPublicKeyAndHashedConcatenation() {
        sut.privateKey = "Private"
        sut.publicKey = "Public"

        let params = sut.urlParameters(timestamp: "Timestamp") { str in
            return "MD5" + str + "MD5"
        }

        XCTAssertEqual(params, "&ts=Timestamp&apikey=Public&hash=MD5TimestampPrivatePublicMD5")
    }

这代码的可读性更强吗?老实说,我认为它甚至变得有点更糟糕了。我使用空行把我的测试分成了 ‘Three A's’ ( Arrange(安排), Act(执行), Assert(断言))。我认为这个特定的闭包弄乱了我的执行部分,同时它也没有名字,这使人更难理解它代表的是什么。

但是我不得不设法找出来!

可测试的 Swift

以下使我至今为止学到的 Swift 是如何使我们可以简单写出写兼顾可测试性和清晰的代码

默认: Swfit 的默认值简化了许多依赖注入技术:

  • 构造注入:在初始化器中使用默认参数
  • 属性注入:使用默认的属性值
  • 方法注入:在任何方法中使用默认的参数值

闭包: Swift 闭包的一致的语法可以在多种地方引进接缝.

  • 闭包属性
  • 闭包参数
  • 因为语法并没有大范围的改动,重构比较简单
  • 因为函数是闭包的,你可以在任何想要的地方抽取想要的闭包。为什么要在一行里做所有事呢?

我并不想滥用闭包。选择是,总会有一个合适的抽象层级等待被发现然后使用于策略模式。但是每一个缝隙都是一个提高可测试性的机会

我仍然只学了 Swift 的皮毛。我从 Joe Masilotti 的文章 Better Unit Testing with Swift 了解到协议提供了极大的机会。但是其它语言特性是如何影响可测试性的?比如说枚举或者泛型?跟着我来把它们探索清楚,测试驱动的 Swift,subscribe today !

你使用了哪些 Swift 的特性来提高可测试性?我应该探索哪些特性?可以在下面的评论中留言让我知道





原文发布时间为:2016年10月30日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
2月前
|
数据采集 机器学习/深度学习 大数据
行为检测代码(一):超详细介绍C3D架构训练+测试步骤
这篇文章详细介绍了C3D架构在行为检测领域的应用,包括训练和测试步骤,使用UCF101数据集进行演示。
81 1
行为检测代码(一):超详细介绍C3D架构训练+测试步骤
|
2月前
|
机器学习/深度学习 人工智能 监控
提升软件质量的关键路径:高效测试策略与实践在软件开发的宇宙中,每一行代码都如同星辰般璀璨,而将这些星辰编织成星系的过程,则依赖于严谨而高效的测试策略。本文将引领读者探索软件测试的奥秘,揭示如何通过精心设计的测试方案,不仅提升软件的性能与稳定性,还能加速产品上市的步伐,最终实现质量与效率的双重飞跃。
在软件工程的浩瀚星海中,测试不仅是发现缺陷的放大镜,更是保障软件质量的坚固防线。本文旨在探讨一种高效且创新的软件测试策略框架,它融合了传统方法的精髓与现代技术的突破,旨在为软件开发团队提供一套系统化、可执行性强的测试指引。我们将从测试规划的起点出发,沿着测试设计、执行、反馈再到持续优化的轨迹,逐步展开论述。每一步都强调实用性与前瞻性相结合,确保测试活动能够紧跟软件开发的步伐,及时适应变化,有效应对各种挑战。
|
3月前
|
Web App开发 JavaScript 前端开发
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
|
22小时前
|
算法 Java 测试技术
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
29 13
|
28天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
62 1
|
3月前
|
SQL JavaScript 前端开发
基于Python访问Hive的pytest测试代码实现
根据《用Java、Python来开发Hive应用》一文,建立了使用Python、来开发Hive应用的方法,产生的代码如下
83 6
基于Python访问Hive的pytest测试代码实现
|
3月前
|
Java C++
代码文件间重复性测试
本文介绍了如何使用代码相似性检测工具simian来找出代码文件中的重复行,并通过示例指令展示了如何将检测结果输出到指定的文本文件中。
|
3月前
|
测试技术 UED
软件测试的艺术:从代码到品质的探索之旅
在数字时代的浪潮中,软件已成为我们生活和工作不可或缺的一部分。然而,高质量的软件背后隐藏着一门鲜为人知的艺术——软件测试。本文将带你走进这门艺术的世界,从基础理论到实践应用,一起探索如何通过软件测试保障产品质量,提升用户体验,并最终实现从代码到品质的华丽转变。
|
3月前
|
敏捷开发 安全 测试技术
软件测试的艺术:从代码到用户体验的全方位解析
本文将深入探讨软件测试的重要性和实施策略,通过分析不同类型的测试方法和工具,展示如何有效地提升软件质量和用户满意度。我们将从单元测试、集成测试到性能测试等多个角度出发,详细解释每种测试方法的实施步骤和最佳实践。此外,文章还将讨论如何通过持续集成和自动化测试来优化测试流程,以及如何建立有效的测试团队来应对快速变化的市场需求。通过实际案例的分析,本文旨在为读者提供一套系统而实用的软件测试策略,帮助读者在软件开发过程中做出更明智的决策。
|
3月前
|
SQL JavaScript 前端开发
基于Java访问Hive的JUnit5测试代码实现
根据《用Java、Python来开发Hive应用》一文,建立了使用Java、来开发Hive应用的方法,产生的代码如下
82 6