Mik*_*e76 5 xcode integration-testing ios xctest xcuitest
来自 Android/Espresso 背景,我仍在为 iOS 的 XCUITest 和 UI 测试苦苦挣扎。我的问题是关于两个相关但不同的问题:
要解决这些问题,我们首先应该了解 XCode 的“单元测试目标”和“UI 测试目标”之间的区别。
XCUITests 在一个完全独立的进程中运行,不能跳转到被测应用程序的方法中。此外,默认情况下,XCUITests 不会链接到被测应用程序的任何来源。
相比之下,XCode 的单元测试与应用程序源相关联。还可以选择执行“@testable 导入”。理论上,这意味着单元测试可以跳转到任意应用程序代码。但是,单元测试不会针对实际应用运行。相反,单元测试针对没有任何 UI 的精简版 iOS SDK 运行。
现在有针对这些约束的不同解决方法:
将一些选定的源文件添加到 UI 测试目标。这不能调用应用程序,但至少可以在应用程序和 UI 测试之间共享选定的代码。
将启动参数CommandLine.arguments从 UI 测试传递到被测应用程序。这可以将特定于测试的配置应用于被测应用程序。但是,这些启动参数需要由应用程序解析和解释,这会导致应用程序被测试代码污染。此外,启动参数只是一种改变被测应用程序行为的非交互式方式。
实现一个只能由 XCUITest 访问的“调试 UI”。同样,这具有污染应用程序代码的缺点。
这引出了我的结论性问题:
有哪些替代方法可以使 XCUI 测试更强大/动态/灵活?
我可以针对整个应用程序源和所有 pod 依赖项编译和链接 UI 测试,而不仅仅是几个选定的文件吗?
是否有可能获得 Android 的仪器测试 + Espresso 的强大功能,我们可以在其中对被测应用程序执行任意状态修改?
为了回应@theMikeSwan,我想澄清我对 UI 测试架构的立场。
UI 测试不需要链接到应用程序代码,它们旨在模拟用户在您的应用程序内点击。如果您在这些测试期间跳入应用程序代码,您将不再测试您的应用程序在现实世界中的功能,您将测试它在以用户永远无法操作的方式进行操作时的功能。UI 测试不应该像用户一样需要任何应用程序代码。
我同意以这种方式操纵应用程序是一种反模式,应该只在极少数情况下使用。然而,我对什么应该是可能的有非常不同的立场。在我看来,UI 测试的正确方法不是黑盒测试,而是灰盒测试。尽管我们希望 UI 测试尽可能黑盒化,但在某些情况下,我们希望深入挖掘被测应用程序的实现细节。只是给你举几个例子:
可扩展性:没有任何 UI 测试框架可以为每个用例提供 API。项目要求不同,有时我们想编写自己的函数来修改应用程序状态。
内部状态断言:我希望能够为应用程序的状态编写自定义断言(不仅依赖 UI 的断言)。在我当前的 Android 项目中,我们有一个众所周知的坏掉的子系统。我使用自定义方法断言了这个子系统,以防止回归错误。
共享模拟对象:在我当前的 Android 项目中,我们有自定义硬件,无法用于 UI 测试。我们用模拟对象替换了这个硬件。我们直接在 UI 测试中对这些模拟对象运行断言。这些断言通过共享内存无缝工作。此外,我不想用所有模拟实现来污染应用程序代码。
将测试数据保留在外部:在我当前的 Android 项目中,我们将测试数据从 JUnit 直接加载到应用程序中。使用 XCUITest 的命令行参数,这将受到更多限制。
自定义同步机制:在我当前的 Android 项目中,我们有围绕多线程基础设施的包装类来同步我们的 UI 测试和后台任务。如果没有共享内存(例如 Espresso IdlingResources),这种同步很难实现。
简单的代码共享:在我当前的 iOS 项目中,我为上述启动参数共享了一个简单的定义文件。这允许以类型安全的方式传递启动参数,而无需复制字符串文字。尽管这是一个次要用例,但它仍然表明选定的代码共享可能很有价值。
对于 UI 测试,您不应该过多地污染您的应用程序代码。您可以使用单个命令行参数来指示 UI 测试正在运行,并使用它来加载一些测试数据、登录测试用户或选择网络调用的测试端点。有了良好的架构,您只需要在应用程序首次启动时进行一次调整,而您的其余代码不知道它正在使用测试数据(就像您有一个开发环境和一个生产环境,您可以在网络调用之间切换)。
这正是我在当前 iOS 项目中所做的事情,而这正是我想要避免的事情。一个好的架构虽然可以避免太多的破坏,但仍然是对app代码的污染。此外,这并不能解决我上面强调的任何用例。通过提出这样的解决方案,您基本上承认激进的黑盒测试不如灰盒测试。正如在生活的许多方面一样,差异化的观点比激进的“只使用我们给你的工具,你不需要这样做”要好。
UI 测试不需要链接到应用程序代码,它们旨在模拟用户在您的应用程序内点击。如果您在这些测试期间跳入应用程序代码,您将不再测试您的应用程序在现实世界中的功能,您将测试它在以用户永远无法操作的方式进行操作时的功能。UI 测试不应该像用户一样需要任何应用程序代码。
当然,对于单元测试和集成测试,您@testable import …可以访问任何未标记为private或 的方法和属性fileprivate。任何标记private或fileprivate仍然无法从测试代码访问的内容,但包括其他所有内容在内的内容internal都可以访问。在这些测试中,您应该有意地将现实世界中不可能发生的数据放入其中,以确保您的代码可以处理它。这些测试仍然不应该触及方法并进行任何更改,否则测试将不会真正测试代码的行为方式。
您可以在一个项目中根据需要创建任意数量的单元测试目标,并且您可以使用这些目标中的一个或多个来保存集成测试而不是单元测试。然后,您可以指定在不同时间运行的目标,这样较慢的集成测试就不会在每次测试时都运行并减慢速度。
运行的环境单元和集成测试实际上拥有一切。您可以创建视图控制器的实例并调用loadViewIfNeeded()以设置整个视图。然后,您可以测试各种插座的存在并触发它们发送动作(Check outUIControl的sendActions(for: )方法)。如果你已经设置了必要的模拟,这将让你验证当用户点击按钮 A 时,调用被发送到 B 的正确方法。
对于 UI 测试,您不应该过多地污染您的应用程序代码。您可以使用单个命令行参数来指示 UI 测试正在运行,并使用它来加载一些测试数据、登录测试用户或选择网络调用的测试端点。有了良好的架构,您只需在应用程序首次启动时进行一次调整,而您的其余代码不知道它正在使用测试数据(就像您有一个开发环境和一个生产环境,您可以在网络调用之间切换)。
如果您想了解有关测试 Swift 的更多信息 Paul Hudson 有一本非常好的书,您可以查看https://www.hackingwithswift.com/store/testing-swift。它有大量关于各种测试的示例以及关于如何拆分它们的好建议。
根据您的编辑和评论进行更新: 看起来您真正想要的是集成测试。这些在 Xcode 的世界中很容易错过,因为它们没有自己的目标来创建。他们使用单元测试目标,但测试多个协同工作的事物。
如果您尚未添加private或添加fileprivate到任何插座,您可以在单元测试目标中创建测试,以确保插座存在,然后根据需要注入文本或触发它们的操作以模拟用户浏览您的应用程序。
通常,这种测试只会从一个视图控制器转到第二个视图控制器,以测试在某个动作发生时是否创建了正确的视图控制器,但没有任何内容表明它不能更进一步。
您不会像使用 UI 测试那样获得失败测试的屏幕图像,并且如果您使用故事板,请确保从故事板实例化您的视图控制器。确保您正在抓取任何导航控制器以及所需的此类控制器。
这种方法将使您像在浏览应用程序一样行事,同时能够在进入各种方法时操作您需要的任何数据。
如果您有一个包含 10 行的方法,并且您想调整第 7 行和第 8 行之间的数据,则需要对可模拟的内容进行外部调用并在那里进行更改,或者使用带有调试器命令的断点进行更改. 这个断点技巧对于调试非常有用,但我认为我不会将它用于测试,因为删除断点会破坏测试。
| 归档时间: |
|
| 查看次数: |
1995 次 |
| 最近记录: |