如何在Swift中编写单元测试而不公开所有内容。

每当我参加讨论单元测试主题的会议演讲时,我都会听到两个问题。 1.我如何说服老板给我时间写这些书? (您没有。您只需编写它们。)和2.单元测试破坏了封装。 你是在告诉我我应该公开一切吗?

我在iOS / Swift的世界中工作,那里有解决特定问题的工具,但总体感觉是,我们必须破解向团队其他成员开放的所有功能,才能进入那里并测试一些变量当他们应该被设置时就被设置。

因此,在进一步详细介绍之前,请先进行警告。

本文的目的是激发话题,而不是倡导任何特定观点。 您采用的任何方法都有其优点和缺点,并且在继续进行计划之前,获取尽可能多的信息总是明智的。

自动化测试虽然不错,但是永远不会修复架构不良的应用程序。 我也不声称他们曾经做到过。 从坚实的基础开始建设总是比将绷带应用于需要推倒的东西总是更好。

ew 感觉很好。 好的,有点自以为是!

这到底是什么问题?

当我们在团队中工作时,每个开发人员都定义了一个API,团队中的其他成员在使用他们的代码时都可以访问。 重要的是,我们要给我们的团队成员一些容易理解的东西,否则他们可能会对我们的代码感到困惑。

如果一个类具有太多面向公众的功能,而另一个开发人员需要相当频繁地使用它,则可能会造成混乱。 与创建具有太多菜单选项的应用程序没什么不同。 用户不容易确定哪个选项是正确的选择。

但是实际上,这比那更糟。 其中一些选项可能会对应用程序致命。 有些变量和函数不应该在类之外使用,因为这样做可能会引入意外的行为。

这就是为什么我们每个人都决定为我们的同伴程序员定义一个api。 我们说“这是公共的,您其他开发人员可以在不破坏应用程序的情况下使用它”,“这是私有的。 没有人可以触摸它。”

这是很长时间以来的样子,并且确实运行良好。 这是传达意图的明确方法。

单元测试与私有方法

不幸的是,这种方法不适用于单元测试。 原因很简单。 在iOS中,您不能针对声明为私有的任何内容编写测试。

等等什么 您应该如何进入那里并测试所有内容? 您应该测试所有内容吗? 您应该只测试几件事吗? 你应该测试什么? 你不应该测试什么? 这变得令人困惑。

似乎没有关于它的手册,而且不同小组之间来回往来很多。

有素食主义者说,单元测试就像在吃蔬菜一样,看着自己变得不健康。 然后是另一个团队放弃了,因为似乎需要考虑太多,而他们的老板也不允许他们编写测试。

我被困在这里问,又是什么问题?

它必须是如此二进制吗?

在这里,我主张“仅仅公开一切”并不是一件坏事。因此,在这里,你也让我死死盯着我的建议的荒谬之处。

好的,那部分结束了。 不管你怎么想,我确实有一个问题。

您是否认为公共/私有的重要性取决于其他外部因素,例如团队规模,项目复杂性,代码可重用性等?

当然,BigCo的一个应用程序项目需要数百名团队成员研究相同的代码,而SmallCo的项目可能只有一两个人正在查看相同的代码。 它必须是。

在较大的公司中,不同人群之间的交流要少得多。 由于无法快速召集您讨论如何与您的班级一起工作,因此定义什么是私有的和什么不是私有的更为重要。

但是,在较小的公司中,只要有一点混乱,您就可以与该人配对。 你们两个可以在一个下午完成工作。 没问题。

现在,我并不是真的在说“去公开一切”。我说的是上下文。 这取决于谁将访问什么。

值得庆幸的是,有一个更好的解决方案。 尽管确实需要更多的工作和重构,但是您也可以进行单元测试和私有方法!

要了解这是如何实现的,我们需要更好地了解Xcode如何处理访问控制。

公开,内部,私人和单元测试

在iOS世界中,我们有三个主要的访问控制级别。 (是的,我知道,他们在Swift3中引入了更多内容)。 有私有事物只能由拥有它们的对象访问,公共事物可以由任何文件访问,而内部事物只能在定义它们的模块中访问。

Xcode默认为内部访问级别。 创建应用程序项目时,Xcode为其定义一个模块。 您创建的任何函数,类,协议等,都只能在您的应用模块中访问,至少在您决定另行声明之前。 您可以通过将事物标记为public,private,fileprivate,open等来实现。

因此,只需编写“ func doSomething()”,即可隐式声明“ doSomething”函数具有内部访问级别,从而可以对其定义的模块内部的任何内容进行访问。对于大多数项目,该模块是您应用程序目标的模块,也就是您要运送的实际产品。

如果您想知道“ @testable import”语句在单元测试中使用时的作用,它基本上是导入模块并允许您针对其内部实体编写单元测试。

@testable import 不允许您针对模块中声明的任何私有实体编写测试。 没有什么可以阻止那些从外面打开的东西。 他们是私人私人。

因此,看起来“私人”已经有了一些不同的含义。 在Xcode中,事物(函数,类,结构等)可以通过两种不同的方式变为私有。

  1. 通过您明确声明该内容为私有
  2. 通过将事物放置在声明事物内部的框架中。 当您在另一个模块中使用框架时, 由于在定义它的模块之外进行访问事物变得私有。

你知道我要去哪里吗? 使用单元测试或保留封装的决定并不像您想象的那样二进制。

我们如何才能使事物私有化并针对它们编写单元测试?

这样的事情有可能吗? 它的确是! 您可以通过以下方式做到这一点:将您的私有内容放入框架中,将私有内容保留在该框架的内部,然后将该框架“ @testable导入”到测试该框架内部内容的单元测试目标中。

当您需要使用在框架中定义的内容时(就像在实际应用中一样),只需将框架导入应用模块即可。 由于您将“私有”功能保留在框架内部,因此导入框架的模块(即您的应用程序模块)将无法使用它们。

您在这里可以两全其美。 您可以使用面向公共和私有变量的显式定义api,以及可以破解内部事物并测试每个小细节的单元测试套件。

现在值得付出所有这些额外的努力吗? 这取决于您的项目时间表,与谁一起工作以及各种外部因素。

就目前而言, 在您针对私有内容编写测试之前,将私有内容声明为私有似乎是安全的。 然后,您可以将实体设置为内部实体,也可以将整个实体移入框架,并针对它作为框架本身的内部实体编写测试。

无论哪种方式,我相信我已经证明了单元测试不一定会破坏封装。 这需要更多的工作,但是您可以在这里获得两全其美。

如果是私人的…

我认为苹果有理由将每个实体默认设置为内部实体。 它促使您以这种方式思考。 尽管私有确实占有一席之地,但是当您可以将代码移至框架并仍然可以通过将私有内容保留在内部而对其进行测试时,它似乎有些过时。

最后,我将为所有经过测试的单元提供一些建议。 如果确实需要私有,那么您应该在上面放一个框架。

目前为止就这样了。 我100%肯定回应会充满冷静而理性的言论,尊重我们每个不同的观点。