自动化Test使用详细解析(一) —— 基本使用(一)

版本记录

版本号 时间
V1.0 2017.10.10

前言

UI Tests是一个自动测试UI与交互的Testing组件,它可以通过编写代码、或者是记录开发者的操作过程并代码化,来实现自动点击某个按钮、视图,或者自动输入文字等功能。接下来几篇我们就说一下该技术的使用。

基本介绍

大家多UI Tests并不陌生,每当我们新建立一个工程,在建立工程界面我们都可以看到包含UITest的复选框。如下图所示。

自动化Test使用详细解析(一) —— 基本使用(一)_第1张图片

这里大家可以看到,下面两个复选框,红色的是数据存储Core Data,下面绿色的框就是我们要说的UI Test。相信大家对它都似曾相识了吧,只是一般我们不用而已。


使用方法

1. 添加UI Tests

新建工程时添加

添加UI Tests有两个方法,第一种就是如上图所示,在创建工程的时候就勾上复选框支持UI Tests,勾选后新建立的工程如下所示。

自动化Test使用详细解析(一) —— 基本使用(一)_第2张图片

这里可以看见,项目中自动生成了文件夹JJUITest_demo1UITests,打开JJUITest_demo1UITests.m我们就可以看见里面的代码。如下所示。

#import 

@interface JJUITest_demo1UITests : XCTestCase

@end

@implementation JJUITest_demo1UITests

- (void)setUp {
    [super setUp];
    
    // Put setup code here. This method is called before the invocation of each test method in the class.
    
    // In UI tests it is usually best to stop immediately when a failure occurs.
    self.continueAfterFailure = NO;
    // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
    [[[XCUIApplication alloc] init] launch];
    
    // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testExample {
    // Use recording to get started writing UI tests.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

@end

这里面测试代码写在- (void)testExample里面,后面我们会讲解。

后续为工程添加

有的时候我们还有这样的需求,那就是,我们开始的时候并没打算使用UI Tests,后来想使用,那这样我们怎么做呢?别急,我们有办法,可以为后续为以前建立的工程添加UI Tests。我们先建立一个不勾选复选框的工程。如下图所示。

自动化Test使用详细解析(一) —— 基本使用(一)_第3张图片

下面我们就看一下如何为这个工程添加UI Tests

  • 选择File -> New -> Target
自动化Test使用详细解析(一) —— 基本使用(一)_第4张图片
  • 选择iOS UI Testing Bundle
自动化Test使用详细解析(一) —— 基本使用(一)_第5张图片
  • 可以起个名字,我这里就用系统给生成的了
自动化Test使用详细解析(一) —— 基本使用(一)_第6张图片
  • 这里就是我们添加UI Tests 后的结果。
自动化Test使用详细解析(一) —— 基本使用(一)_第7张图片

2. 添加测试代码

我们前面提到过,我们可以在方法- (void)testExample中添加测试代码。对于新手可能不会写代码,这里苹果为我们先到了,可以自动生成代码,也可以手动写入代码。下面我们就看一下这两种方法。

准备页面和素材

为了测试,我们先创建一个页面。代码如下所示。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor lightGrayColor];
    
    UIButton *demoButton = [[UIButton alloc] initWithFrame:CGRectMake(50.0, 200.0, 200.0, 100.0)];
    demoButton.backgroundColor = [UIColor redColor];
    [demoButton addTarget:self action:@selector(buttonDidClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:demoButton];
}

#pragma mark - Object Private Function

- (void)buttonDidClick
{
    CGFloat colorRandomRedsValue = arc4random()%255;
    CGFloat colorRandomBluesValue = arc4random()%255;
    CGFloat colorRandomGreenValue = arc4random()%255;
    self.view.backgroundColor = [UIColor colorWithRed:colorRandomRedsValue/255.0 green:colorRandomGreenValue/255.0 blue:colorRandomBluesValue/255.0 alpha:1.0];
}

@end

这个是页面操作效果。

自动化Test使用详细解析(一) —— 基本使用(一)_第8张图片

自动生成代码

如何自动生成代码呢?我们需要选择测试文件,并点击左下角的红色按钮,这时候开始进行操作,它会记录你的操作步骤,并生成测试代码。

自动化Test使用详细解析(一) —— 基本使用(一)_第9张图片

大家可以看见,是暗红色不可点击的,这个时候我们把鼠标插入点放在方法- (void)testExample里面,这个时候按钮就会变得深红色就是可以点击的了。这个时候我们点击按钮,然后开始操作,系统就会记录我们的操作并生成代码。

大家可以看一下,下面就是我生成的代码。

- (void)testExample
{
    XCUIElement *window = [[[[XCUIApplication alloc] init] childrenMatchingType:XCUIElementTypeWindow] elementBoundByIndex:0];
    [[window childrenMatchingType:XCUIElementTypeOther].element tap];
    
    XCUIElement *button = [window.otherElements childrenMatchingType:XCUIElementTypeButton].element;
    [button tap];
    [button tap];
    [button tap];
    [button tap];
}

@end

这里我一共点击了四次按钮。

下面我们点击下面这个开关或者入口就可以自动测试了。

自动化Test使用详细解析(一) —— 基本使用(一)_第10张图片

下面是控制台打印出的部分信息。

自动化Test使用详细解析(一) —— 基本使用(一)_第11张图片

当测试成功或者失败都会弹出一个提示框,存在一段时间就会自动消失。

手动输入代码

前面说的自动生成代码,是xcode替我们做了很多的事情,这里示例比较简单,但是对于一些大型项目来说,有时候生成的测试代码并不一定很准确,所以很多时候我们需要手动的编写测试代码。但是对于初学者来说,并不是很简单,需要对相关语法有一定的了解,下面我们就先看一下想关语法和知识。

XCUIApplication

XCUIApplication这个类继承自XCUIElement,掌管应用程序的生命周期。它有下面几个方法和属性。

@interface XCUIApplication : XCUIElement

- (instancetype)init NS_DESIGNATED_INITIALIZER;
+ (instancetype)new;
- (instancetype)initWithBundleIdentifier:(NSString *)bundleIdentifier
- (void)launch;
- (void)activate;
- (void)terminate;
- (BOOL)waitForState:(XCUIApplicationState)state timeout:(NSTimeInterval)timeout XCT_WARN_UNUSED;

@property (nonatomic, copy) NSArray  *launchArguments;
@property (nonatomic, copy) NSDictionary  *launchEnvironment;
@property (readonly) XCUIApplicationState state;

XCUIElement

XCUIElement继承NSObject,实现协议XCUIElementAttributes, XCUIElementTypeQueryProvider可以表示系统的各种UI元素。

@interface XCUIElement : NSObject 

+ (instancetype)new XCT_UNAVAILABLE("Use XCUIElementQuery to create XCUIElement instances.");
- (instancetype)init XCT_UNAVAILABLE("Use XCUIElementQuery to create XCUIElement instances.");

/*! Test to determine if the element exists. */
@property (readonly) BOOL exists;

/*! Waits the specified amount of time for the element's exist property to be true and returns false if the timeout expires without the element coming into existence. */
- (BOOL)waitForExistenceWithTimeout:(NSTimeInterval)timeout XCT_WARN_UNUSED;

/*! Whether or not a hit point can be computed for the element for the purpose of synthesizing events. */
@property (readonly, getter = isHittable) BOOL hittable;

/*! Returns a query for all descendants of the element matching the specified type. */
- (XCUIElementQuery *)descendantsMatchingType:(XCUIElementType)type;

/*! Returns a query for direct children of the element matching the specified type. */
- (XCUIElementQuery *)childrenMatchingType:(XCUIElementType)type;

#if !TARGET_OS_TV
/*! Creates and returns a new coordinate that will compute its screen point by adding the offset multiplied by the size of the element’s frame to the origin of the element’s frame. */
- (XCUICoordinate *)coordinateWithNormalizedOffset:(CGVector)normalizedOffset;
#endif

/*!
 @discussion
 Provides debugging information about the element. The data in the string will vary based on the
 time at which it is captured, but it may include any of the following as well as additional data:
    • Values for the elements attributes.
    • The entire tree of descendants rooted at the element.
    • The element's query.
 This data should be used for debugging only - depending on any of the data as part of a test is unsupported.
 */
@property (readonly, copy) NSString *debugDescription;

@end

下面重点说几个方法

/*! Returns a query for all descendants of the element matching the specified type. */
- (XCUIElementQuery *)descendantsMatchingType:(XCUIElementType)type;

/*! Returns a query for direct children of the element matching the specified type. */
- (XCUIElementQuery *)childrenMatchingType:(XCUIElementType)type;

方法descendantsMatchingType :和方法childrenMatchingType都是返回的是子类,区别在于前者取某种类型的元素以及它的子类集合,比如自定义按钮,后者取某种类型的元素集合,不包含它的子类,比如系统级别的UIButton。

还有属性@property (readonly) BOOL exists;用来判断该控件是否存在,不存在的控件如果向它发送消息,会抛出异常。

下面在看一下相关的交互方法。

tap():         点击
doubleTap():   双击

//轻扫
/*!
 * Sends a swipe-up gesture.
 */
- (void)swipeUp;

/*!
 * Sends a swipe-down gesture.
 */
- (void)swipeDown;

/*!
 * Sends a swipe-left gesture.
 */
- (void)swipeLeft;

/*!
 * Sends a swipe-right gesture.
 */
- (void)swipeRight;

pressForDuration(duration: NSTimeInterval):  //长按
typeText(text: String):  用于textField和textView输入文本时使用,使用前要确保文本框获得输入焦点,可以使用tap()函数使其获得焦点

//捏合
- (void)pinchWithScale:(CGFloat)scale velocity:(CGFloat)velocity;

//旋转
- (void)rotate:(CGFloat)rotation withVelocity:(CGFloat)velocity;

//点击次数
- (void)tapWithNumberOfTaps:(NSUInteger)numberOfTaps numberOfTouches:(NSUInteger)numberOfTouches;
  • 下面的就是XCUIElement的一个分类XCUIScreenshotProviding
@interface XCUIElement (XCUIScreenshotProviding) 
@end
  • 还有分类XCUIElementKeyboardEvents
@interface XCUIElement (XCUIElementKeyboardEvents)

/*!
 * Types a string into the element. The element or a descendant must have keyboard focus; otherwise an
 * error is raised.
 *
 * This API discards any modifiers set in the current context by +performWithKeyModifiers:block: so that
 * it strictly interprets the provided text. To input keys with modifier flags, use  -typeKey:modifierFlags:.
 */
- (void)typeText:(NSString *)text;

#if TARGET_OS_OSX

/*!
 * Hold modifier keys while the given block runs. This method pushes and pops the modifiers as global state
 * without need for reference to a particular element. Inside the block, elements can be clicked on, dragged
 * from, typed into, etc.
 */
+ (void)performWithKeyModifiers:(XCUIKeyModifierFlags)flags block:(XCT_NOESCAPE void (^)(void))block;

/*!
 * Types a single key with the specified modifier flags. Although `key` is a string, it must represent
 * a single key on a physical keyboard; strings that resolve to multiple keys will raise an error at runtime.
 * In addition to literal string key representations like "a", "6", and "[", keys such as the arrow keys,
 * command, control, option, and function keys can be typed using constants defined for them in XCUIKeyboardKeys.h
 */
- (void)typeKey:(NSString *)key modifierFlags:(XCUIKeyModifierFlags)flags;

#endif // TARGET_OS_OSX

@end
  • 还有分类XCUIElementTouchEvents
@interface XCUIElement (XCUIElementTouchEvents)

/*!
 * Sends a tap event to a hittable point computed for the element.
 */
- (void)tap;

/*!
 * Sends a double tap event to a hittable point computed for the element.
 */
- (void)doubleTap;

/*!
 * Sends a two finger tap event to a hittable point computed for the element.
 */
- (void)twoFingerTap;

/*!
 * Sends one or more taps with one of more touch points.
 *
 * @param numberOfTaps
 * The number of taps.
 *
 * @param numberOfTouches
 * The number of touch points.
 */
- (void)tapWithNumberOfTaps:(NSUInteger)numberOfTaps numberOfTouches:(NSUInteger)numberOfTouches;

/*!
 * Sends a long press gesture to a hittable point computed for the element, holding for the specified duration.
 *
 * @param duration
 * Duration in seconds.
 */
- (void)pressForDuration:(NSTimeInterval)duration;

/*!
 * Initiates a press-and-hold gesture that then drags to another element, suitable for table cell reordering and similar operations.
 * @param duration
 * Duration of the initial press-and-hold.
 * @param otherElement
 * The element to finish the drag gesture over. In the example of table cell reordering, this would be the reorder element of the destination row.
 */
- (void)pressForDuration:(NSTimeInterval)duration thenDragToElement:(XCUIElement *)otherElement;

/*!
 * Sends a swipe-up gesture.
 */
- (void)swipeUp;

/*!
 * Sends a swipe-down gesture.
 */
- (void)swipeDown;

/*!
 * Sends a swipe-left gesture.
 */
- (void)swipeLeft;

/*!
 * Sends a swipe-right gesture.
 */
- (void)swipeRight;

/*!
 * Sends a pinching gesture with two touches.
 *
 * The system makes a best effort to synthesize the requested scale and velocity: absolute accuracy is not guaranteed.
 * Some values may not be possible based on the size of the element's frame - these will result in test failures.
 *
 * @param scale
 * The scale of the pinch gesture.  Use a scale between 0 and 1 to "pinch close" or zoom out and a scale greater than 1 to "pinch open" or zoom in.
 *
 * @param velocity
 * The velocity of the pinch in scale factor per second.
 */
- (void)pinchWithScale:(CGFloat)scale velocity:(CGFloat)velocity;

/*!
 * Sends a rotation gesture with two touches.
 *
 * The system makes a best effort to synthesize the requested rotation and velocity: absolute accuracy is not guaranteed.
 * Some values may not be possible based on the size of the element's frame - these will result in test failures.
 *
 * @param rotation
 * The rotation of the gesture in radians.
 *
 * @param velocity
 * The velocity of the rotation gesture in radians per second.
 */
- (void)rotate:(CGFloat)rotation withVelocity:(CGFloat)velocity;

@end
  • 还有分类XCUIElementMouseEvents
@interface XCUIElement (XCUIElementMouseEvents)

/*!
 * Moves the cursor over the element.
 */
- (void)hover;

/*!
 * Sends a click event to a hittable point computed for the element.
 */
- (void)click;

/*!
 * Sends a double click event to a hittable point computed for the element.
 */
- (void)doubleClick;

/*!
 * Sends a right click event to a hittable point computed for the element.
 */
- (void)rightClick;

/*!
 * Clicks and holds for a specified duration (generally long enough to start a drag operation) then drags
 * to the other element.
 */
- (void)clickForDuration:(NSTimeInterval)duration thenDragToElement:(XCUIElement *)otherElement;

/*!
 * Scroll the view the specified pixels, x and y.
 */
- (void)scrollByDeltaX:(CGFloat)deltaX deltaY:(CGFloat)deltaY;

@end
  • 还有分类XCUIElementTouchBarEvents
@interface XCUIElement (XCUIElementTouchBarEvents)

/*!
 * Sends a tap event to a central point computed for the element.
 */
- (void)tap;

/*!
 * Sends a double tap event to a central point computed for the element.
 */
- (void)doubleTap;

/*!
 * Sends a long press gesture to a central point computed for the element, holding for the specified duration.
 *
 * @param duration
 * Duration in seconds.
 */
- (void)pressForDuration:(NSTimeInterval)duration;

/*!
 * Initiates a press-and-hold gesture that then drags to another element.
 * @param duration
 * Duration of the initial press-and-hold.
 * @param otherElement
 * The element to finish the drag gesture over.
 */
- (void)pressForDuration:(NSTimeInterval)duration thenDragToElement:(XCUIElement *)otherElement;

@end
  • 还有分类XCUIElementTypeSlider
/*! This category on XCUIElement provides functionality for automating UISlider and NSSlider. */
@interface XCUIElement (XCUIElementTypeSlider)

/*! Manipulates the UI to change the displayed value of the slider to one based on a normalized position. 0 corresponds to the minimum value of the slider, 1 corresponds to its maximum value. The adjustment is a "best effort" to move the indicator to the desired position; absolute fidelity is not guaranteed. */
- (void)adjustToNormalizedSliderPosition:(CGFloat)normalizedSliderPosition;

/*! Returns the position of the slider's indicator as a normalized value where 0 corresponds to the minimum value of the slider and 1 corresponds to its maximum value. */
@property (readonly) CGFloat normalizedSliderPosition;

@end
  • 还有分类XCUIElementTypePickerWheel
/*! This category on XCUIElement provides functionality for automating the picker wheels of UIPickerViews and UIDatePickers. */
@interface XCUIElement (XCUIElementTypePickerWheel)

/*! Changes the displayed value for the picker wheel. Will generate a test failure if the specified value is not available. */
- (void)adjustToPickerWheelValue:(NSString *)pickerWheelValue;

@end

大家也注意到了XCUIElement遵守了两个协议,XCUIElementAttributes, XCUIElementTypeQueryProvider

XCUIElementAttributes

描述在用户界面元素上公开的属性并在查询匹配期间可用的协议。 这些属性表示暴露给系统的数据。

*! Protocol describing the attributes exposed on user interface elements and available during query matching. These attributes represent data exposed to the Accessibility system. */
@protocol XCUIElementAttributes

/*! The accessibility identifier. */
@property (readonly) NSString *identifier;

/*! The frame of the element in the screen coordinate space. */
@property (readonly) CGRect frame;

/*! The raw value attribute of the element. Depending on the element, the actual type can vary. */
@property (readonly, nullable) id value;

/*! The title attribute of the element. */
@property (readonly, copy) NSString *title;

/*! The label attribute of the element. */
@property (readonly, copy) NSString *label;

/*! The type of the element. /seealso XCUIElementType. */
@property (readonly) XCUIElementType elementType;

/*! Whether or not the element is enabled for user interaction. */
@property (readonly, getter = isEnabled) BOOL enabled;

/*! The horizontal size class of the element. */
@property (readonly) XCUIUserInterfaceSizeClass horizontalSizeClass;

/*! The vertical size class of the element. */
@property (readonly) XCUIUserInterfaceSizeClass verticalSizeClass;

/*! The value that is displayed when the element has no value. */
@property (readonly, nullable) NSString *placeholderValue;

/*! Whether or not the element is selected. */
@property (readonly, getter = isSelected) BOOL selected;

#if TARGET_OS_TV
/*! Whether or not the elment has UI focus. */
@property (readonly) BOOL hasFocus;
#endif

@end

XCUIElementTypeQueryProvider

里面包含了系统中大部分UI控件的类型,可通过读属性的方式取得某种类型的UI集合。

@protocol XCUIElementTypeQueryProvider

@property (readonly, copy) XCUIElementQuery *touchBars;
@property (readonly, copy) XCUIElementQuery *groups;
@property (readonly, copy) XCUIElementQuery *windows;
@property (readonly, copy) XCUIElementQuery *sheets;
@property (readonly, copy) XCUIElementQuery *drawers;
@property (readonly, copy) XCUIElementQuery *alerts;
@property (readonly, copy) XCUIElementQuery *dialogs;
@property (readonly, copy) XCUIElementQuery *buttons;
@property (readonly, copy) XCUIElementQuery *radioButtons;
@property (readonly, copy) XCUIElementQuery *radioGroups;
@property (readonly, copy) XCUIElementQuery *checkBoxes;
@property (readonly, copy) XCUIElementQuery *disclosureTriangles;
@property (readonly, copy) XCUIElementQuery *popUpButtons;
@property (readonly, copy) XCUIElementQuery *comboBoxes;
@property (readonly, copy) XCUIElementQuery *menuButtons;
@property (readonly, copy) XCUIElementQuery *toolbarButtons;
@property (readonly, copy) XCUIElementQuery *popovers;
@property (readonly, copy) XCUIElementQuery *keyboards;
@property (readonly, copy) XCUIElementQuery *keys;
@property (readonly, copy) XCUIElementQuery *navigationBars;
@property (readonly, copy) XCUIElementQuery *tabBars;
@property (readonly, copy) XCUIElementQuery *tabGroups;
@property (readonly, copy) XCUIElementQuery *toolbars;
@property (readonly, copy) XCUIElementQuery *statusBars;
@property (readonly, copy) XCUIElementQuery *tables;
@property (readonly, copy) XCUIElementQuery *tableRows;
@property (readonly, copy) XCUIElementQuery *tableColumns;
@property (readonly, copy) XCUIElementQuery *outlines;
@property (readonly, copy) XCUIElementQuery *outlineRows;
@property (readonly, copy) XCUIElementQuery *browsers;
@property (readonly, copy) XCUIElementQuery *collectionViews;
@property (readonly, copy) XCUIElementQuery *sliders;
@property (readonly, copy) XCUIElementQuery *pageIndicators;
@property (readonly, copy) XCUIElementQuery *progressIndicators;
@property (readonly, copy) XCUIElementQuery *activityIndicators;
@property (readonly, copy) XCUIElementQuery *segmentedControls;
@property (readonly, copy) XCUIElementQuery *pickers;
@property (readonly, copy) XCUIElementQuery *pickerWheels;
@property (readonly, copy) XCUIElementQuery *switches;
@property (readonly, copy) XCUIElementQuery *toggles;
@property (readonly, copy) XCUIElementQuery *links;
@property (readonly, copy) XCUIElementQuery *images;
@property (readonly, copy) XCUIElementQuery *icons;
@property (readonly, copy) XCUIElementQuery *searchFields;
@property (readonly, copy) XCUIElementQuery *scrollViews;
@property (readonly, copy) XCUIElementQuery *scrollBars;
@property (readonly, copy) XCUIElementQuery *staticTexts;
@property (readonly, copy) XCUIElementQuery *textFields;
@property (readonly, copy) XCUIElementQuery *secureTextFields;
@property (readonly, copy) XCUIElementQuery *datePickers;
@property (readonly, copy) XCUIElementQuery *textViews;
@property (readonly, copy) XCUIElementQuery *menus;
@property (readonly, copy) XCUIElementQuery *menuItems;
@property (readonly, copy) XCUIElementQuery *menuBars;
@property (readonly, copy) XCUIElementQuery *menuBarItems;
@property (readonly, copy) XCUIElementQuery *maps;
@property (readonly, copy) XCUIElementQuery *webViews;
@property (readonly, copy) XCUIElementQuery *steppers;
@property (readonly, copy) XCUIElementQuery *incrementArrows;
@property (readonly, copy) XCUIElementQuery *decrementArrows;
@property (readonly, copy) XCUIElementQuery *tabs;
@property (readonly, copy) XCUIElementQuery *timelines;
@property (readonly, copy) XCUIElementQuery *ratingIndicators;
@property (readonly, copy) XCUIElementQuery *valueIndicators;
@property (readonly, copy) XCUIElementQuery *splitGroups;
@property (readonly, copy) XCUIElementQuery *splitters;
@property (readonly, copy) XCUIElementQuery *relevanceIndicators;
@property (readonly, copy) XCUIElementQuery *colorWells;
@property (readonly, copy) XCUIElementQuery *helpTags;
@property (readonly, copy) XCUIElementQuery *mattes;
@property (readonly, copy) XCUIElementQuery *dockItems;
@property (readonly, copy) XCUIElementQuery *rulers;
@property (readonly, copy) XCUIElementQuery *rulerMarkers;
@property (readonly, copy) XCUIElementQuery *grids;
@property (readonly, copy) XCUIElementQuery *levelIndicators;
@property (readonly, copy) XCUIElementQuery *cells;
@property (readonly, copy) XCUIElementQuery *layoutAreas;
@property (readonly, copy) XCUIElementQuery *layoutItems;
@property (readonly, copy) XCUIElementQuery *handles;
@property (readonly, copy) XCUIElementQuery *otherElements;
@property (readonly, copy) XCUIElementQuery *statusItems;

/*!
 * Returns an element that will use the query for resolution. This changes how the query is resolved
 * at runtime; instead of evaluating against every element in the user interface, `firstMatch` stops
 * the search as soon as a single matching element is found. This can result in significantly faster
 * evaluation if the element is located early in a large view hierarchy but also means that multiple
 * matches will not be detected.
 */
@property (readonly) XCUIElement *firstMatch;

@end

NS_ASSUME_NONNULL_END

#endif

后记

未完,待续~~~

自动化Test使用详细解析(一) —— 基本使用(一)_第12张图片

你可能感兴趣的:(自动化Test使用详细解析(一) —— 基本使用(一))