uitest
Let’s continue with what we started last week. In the previous article, we discussed how to setup UITests and how to assert the state of the UI to ensure that it’s behaving as we expect.
让我们继续上周开始的工作。 在上一篇文章中,我们讨论了如何设置UITest以及如何声明UI状态以确保其行为符合我们的预期。
We know that good tests must be:
我们知道好的测试必须是:
- Independent from each other 彼此独立
- Repeatable 可重复的
- Must run as fast as possible. 必须尽可能快地运行。
UITests make it hard to achieve these properties: in fact, a UITest runs in a separate process with respect to the app and it acts as a user that interacts with the app. Therefore, every time a UITest runs, it actually relaunches the app under test: if the app has saved something on the disk in the previous execution, then we will see those data in the current execution. Thus, the tests are not independent anymore, nor they are repeatable.
UITest使实现这些属性变得很困难:实际上,UITest在相对于应用程序的单独过程中运行,并且它充当与应用程序进行交互的用户。 因此,每次UITest运行时,它实际上都会重新启动被测试的应用程序:如果该应用程序在上一次执行中已将某些内容保存在磁盘上,那么我们将在当前执行中看到这些数据。 因此,测试不再是独立的 , 也不是可重复的 。
If the tests have to assert something into a very nested ViewController
, the tests must perform a lot of navigation, thus they will run very slowly.
如果测试必须在非常嵌套的ViewController
声明某些内容,则测试必须执行大量导航,因此它们将运行得非常慢 。
In this article, I’d like to share some tips and tricks that will make your tests more robust, more repeatable, cleaner, and faster. These tips and tricks leverage:
在本文中,我想分享一些技巧和窍门,这些技巧将使您的测试更强大,更可重复,更清洁,更快。 这些提示和技巧可以利用:
Typed identifiers instead of raw strings to access the
XCUIElements
.输入标识符而不是原始字符串来访问
XCUIElements
。- Conditional compilation to exclude part of the code that makes sense only when running UITests and control the execution flow. 条件编译可排除仅在运行UITest并控制执行流程时才有意义的部分代码。
- Launch arguments to pass some information to control the app behavior from the tests. 启动参数以传递一些信息以控制测试中的应用行为。
Like last week, you can find the code here.
与上周一样,您可以在此处找到代码。
1.使用类型标识符 (1. Use Typed Identifiers)
In the previous article, we saw that it’s possible to use accessibilityIdentifiers
to access to XCUIElement
s in a safe way, even when their static text change:
在上一篇文章中,我们看到可以使用accessibilityIdentifiers
以安全的方式访问XCUIElement
,即使它们的静态文本发生了变化:
This certainly works. However, what happens if someone decides that the identifier convention must be changed? Or if she updates the "tos_button"
to be something like "terms_of_services_button"
?
当然可以。 但是,如果有人决定必须更改标识符约定,会发生什么? 或者,如果她将"tos_button"
更新为类似于"terms_of_services_button"
?
In that case, the tests will break even if they shouldn’t. This approach is also very error-prone because who is writing the tests can always make a typo in one of the two places and such bugs are huge time sinks.
在这种情况下,即使不进行测试,测试也会失败。 这种方法也很容易出错,因为编写测试的人总是可以在两个地方之一中打错字,而这样的错误会占用大量的时间。
A more solid approach is to use enums, shared between the UITests target and the app target. In this way, we can define the identifiers only once and use them in two places.
一种更可靠的方法是使用在UITests目标和app目标之间共享的枚举。 这样,我们只能定义一次标识符,并在两个地方使用它们。
This can not be done just by importing the app module into the UITest one. That’s because those are two different targets which run in different processes. Xcode may build with the import, but it will crash at runtime because the UITests can not find the right identifiers. So, to make it work, we need to perform 2 steps:
仅仅通过将应用模块导入UITest并不能做到这一点。 这是因为这些是在不同流程中运行的两个不同目标。 Xcode可能会随导入一起生成,但由于UITests找不到正确的标识符,它会在运行时崩溃。 因此,要使其工作,我们需要执行两个步骤:
Update the code with the new extension, where we define the
enum
.使用定义了
enum
的新扩展名更新代码。- Add the file with the identifier to both targets. 将带有标识符的文件添加到两个目标。
添加扩展 (Add the Extension)
To add the extension we can just refactor the old LegalView
code, with this new code:
要添加扩展名,我们可以使用以下新代码重构旧的LegalView
代码:
and then use the AccessibilityIdentifiers
in the UITests:
然后在UITest中使用AccessibilityIdentifiers
:
If we build our code at this point, it will compile. But, as explained above, if we run it, it will fail badly. Let’s fix this crash.
如果我们此时构建代码,它将进行编译。 但是,如上所述,如果我们运行它,它将严重失败。 让我们修复此崩溃。
将查看文件添加到两个目标 (Add the View file to both targets)
To solve this issue, we have to add the files where the extensions are declared to both the targets. To do so we need to:
要解决此问题,我们必须将声明扩展名的文件添加到两个目标。 为此,我们需要:
- Open the right pane of Xcode 打开Xcode的右窗格
Open the
File Inspector
打开
File Inspector
Update the
Target Membership
of theLegalView
file as the image shows.如图所示,更新
LegalView
文件的Target Membership
。
By checking the last checkbox, we instruct Xcode to add the LegalView
file to both the target. Now, our UITests have access to theAccessibilityIdentifiers
!
通过选中最后一个复选框,我们指示Xcode将LegalView
文件添加到两个目标中。 现在,我们的UITests可以访问AccessibilityIdentifiers
!
2.不提供UITest代码 (2. Do Not Ship UITest Code)
If your app supports Accessibility
, fine. The code we wrote until now hits two pigeons with a single stone. However, chances are that you are not supporting it and, anyway, we will soon write some code in the app that makes sense only in the context of UITests. We don’t want that code to go in production.
如果您的应用程序支持Accessibility
,则很好。 到目前为止,我们编写的代码仅用一块石头就可以击中两只鸽子。 但是,很可能您不支持它,无论如何,我们很快就会在应用程序中编写一些仅在UITests上下文中才有意义的代码。 我们不希望该代码投入生产。
This can be easily done by using compilation pragmas. I think every one of you had written something like #if DEBUG <custom_code> #endif
. The <custom_code>
is something that the compiler builds and inject in the binary if and only if the DEBUG
flag is set to 1
.
这可以通过使用编译实用程序轻松完成。 我想你们每个人都写过#if DEBUG <custom_code> #endif
。 <custom_code>
是编译器将生成的东西, 并且仅当 DEBUG
标志设置为1
,才将其注入二进制文件。
So, let’s create a custom UITESTING
flag that we can use to tell the compiler which portions of code make sense only in the context of UI testing. Do to so we need to:
因此,让我们创建一个自定义的UITESTING
标志,我们可以使用该标志来告诉编译器哪些代码部分仅在UI测试的上下文中才有意义。 为此,我们需要:
Create a new
UITesting
configuration;创建一个新的
UITesting
配置;Crete the new flags in both the
UITesting
target and in theApp
target;在
UITesting
目标和App
目标中UITesting
新标志;- Inform the compiler that we have to use them; 通知编译器我们必须使用它们;
- Edit the scheme so that we use the new configuration when we run our tests; 编辑方案,以便在运行测试时使用新配置;
- Use them in the code. 在代码中使用它们。
创建一个新配置 (Create a New Configuration)
The first step is really easy.
第一步真的很容易。
In the
Project Navigator
select the Root project, the one with the Xcode icon.在
Project Navigator
选择Root项目,该项目带有Xcode图标。Select the
Project
and theInfo
tab选择
Project
和Info
选项卡Under
Configurations
, press the+
button and choose to duplicate theDebug
configuration在“
Configurations
,按+
按钮,然后选择复制“Debug
配置Insert the
UITesting
as name.插入
UITesting
作为名称。
创建UITesting
标志并激活它们。 (Create the UITesting
Flags and Activate Them.)
We need to create the flags we are going to use and we have to activate them. Luckily, we are already in the right window of Xcode, so we can just follow these steps:
我们需要创建将要使用的标志,并且必须激活它们。 幸运的是,我们已经在Xcode的右侧窗口中,因此我们可以按照以下步骤操作:
Click on your
App
Target (UITesting
in the example).单击您的
App
目标(在示例中为UITesting
)。Select the
Build Settings
tab.选择
Build Settings
选项卡。Filter for
Preprocessor Macros
.Preprocessor Macros
过滤器。Add the
UITESTING=1
flag for theUITesting
configuration.为
UITesting
配置添加UITESTING=1
标志。Filter for
Active Compilation Conditions
.Active Compilation Conditions
过滤器。Add the
UITESTING
flag in theUITesting
configuration. This activates the flag when the app is running in that configuration.在
UITesting
配置中添加UITESTING
标志。 当应用程序在该配置中运行时,这将激活该标志。Repeat the steps 1 to 6 for the
UITest
Target (UITestingUITest
in the example).对
UITest
目标(UITestingUITest
中的UITestingUITest
)重复步骤1至6。
At the end of the steps, if you filter for DEBUG
in one of the two targets we modified, you should have a situation similar to this one.
在步骤的最后,如果您在我们修改的两个目标之一中过滤DEBUG
,那么您应该会遇到与此类似的情况。
更新方案以使用新配置 (Update the Scheme To Use the New Configuration)
At this point, we need to update the scheme to inform Xcode that we want to use the new configuration when we run the tests. To do so we need to:
此时,我们需要更新方案,以在运行测试时通知Xcode我们要使用新配置。 为此,我们需要:
Click on the
App
next to thePlay
andStop
buttons in Xcode单击Xcode中“
Play
和“Stop
按钮旁边的App
Select
Edit Scheme
选择
Edit Scheme
Click on
Test
点击
Test
Select the
UITesting
configuration选择
UITesting
配置
From now on, whenever we press cmd+U
we are going to use the UITesting
configuration which is aware of the new UITESTING
compiler flag. Now we just need to…
从现在开始,每当我们按cmd+U
我们将使用UITesting
配置,该配置知道新的UITESTING
编译器标志。 现在我们只需要...
使用标记中的代码 (Use the Flag In Code)
To use it, we leverage the same syntax of #if DEBUG <custom_code> #endif
. We need to navigate to the LegalView
file and update the code as follows:
要使用它,我们利用与#if DEBUG <custom_code> #endif
相同的语法。 我们需要导航到LegalView
文件并更新代码,如下所示:
With this code, everything that is between #if UITESTING
and #endif
is not included in the binary unless the UITESTING
flag is set to 1
and that’s true only when we run UITests.
使用此代码,除非UITESTING
标志设置为1
,否则#if UITESTING
和#endif
之间的所有内容都不会包含在二进制文件中,并且仅当我们运行UITests时才如此。
3.使UITest与应用程序通信 (3. Make UITest Communicates With the App)
As already stated above, UITests run in a different process with respect to the app they interact with. So… how can we control the state of the app so that our tests are predictable and repeatable?
如上所述,UITest在与之交互的应用程序上以不同的过程运行。 那么……我们如何控制应用程序的状态,以便我们的测试是可预测的和可重复的?
XCUIApplication
allow us to pass some launch arguments from the UITest to the application. Once those arguments are received by the app, we can then handle them appropriately. To do so we need to:
XCUIApplication
允许我们将一些启动参数从UITest传递到应用程序。 应用收到这些参数后,我们就可以对其进行适当处理。 为此,我们需要:
Set the arguments we need before calling
XCUIApplication.launch
在调用
XCUIApplication.launch
之前设置我们需要的参数- Parse the arguments in the app. 解析应用程序中的参数。
- Implement the logic to react to these arguments. 实现对这些参数做出React的逻辑。
To illustrate this process, consider that we want to create a test that starts the app directly in the HomeView
instead of launching it from LegalView
and then navigate to the HomeView
.
为了说明此过程,请考虑我们要创建一个直接在HomeView
中启动该应用程序的测试,而不是从LegalView
中启动它,然后导航至HomeView
。
传递启动参数 (Pass Launch Arguments)
To pass launch arguments, simply set them in the app
before invoking the launch method.
要传递启动参数,只需在调用启动方法之前在app
进行设置即可。
解析应用程序中的参数并处理代码 (Parse the Arguments in the App and Handle the Code)
Now, we need to parse them. If you ever wrote a CLI program, you know how tedious this code can be. Instead, I’d like to thank John Sundell for this nice little trick: we can use UserDefaults
to parse the arguments, leveraging also the Swift type system!
现在,我们需要解析它们。 如果您曾经编写过CLI程序,您就会知道此代码可能很繁琐。 相反,我要感谢John Sundell的这个小技巧:我们可以使用UserDefaults
来解析参数,还可以利用Swift类型系统!
In this code, you can see a couple of things:
在这段代码中,您可以看到几件事:
we keep our functions small and simple. The
scene(_:willConnectTo:options:)
calls a function calledbuildViewController()
that perform the#if UITESTING
check and creates the rightUIViewController
based on the argument.我们保持功能小而简单。
scene(_:willConnectTo:options:)
调用一个名为buildViewController()
的函数,该函数执行#if UITESTING
检查并根据该参数创建正确的UIViewController
。In the app, we access the launch argument without using the
-
symbol. That symbol is a convention that tellsUserDefaults
that what follows the-
is thekey
and the next argument is the value for that key. In fact, when we access theUserDefaults.standard.string(forKey: "initialScreen")
UserDefaults
returns the"home_view"
value.在应用程序中,我们无需使用
-
符号即可访问启动参数。 这符号是告诉约定UserDefaults
什么如下-
是key
,下一个参数是该键的值。 实际上,当我们访问UserDefaults.standard.string(forKey: "initialScreen")
UserDefaults
返回"home_view"
值。
The code to handle the launch argument is pretty simple: we just have to switch on the argument value to present the right UIViewController
.
处理启动参数的代码非常简单:我们只需要打开参数值即可显示正确的UIViewController
。
Today we explored some more advanced topics in the field of UITests. We learn how to structure our code to be more robust, how we can avoid to compile and push in production testing code, and we learn how the UITests and the app can communicate.
今天,我们探讨了UITests领域中的一些更高级的主题。 我们将学习如何使代码结构更健壮,如何避免编译和推送生产测试代码,以及UITests和应用程序如何进行通信。
There are still some more topics about UITests that I like to discuss: how can we pass complex information to the app, how to clean the state so that the tests can be independent, how to mock dependencies so that the tests are reliable.
我还想讨论有关UITest的更多主题:如何将复杂信息传递给应用程序,如何清除状态以使测试可以独立,如何模拟依赖项以使测试可靠。
However, I’d like to keep these topics for the next week: I think that there is enough information to assimilate in today’s article.
但是,我想在下周保留这些主题:我认为今天的文章中有足够的信息可以吸收。
翻译自: https://uxdesign.cc/tips-and-tricks-for-uitests-b3b81356b641
uitest