• 0
  • 0
分享
  • 开发人员谈测试:网络测试和UI测试——软件测试圈
  • 恬恬圈 2023-09-14 16:51:47 字数 9044 阅读 910 收藏 0

  网络测试

  我们在测试某个方法的时候可能会遇到方法内部调用了网络通信能力:

  · 网络请求成功,可能刷新 UI 或者给出一些成功的提示

  · 网络失败或者网络不可用则给出一些失败的提示

  所以需要对网络通信去看进行模拟。

  iOS 中很多网络都是基于 NSURL 系统下的类实现的。所以我们可以利用 NSURLProtocol 的能力来监控网络并 mock 网络数据。

  开源项目 OHHTTPStubs  就是一个对网络模拟的库。它可以拦截 HTTP 请求,返回 json 数据,定制各种头信息。

  几个主要类及其功能:HTTPStubsProtocol 拦截网络请求、HTTPStubs 单例管理 HTTPStubsDescriptor 实例对象、HTTPStubsResponse 伪造 HTTP 请求。

  HTTPStubsProtocol 继承自 NSURLProtocol,可以在 HTTP 请求发送之前对 request 进行过滤处理:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
   BOOL found = ([HTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil);
   if (!found && HTTPStubs.sharedInstance.onStubMissingBlock) {
      HTTPStubs.sharedInstance.onStubMissingBlock(request);
   }
   return found;
}

  firstStubPassingTestForRequest 方法内部会判断请求是否需要被当前对象处理。

  紧接着开始发送网络请求。实际上在 - (void)startLoading 方法中可以用任何网络能力去完成请求,比如NSURLSession、NSURLConnection、AFNetworking 或其他网络框架,OHHTTPStubs 的做法是获取 request、client 对象。

  如果 HTTPStubs 单例中包含 onStubActivationBlock 对象,则执行该 block,然后利用 responseBlock 对象返回一个 HTTPStubsResponse 响应对象。

  OHHTTPStubs 的具体 API 可以查看文档,文档地址:https://github.com/AliSoftware/OHHTTPStubs/wiki/Usage-Examples。

  举个例子,利用 Kiwi、OHHTTPStubs 测试离线包功能,代码如下:

@interface HORouterManager (Unittest)
- (void)fetchOfflineInfoIfNeeded;
@end
SPEC_BEGIN(HORouterTests)
describe(@"routerTests", ^{
    context(@"criticalPath", ^{
        __block HORouterManager *routerManager = nil;
        beforeAll(^{
            routerManager = [[HORouterManager alloc] init];
        });
        it(@"getLocalPath", ^{
            __block NSString *pagePath = nil;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                pagePath = [routerManager filePathOfUrl:@"http://***/resource1"];
            });
            [[expectFutureValue(pagePath) shouldEventuallyBeforeTimingOutAfter(5)] beNonNil];
            
            __block NSString *rescPath = nil;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                rescPath = [routerManager filePathOfUrl:@"http://***/resource1"];
            });
            [[expectFutureValue(rescPath) shouldEventuallyBeforeTimingOutAfter(5)] beNonNil];
        });
        it(@"fetchOffline", ^{
            [HOOfflineManager sharedInstance].offlineInfoInterval = 0;
            [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
                return [request.URL.absoluteString containsString:@"h5-offline-pkg"];
            } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
                NSMutableDictionary *dict = [NSMutableDictionary dictionary];
                dict[@"code"] = @(0);
                dict[@"data"] = @"f722fc3efce547897819e9449d2ac562cee9075adda79ed74829f9d948e2f6d542a92e969e39dfbbd70aa2a7240d6fa3e51156c067e8685402727b6c13328092ecc0cbc773d95f9e0603b551e9447211b0e3e72648603e3d18e529b128470fa86aeb45d16af967d1a21b3e04361cfc767b7811aec6f19c274d388ddae4c8c68e857c14122a44c92a455051ae001fa7f2b177704bdebf8a2e3277faf0053460e0ecf178549e034a086470fa3bf287abbdd0f79867741293860b8a29590d2c2bb72b749402fb53dfcac95a7744ad21fe7b9e188881d1c24047d58c9fa46b3ebf4bc42a1defc50748758b5624c6c439c182fe21d4190920197628210160cf279187444bd1cb8707362cc4c3ab7486051af088d7851846bea21b64d4a5c73bd69aafc4bb34eb0862d1525c4f9a62ce64308289e2ecbc19ea105aa2bf99af6dd5a3ff653bbe7893adbec37b44a088b0b74b80532c720c79b7bb59fda3daf85b34ef35";
                NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];
                return [OHHTTPStubsResponse responseWithData:data
                                                  statusCode:200
                                                     headers:@{@"Content-Type":@"application/json"}];
            }];
            [routerManager fetchOfflineInfoIfNeeded];
            [[HOOfflineInfo shouldEventually] receive:@selector(saveToLocal:)];
        });
    });
});
SPEC_END

  插一嘴,我贴的代码已经好几次可以看到不同的测试框架组合了,所以不是说选了框架 A 就完事,根据场景选择最优解。

  UI 测试

  上面文章大篇幅的讲了单元测试相关的话题,单元测试十分适合代码质量、逻辑、网络等内容的测试,但是针对最终产物 App 来说单元测试就不太适合了,如果测试 UI 界面的正确性、功能是否正确显然就不太适合了。

  Apple 在 Xcode 7 开始推出的 UI Testing  就是苹果自己的 UI 测试框架。

  很多 UI 自动化测试框架的底层实现都依赖于 Accessibility,也就是 App 可用性,UI Accessibility 是 iOS 3.0 引入的一个人性化功能,帮助身体不便的人士方便使用 App。

  Accessibility 通过对 UI 元素进行分类和标记。分类成类似按钮、文本框、文本等类型,使用 identifier 来区分不同 UI 元素,无痕埋点的设计与实现里面也使用 accessibilityIdentifier 来绑定业务数据。

  1、使用 Xcode 自带的 UI测试则在创建工程的时候需要勾选 “Include UI Tests”。

  2、像单元测试意义,UI 测试方法命名以 test 开头。将鼠标光标移到方法内,点击 Xcode 左下方的红色按钮,开始录制 UI 脚本。

2-1.png

  解释说明:

/*! Proxy for an application that may or may not be running. */
@interface XCUIApplication : XCUIElement
// ...
@end

  XCUIApplication launch 来启动测试,XCUIApplication 是 UIApplication 在测试进程中的代理,用来和 App 进行一些交互。

  使用 staticTexts来获取当前屏幕上的静态文本(UILabel)元素的代理。等价于 [app descendantsMatchingType:XCUIElementTypeStaticText]。XCUIElementTypeStaticText 参数是枚举类型。

typedef NS_ENUM(NSUInteger, XCUIElementType) {
    XCUIElementTypeAny = 0,
    XCUIElementTypeOther = 1,
    XCUIElementTypeApplication = 2,
    XCUIElementTypeGroup = 3,
    XCUIElementTypeWindow = 4,
    XCUIElementTypeSheet = 5,
    XCUIElementTypeDrawer = 6,
    XCUIElementTypeAlert = 7,
    XCUIElementTypeDialog = 8,
    XCUIElementTypeButton = 9,
    XCUIElementTypeRadioButton = 10,
    XCUIElementTypeRadioGroup = 11,
    XCUIElementTypeCheckBox = 12,
    XCUIElementTypeDisclosureTriangle = 13,
    XCUIElementTypePopUpButton = 14,
    XCUIElementTypeComboBox = 15,
    XCUIElementTypeMenuButton = 16,
    XCUIElementTypeToolbarButton = 17,
    XCUIElementTypePopover = 18,
    XCUIElementTypeKeyboard = 19,
    XCUIElementTypeKey = 20,
    XCUIElementTypeNavigationBar = 21,
    XCUIElementTypeTabBar = 22,
    XCUIElementTypeTabGroup = 23,
    XCUIElementTypeToolbar = 24,
    XCUIElementTypeStatusBar = 25,
    XCUIElementTypeTable = 26,
    XCUIElementTypeTableRow = 27,
    XCUIElementTypeTableColumn = 28,
    XCUIElementTypeOutline = 29,
    XCUIElementTypeOutlineRow = 30,
    XCUIElementTypeBrowser = 31,
    XCUIElementTypeCollectionView = 32,
    XCUIElementTypeSlider = 33,
    XCUIElementTypePageIndicator = 34,
    XCUIElementTypeProgressIndicator = 35,
    XCUIElementTypeActivityIndicator = 36,
    XCUIElementTypeSegmentedControl = 37,
    XCUIElementTypePicker = 38,
    XCUIElementTypePickerWheel = 39,
    XCUIElementTypeSwitch = 40,
    XCUIElementTypeToggle = 41,
    XCUIElementTypeLink = 42,
    XCUIElementTypeImage = 43,
    XCUIElementTypeIcon = 44,
    XCUIElementTypeSearchField = 45,
    XCUIElementTypeScrollView = 46,
    XCUIElementTypeScrollBar = 47,
    XCUIElementTypeStaticText = 48,
    XCUIElementTypeTextField = 49,
    XCUIElementTypeSecureTextField = 50,
    XCUIElementTypeDatePicker = 51,
    XCUIElementTypeTextView = 52,
    XCUIElementTypeMenu = 53,
    XCUIElementTypeMenuItem = 54,
    XCUIElementTypeMenuBar = 55,
    XCUIElementTypeMenuBarItem = 56,
    XCUIElementTypeMap = 57,
    XCUIElementTypeWebView = 58,
    XCUIElementTypeIncrementArrow = 59,
    XCUIElementTypeDecrementArrow = 60,
    XCUIElementTypeTimeline = 61,
    XCUIElementTypeRatingIndicator = 62,
    XCUIElementTypeValueIndicator = 63,
    XCUIElementTypeSplitGroup = 64,
    XCUIElementTypeSplitter = 65,
    XCUIElementTypeRelevanceIndicator = 66,
    XCUIElementTypeColorWell = 67,
    XCUIElementTypeHelpTag = 68,
    XCUIElementTypeMatte = 69,
    XCUIElementTypeDockItem = 70,
    XCUIElementTypeRuler = 71,
    XCUIElementTypeRulerMarker = 72,
    XCUIElementTypeGrid = 73,
    XCUIElementTypeLevelIndicator = 74,
    XCUIElementTypeCell = 75,
    XCUIElementTypeLayoutArea = 76,
    XCUIElementTypeLayoutItem = 77,
    XCUIElementTypeHandle = 78,
    XCUIElementTypeStepper = 79,
    XCUIElementTypeTab = 80,
    XCUIElementTypeTouchBar = 81,
    XCUIElementTypeStatusItem = 82,
};

  通过 XCUIApplication 实例化对象调用 descendantsMatchingType: 方法得到的是 XCUIElementQuery 类型。比如 @property (readonly, copy*) XCUIElementQuery *staticTexts;:

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

  descendantsMatchingType 返回所有后代的类型匹配对象。childrenMatchingType 返回当前层级子元素的类型匹配对象:

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

  拿到 XCUIElementQuery 后不能直接拿到 XCUIElement。和 XCUIApplication 类似,XCUIElement 不能直接访问 UI 元素,它是 UI 元素在测试框架中的代理。可以通过 Accessibility 中的 frame、identifier 来获取。

  对比很多自动化测试框架都需要找出 UI 元素,也就是借助于 Accessibility 的 identifier。

  第三方 UI 自动化测试框架挺多的,可以查看下典型的 appium、macaca。

  测试经验总结

  TDD 写好测试再写业务代码,BDD 先写实现代码,再写基于行为的测试代码。

  另一种思路是没必要针对每个类的私有方法或者每个方法进行测试,因为等全部功能做完后针对每个类的接口测试,一般会覆盖据大多数的方法。等测试完看如果方法未被覆盖,则针对性的补充 Unit Test。

  目前,UI 测试(appium) 还是建议在核心逻辑且长时间没有改动的情况下去做,这样子每次发版本的时候可以当作核心逻辑回归了,目前来看价值是方便后续的迭代和维护上有一些便利性,其他的功能性测试还是走 BDD。

  对于类、函数、方法的走 TDD,老老实实写 UT、走 UT 覆盖率的把控。

  UITesting 还是建议在核心逻辑且长时间没有改动的情况下去做,这样子每次发版本的时候可以当作核心逻辑回归,目前来看价值是方便后续的迭代和维护上有一些便利性。例如用户中心 SDK 升级后,当时有了UITesing,基本上免去了测试人员介入。

  如果是一些活动页和逻辑经常变动的,老老实实走测试黑盒……

  我觉得一直有个误区,就是觉得自动测试是为了质量,其实质量都是附送的,测试先行是让开发更快更爽的。

2-2.png

  WWDC 这张图也很清楚,UI 其实需要的占比较小,还是要靠单测驱动。


作者:杭城小刘    

来源:http://www.51testing.com/html/03/n-4481203.html

  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 1.人工智能与机器学习人工智能(AI, Artificial Intelligence)与机器学习(ML, Machine Learning)是时下最火热的技术方向之一。准确地讲,人工智能不等同于机器学习,机器学习只是人工智能的一个子集。由于媒体的误用,本文遵循媒体的叫法,文中提到的人工智能,一般泛指机器学习。人工智能,顾名思义,就是希望创造出拥有像人类那样智能的机器,这是人类的终极梦想之一。该词早在1956年就提出了,当时的科学家曾乐观地估计,二十年左右人类就可以将其实现。然而科学家也很快就发现其难度远大于预估。在此之后的很长一段时间里,人工智能的研究陷入低谷。通常我们将人工智能分为弱人工智...
            0 0 1952
            分享
          •   一、Charles介绍  Charles是一款用Java编写的代理软件,电脑或者手机访问网站首先会访问到Charles代理工具上,由代理工具再把访问数据转发到相应的网站上,所以可以很好的通过设置Charles,对接口的请求和响应进行加工处理。  Fiddler虽然也是代理工具,但是Charles比Fiddler更好的地方是Charles接口管理层次分明,Charles可以用在Mac平台上。  二、Charles功能  1.Charles  是一款Proxy代理工具  2.Throtte Seting  模拟弱网环境  3. DNS Spoofing  &nbs...
            0 0 950
            分享
          •   据 Display Supply Chain Consultants 称,苹果即将推出的iPad Pro机型将采用"迄今为止市场上最好的 OLED 平板面板"。定于 5 月 7 日发布的OLED iPad Pro机型将采用 LTPO(一种更省电的 OLED)、120Hz ProMotion 刷新率、串联堆叠和玻璃减薄技术,从而带来"超薄、超轻显示屏",支持高亮度、更长的电池续航时间和更长的使用寿命。  与之前的传言一样,今天的报道显示 iPad Pro 机型将有 12.9 英寸和 11.1 英寸两种尺寸可供选择,两种选择都将采用纤薄边框和更薄的设计。...
            0 0 589
            分享
          •    模块和包的定义  模块的定义:任何  *.py  的文件都可以当作模块使用  import  导入  包的定义:包含一个__init__.py和其他模块、其他子包的一个目录  实际项目中,所谓的包和模块分别代表什么,如下:  包就是指 test  模块就是 do_excel.py , http_request.py , run.py  我们以上面这个目录讲解,在 run.py 文件中导入各个包的方式  导入单个test包 import test  导入report、log包   ...
            1 1 1763
            分享
          • 在之前的文章和视频中,我分享过Groovy语法中def关键字的基本使用方法。当时对def理解是:不定类型变量,资料中也有说是无类型变量,感觉两个意思大差不差,就是不显式声明对象类型。基本使用方式如下:def a = 1def b = "FunTester"类似这样的方式,当时Java新版也已经支持了var可以替代一些显式声明变量类型的代码,但是除了使用范围上,Groovy的def还是明显优于var,而且功能上也是强不少(当然有人理解为弱不少,后续讲解)。本质区别在于Groovy的def不仅可以替代显式的类型声明,还...
            0 0 833
            分享
      • 51testing软件测试圈微信