iOS 中使用 KIF 测试 UI 已翻译 100%

isaced 投递于 2014/02/24 12:51 (共 22 段, 翻译完成于 03-14)
阅读 12973
收藏 91

Users expect a high level of polish from iOS apps, so it’s up to you to design, develop and test your apps to meet ever-rising expectations. Think about it for just a moment: How much time have you poured into conducting basic manual user interface testing? You know the drill…launching your app from Xcode, and incessantly tapping the same series of buttons to make sure no regressions slipped into your design. Surely, there are other things you’d rather do?

Instead, consider that the enhanced test UI in Xcode 5 and continuous integration support in OS X Server demonstrate Apple’s commitment to provide developers the best tools. That’s great, you might say, but how do you automate testing simple user actions, like ensuring a double-tap or swipe at the right spot brings up the correct view? Even test scripts and bots don’t have capacitive touch fingers to swipe across the screen…or…do they?

In this tutorial, you’ll learn all about KIF (“Keep it Functional”), an open-source user interface testing framework. With KIF, and by leveraging the Accessibility APIs in iOS, you’ll be able to write tests that simulate user input, like touches, swipes and text entry. These tests give your apps an automated, real-world user interface workout, and help keep your mind at ease so you can just focus on developing that killer app – not spending half a lifetime on UI testing.

Let’s get testing!

已有 1 人翻译此段

Getting Started

The sample project is a timer app called Solanum (named after the tomato plant species) based on the Pomodoro time-boxing method. Here’s how it works: you work for a defined number of minutes, take a break, then repeat. After several of these cycles, you take a longer break. The app is a just a simple timer that keeps track of time periods. Feel free to use the app afterwards to help make you more productive during your own development!

Main Timer screen

Download and unzip the starter project archive here. Note that KIF is a separate project, and its role is to build a library for Solanum’s test target. You’ll need to double-click solanum.xcworkspace to open the project in Xcode rather than solanum.xcodeproj. Look for two projects to open in the in the project navigator, it should like the example below.

Workspace with two projects

已有 1 人翻译此段

Set the app target to solanum, and select either the 3.5-inch or 4-inch iPhone Simulator target. Don’t use the 64-bit build; at the time of writing this tutorial KIF didn’t appear to be fully compatible. Build and run the app. Look around, then switch to the Settings tab.

The Settings screen

The app has a debug mode that accelerates time, so you can set a 20-minute timer and it will help with testing by ticking by in 10 seconds. This is just to aid testing the app. You wouldn’t want each test run to take 20 minutes!

Turn on Debug Mode switch to speed up the timer. Next, tap the Clear History button, and then tap Clear on the confirmation alert view. These steps will ensure you’re starting out in a clean, test-friendly environment. Return to Xcode and stop the app.

已有 1 人翻译此段

Pre-Test Actions

In the Project Navigator, expand the solanum project. Right-click the UI Tests folder and click New File… to add your first test case.

New File in UI Tests

Select iOS\Cocoa Touch\Objective-C class and click Next. Name the class UITests and make it a subclass of KIFTestCase.

A KIFTestCase called UITests

Click Next and make sure the files are be added to the UI Tests target, not the solanum target. Finally, click Create on the following screen to save the files.

KIFTestCase is a subclass of SenTestCase. That means you have most of the standard OCUnit testing methods and mechanisms available, in case you’re already familiar with unit testing.

Open UITests.m and add the following method after the @implementation line:

- (void)beforeAll {
  [tester tapViewWithAccessibilityLabel:@"Settings"];
   [tester setOn:YES forSwitchWithAccessibilityLabel:@"Debug Mode"];
   [tester tapViewWithAccessibilityLabel:@"Clear History"];  [tester tapViewWithAccessibilityLabel:@"Clear"];}

beforeAll is a special method that is called exactly once, before all of the tests run. You can set up any instance variables or initial conditions for the rest of your tests here.

The tester object is a special shortcut to an instance of the KIFUITestActor class. That class includes the methods that will simulate user activity, including tapping and swiping on views.

已有 1 人翻译此段

tapViewWithAccessibilityLabel: might be the most common test action method. As the name suggests, it simulates tapping the view with the given accessibility label. In most cases, such as for buttons, the accessibility label matches the visible text label. If not, as you’ll see in the next section, you’ll need to set the accessibility label manually.

Some controls, such as UISwitch, are more complicated and need more than just simple taps. KIF provides a specific setOn:forSwitchWithAccessibilityLabel: method to change the state of a switch.

In summary, this method has four steps for the test actor:

  • Tap the “Settings” tab bar button.

  • Set the “Debug Mode” switch to its “On” state.

  • Tap the “Clear History” button.

  • Tap the “Clear” button on the UIAlertView.

Do these steps seem familiar? They should be! They’re what you did manually in the previous section!

Run the tests by going to Product\Test or hitting Command-U on the keyboard. You should see the app launch; then you’ll see KIF take over, automatically enable debug mode and clear the history.

beforeAll running

If you have notifications enabled, Xcode will also tell you the test status:

Test Succeeded

Sometimes the test runner, or KIF, can get a little finicky and will refuse to run your tests, in which case you’ll just see a blank simulator screen. If this happens:

  • Clean the project (Product\Clean)

  • Build and run

  • Wait for the app to launch

  • Stop the app in Xcode

This process ensures the Simulator is running and that you’re working with the latest build. After going through the above steps, try running the tests again. The problems should be gone.

If you continue to have trouble, check out the KIF troubleshooting steps.

Now that you have a pre-test action in beforeAll, it’s time to add your first test!

已有 1 人翻译此段

A Simple Test: Tapping Around

The app has a standard tab bar controller layout with a UINavigationController inside each of the three tabs. To warm up for the next exercise, you’ll determine if:

  • The storyboard is wired up properly

  • The tabs display the correct views.

Tab bar buttons automatically set accessibility labels to be the same as their text label, so KIF will be able to find the History, Timer, and Settings tab bar buttons with the labels “History”, “Timer”, and “Settings”.

The History tab has a table view that shows all the tasks performed with the timer. Open HistoryViewController.m from the solanum group and add these lines to the end of viewDidLoad:

[self.tableView setAccessibilityLabel:@"History List"];
[self.tableView setIsAccessibilityElement:YES];

This will set the table view’s accessibility label, so that KIF can find it. Usually, a table view is only accessible if it’s empty. If there are table view cells they’re a more likely target, so the table view itself will hide from the accessibility API. Essentially, the accessibility API assumes, by default, that the table view isn’t important. This is likely the case in terms of accessibility, but if you want to reference the table view in KIF then it needs to be accessible as well. The setIsAccessibilityElement: call ensures the table view is always accessible, regardless of its contents.

已有 1 人翻译此段

Depending on the app, accessible non-empty table views can actually make things more difficult if for users who use the accessibility features (e.g. VoiceOver). In your own apps, you can wrap lines of code between #ifdef DEBUG and #endif directives so they’re only compiled into your debug builds. The DEBUG preprocessor macro is pre-defined in Xcode’s project templates.

The Timer tab has several controls you could look for, but the “Task Name” text field is conveniently at the top of the view. Rather than set the label through code, open Main.storyboard and find the Timer View Controller view. Select the task name text field.

Timer view in storyboard

Open the Utilities  panel if it isn’t already up — and select the Identity Inspector. Hint: it’s the third icon from the left, or use the keyboard shortcut ‘⌥ ⌘ 3′.

Utilities and Storyboard Inspector

Under Accessibility in the inspector, enter “Task Name” in the Label field. Stay sharp now, because accessibility labels are case-sensitive. Be sure to enter that exactly as shown with a capital T and N!

Storyboard accessibility

The Settings tab has already been set up the views with accessibility labels, so you’re all set to move to the next step!

已有 1 人翻译此段

In your own projects, you’ll need to continue to fill in the accessibility labels from code or in Interface Builder as you’ve done here. For your convenience, the rest of sample app’s accessibility labels are already set.

Back in UITests.m, add this method after beforeAll:

- (void)test00TabBarButtons {
  // 1
  [tester tapViewWithAccessibilityLabel:@"History"];
  [tester waitForViewWithAccessibilityLabel:@"History List"];
  // 2
  [tester tapViewWithAccessibilityLabel:@"Timer"];
  [tester waitForViewWithAccessibilityLabel:@"Task Name"];
  // 3
  [tester tapViewWithAccessibilityLabel:@"Settings"];
  [tester waitForViewWithAccessibilityLabel:@"Debug Mode"];

The test runner will look for all methods that start with the word “test” at runtime, and then run them in alphabetical order. This method starts with the name “test00″ so that it will run before the tests you’ll add later, because those names will start with “test10″, “test20″, etc.

Each of the three parts of the method will perform a similar set of actions: tap on a tab bar button, and check for the expected view to show on the screen. 10 seconds is the default timeout for waitForViewWithAccessibilityLabel:. If the view with the specified accessibility label doesn’t show itself during that timeframe, the test will fail.

Run the tests by going to Product\Test or hitting Command-U. You’ll see the beforeAll steps which will clear the History, and then test00TabBarButtons will take over and switch to the History, Timer and Settings tabs in sequence.


Well, what do you know? You just wrote and ran an interface test, and saw your little app “drive” itself!  Congrats! You’re on your way to mastering automated UI testing.

已有 1 人翻译此段

User Input

Sure, switching tabs is nifty, but it’s time to move on to more realistic actions: entering text, triggering modal dialogs and selecting a table view row.

The test app has some built-in presets that will change the work time, break time and number of repetitions to a set of recommended values. If you’re curious to see their definitions, have a look at presetItems in PresetsViewController.m.

Selecting a preset could be a test of its own, but that action is more efficient when it is a part of other tests. In this case, it’s worth splitting it out into a helper method.

Add the following method to the implementation block of UITests.m:

- (void)selectPresetAtIndex:(NSInteger)index {
  [tester tapViewWithAccessibilityLabel:@"Timer"];
  [tester tapViewWithAccessibilityLabel:@"Presets"];
  [tester tapRowInTableViewWithAccessibilityLabel:@"Presets List"
    atIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
  [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Presets List"];

The first step here is to switch to the Timer tab so that you’re in the right place. Then tap the Presets button in the navigation bar. When the “Presets List” table view appears, tap on the row at the specified index.

已有 1 人翻译此段

Tapping on the row will dismiss the view controller, so use waitForAbsenceOfViewWithAccessibilityLabel: to ensure it vanishes before you continue.

Did you notice that this method doesn’t start with the word test? The test runner won’t automatically run it. Instead, you’ll manually call the method  from within your own tests.

Now, add the following test method to UITests.m:

- (void)test10PresetTimer {
  // 1
  [tester tapViewWithAccessibilityLabel:@"Timer"];
  // 2
  [tester enterText:@"Set up a test" intoViewWithAccessibilityLabel:@"Task Name"];
  [tester tapViewWithAccessibilityLabel:@"done"];
  // 3
  [self selectPresetAtIndex:1];
  // 4
  UISlider *slider = (UISlider *)[tester waitForViewWithAccessibilityLabel:@"Work Time Slider"];
  STAssertEqualsWithAccuracy([slider value], 15.0f, 0.1, @"Work time slider was not set!");

KIF test actions have very readable names; see if you can figure out what’s going on here. Think of it as a…test! Yes, of course the pun is intentional.  :]

OK, here’s what’s happening section by section:

  1. Switch to the Timer tab.

  2. Enter “Set up a test” into the text field with the “Task Name” accessibility label (remember, you added this label to the storyboard earlier). Tap the “Done” button to close the keyboard.

  3. Call the helper method to select the second preset.

  4. Selecting the preset should change the slider values, so make sure it has indeed changed to the correct value.

In the final section of code, you’ll find a handy trick: waitForViewWithAccessibilityLabel:. Not only will it wait for the view to appear, but it actually returns a pointer to the view itself! Here, you cast the return value to UISlider to match up the proper type.

已有 1 人翻译此段
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。