使用Swift协议提高代码可测试性

作为开发人员,我们最大的挑战之一是实现高代码可测试性。 这些测试对于确保您开发的代码可以正常工作,并且在添加新功能时没有任何损坏方面非常有用。 另外,当您在团队中工作时,会有很多人在修改项目。 因此,确保代码的完整性也很重要。

有很多测试,但是它们不应该是有问题的或复杂的。 那么,为什么没有很多开发人员这样做呢? 主要的原因是时间不足。 我相信,最大的问题之一是我们的代码在层,类和具有外部框架的依赖关系之间耦合太深。

我想证明,创建框架的抽象层或将类解耦不是一件容易的事。


想象一下,我们需要开发一个应用程序,该应用程序需要知道用户的位置,因此,我们需要使用CoreLocation

我们的ViewController可能看起来像这样:

https://gist.github.com/Juanpe/c4b0af6ac096a608e8737aba45c5345c

它有一个locationManager作为CLLocationManager ,以请求用户的位置或请求授权(如果适用)。 它还符合CLLocationManagerDelegate协议,并在其中接收locationManager输出。

在这里,我们可以看到ViewControllerCoreLocation以及其他与职责分离相关的问题结合在一起。

无论如何,让我们为ViewController创建测试。 这可能是一个很好的例子:

https://gist.github.com/Juanpe/0a332b315701dea350deab410db0c03d

我们可以看到sut被测系统)和可能的测试之一。 在这里,我们请求用户的位置,并将其存储在我们的本地变量( userLocation )中。

这里的问题开始出现…… CLLocationManager管理请求,它不是一个同步过程,因此当我们检查存储的位置仍然为nil时 。 同样,我们可能没有授权请求位置的权限,在这种情况下,位置也将为nil

现在,我们有一些可能的解决方案! 让我们在不测试与位置相关的任何东西的情况下测试ViewController ,创建CLLocationManager的子类并模拟方法,或者尝试正确地进行操作并将CLLocationManager与我们的类分离。 我选了最后一个

“ Swift设计的核心是两个令人难以置信的强大想法:面向协议的编程和一流的值语义” – Apple

POP是开发人员的强大工具,而Swift无疑是一种面向协议的语言。 所以我的建议是使用协议解决这些依赖性。


首先,要抽象CLLocation ,我们将定义仅包含代码所需变量或函数的协议。

https://gist.github.com/Juanpe/e71b220547eeda4318ef8b614a90f52f

现在,我们可以获得一个没有CoreLocation的位置 因此,如果我们分析ViewController ,我们可以看到我们实际上并不需要CLLocationManager ,而只有在我们请求时向我们提供用户位置的人才可以。 因此,我们将创建一个包含我们需求的协议,只要符合该协议的人都可以作为提供者。

https://gist.github.com/Juanpe/ec84b3b26227d87f9759133914fb80b1

在我们的例子中,我们创建了UserLocationProvider。 该协议指定我们只需要一个方法来请求用户的位置,结果将通过我们提供的回调。

我们准备创建一个UserLocationService ,它符合该协议并向我们提供位置。 通过这种方式,我们已经在类中解决了CoreLocation的依赖关系,但是请稍候…… UserLocationService需要通过CLLocationManager请求位置……因此,看来问题尚未解决😅

再次进行救援的协议,只需创建一个新协议即可为我们指定什么是位置提供者:

https://gist.github.com/Juanpe/502a8d0459ec9c9d97cb5f0a60464330

我们扩展了CLLocationManager功能,以符合我们的新协议。

现在是的,我们准备创建UserLocationService🎉 ,它看起来像这样:

https://gist.github.com/Juanpe/bc99fee4dc43478c7ae1e64979f3bbe1

UserLocationService有他的位置提供者,但是他不知道自己是谁,对他来说没关系,他只在需要时才需要用户的位置,其余的都不由他负责。

需要扩展以符合CLLocationManagerDelegate协议,因为我们将使用CoreLocation。 但是我们在测试中将如何看待,我们实际上并不需要它来验证我们的类工作正常。

我们可以在协议中添加任何类型的委托,但是对于这个示例来说,太多了​​,我认为

在开始测试之前,我们先来看一下使用UserLocationProvider而不是CLLocationManager的 ViewController的外观。

https://gist.github.com/Juanpe/65fc919e7cc145bfabee9e9a390df6e8

查看这段代码,我们得出的结论是,现在,我们的ViewController具有更少的代码,更少的责任和更多的可测试性。

让我们进行测试。 首先,我们将创建一些需要测试ViewController的模拟类。

https://gist.github.com/Juanpe/3a621ff58f63bb3133ca2f9d4ea31317

使用这些模拟,我们可以注入所需的任何结果,我们将模拟UserLocationProvider的工作方式。 因此,我们将焦点放在真正的目标ViewController上

https://gist.github.com/Juanpe/717feef9ca2bf5dbdd8db62ca9c32e71

我们创建了两个测试,一个测试检查我们是否无权请求该位置,提供者不提供任何内容。 而另一种情况则相反,如果我们被授权,我们应该获得用户的位置。 如您所见,测试已通过!! 💪

除了ViewController之外 ,我们还创建了一个附加类UserLocationService ,因此我们也应该介绍他。

需要模拟LocationProvider ,因为它不是此测试的目标。

https://gist.github.com/Juanpe/43555755203603a3c27bdbbfde486cc3

可以创建许多测试,但是请验证提供者是否说我们有授权(如果没有要求,否则我们要求位置)可以是其中之一。

https://gist.github.com/Juanpe/25b39fb3559c00cb9669d0373de60fad

您可以想象,有许多方法可以使代码解耦,而本出版物只是其中之一。 但是我认为这可能是一个很好的例子,表明测试并不是一项艰巨的任务。

如果您记得顶部的图像,您会看到乐高积木,这就是为什么我认为它们很好地解释了什么是解耦和抽象您的组件。 最后,它被定义为一种特定的连接方式,但是颜色并不重要。

也许最懒的任务之一就是创建模拟,但是为此,已经有库和工具来简化这项工作,例如Sourcery 。 另外,这是我的同事Hugo Peral的文章,解释了如何使用Sourcery节省测试时间。 或是John Sundell撰写的这篇文章,它提供了有关如何制作模拟游戏的更多细节。

没什么,感谢您阅读本出版物。 如果对您有用,或者您认为对某人有用,请分享it。 如果您有任何疑问或任何改进建议,请在下面发表评论。