首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >XCode UI测试swizzle类方法

XCode UI测试swizzle类方法
EN

Stack Overflow用户
提问于 2015-11-10 12:21:35
回答 2查看 1.6K关注 0票数 3

我有一个简单的应用程序,它有两个按钮,调用一个JSON服务并输出结果消息。

我想尝试新的XCode 7 UI测试,但我不明白如何模拟API请求。

为了简单起见,我构建了一个示例,没有实际的请求,也没有任何异步操作。

我的主要目标是ZZSomeAPI.swift文件:

代码语言:javascript
复制
import Foundation
public class ZZSomeAPI: NSObject {
  public class func call(parameter:String) -> Bool {
      return true
  }
}

然后我的ZZSomeClientViewController.swift

代码语言:javascript
复制
import UIKit
class ZZSomeClientViewController: UIViewController {
    @IBAction func buttonClick(sender: AnyObject) {
        print(ZZSomeAPI.call("A"))
    }
}

现在,我添加了一个UITest目标,记录了点击按钮的情况,我有如下所示:

代码语言:javascript
复制
import XCTest
class ZZSomeClientUITests: XCTestCase {
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        XCUIApplication().launch()
    }

    func testCall() {
        let app = XCUIApplication()
        app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Button).elementBoundByIndex(0).tap()
    }        
}

因此,这是可行的,运行测试将打印出true。但是,我想在API返回false时包含一个测试,而不影响API。因此,我将ZZSomeAPI.swift添加到UI测试目标中,并尝试了方法swizzling (UITest代码已更新):

代码语言:javascript
复制
import XCTest

class ZZSomeClientUITests: XCTestCase {

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        XCUIApplication().launch()
    }

    func testSwizzle() {
        XCTAssert(ZZSomeAPI.call("a"))
        XCTAssertFalse(ZZSomeAPI.callMock("a"))
        XCTAssert(ZZSomeAPI.swizzleClass("call", withSelector: "callMock", forClass: ZZSomeAPI.self))
        XCTAssertFalse(ZZSomeAPI.call("a"), "failed swizzle")
    }

    func testCall() {
        XCTAssert(ZZSomeAPI.swizzleClass("call", withSelector: "callMock", forClass: ZZSomeAPI.self))
        let app = XCUIApplication()
        app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Button).elementBoundByIndex(0).tap()
    }

}

extension NSObject {
    public class func swizzleClass(origSelector: String!, withSelector: String!, forClass:AnyClass!) -> Bool {
        var originalMethod: Method?
        var swizzledMethod: Method?

        originalMethod = class_getClassMethod(forClass, Selector(origSelector))
        swizzledMethod = class_getClassMethod(forClass, Selector(withSelector))

        if (originalMethod == COpaquePointer(bitPattern: 0)) { return false }
        if (swizzledMethod == COpaquePointer(bitPattern: 0)) { return false }

        method_exchangeImplementations(originalMethod!, swizzledMethod!)
        return true
    }
}

extension ZZSomeAPI {
    public class func callMock(parameter:String) -> Bool {
      return false
    }
}

因此,testSwizzle()通过了,这意味着swizzling成功了。但是testCall()仍然打印true而不是false

这是因为只有当UITest和main目标是两个不同的应用程序时,才在测试目标上执行swizzling吗?

有办法绕过这件事吗?

我已经找到了Mock API Requests Xcode 7 Swift Automated UI Testing,但我不知道如何在这里使用launchArguments

在这个例子中只有一个例子,但是我需要针对不同测试方法的不同结果来模拟call()方法.如果我使用一个launchArgument,如包含要返回的完整响应的MOCK_API_RESPONSE,则主目标应用程序委托将有一些“只进行测试”的代码.是否有任何方法(在主目标中)检查它是为UITest目标编译的,所以它只包含了该模拟launchArguments的代码?

最干净的选择就是工作.

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-11-10 13:14:39

Xcode UI测试在与应用程序不同的应用程序中执行。因此,对测试运行器应用程序中的类的更改不会影响测试应用程序中的类。

这与单元测试不同,在单元测试中,测试在应用程序进程中运行。

票数 3
EN

Stack Overflow用户

发布于 2015-11-10 20:01:50

我接受了Mats的答案,因为实际的问题( UI测试中的swizzling)已经得到了回答。

但是,我最终得到的解决方案是使用一个在线模拟服务器http://mocky.io/,因为它的目标是模拟远程API调用。

使用API的public属性更新了ZZSomeAPI.swift,而不是在方法中获得它:

代码语言:javascript
复制
import Foundation
public class ZZSomeAPI {
  public static var apiURL: String = NSBundle.mainBundle().infoDictionary?["API_URL"] as! String

  public class func call(parameter:String) -> Bool {
      ... use apiURL ...
  }
}

然后更新应用程序委托,以利用launchArguments

代码语言:javascript
复制
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        if NSProcessInfo().arguments.contains("MOCK_API") { // for UI Testing
            if let param = NSProcessInfo().environment["MOCK_API_URL"] {
                ZZSomeAPI.apiURL = param
            }
        }
        return true
    }
}

然后在我的UI测试用例类中创建setupAPIMockWith,以便在按需mocky.io中创建模拟响应:

代码语言:javascript
复制
import XCTest
class ZZSomeClientUITests: XCTestCase {

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
    }

    func setupAPIMockWith(jsonBody: NSDictionary) -> String {
        let expectation = self.expectationWithDescription("mock request setup")
        let request = NSMutableURLRequest(URL: NSURL(string: "http://www.mocky.io/")!)
        request.HTTPMethod = "POST"

        var theJSONText: NSString?
        do {
            let theJSONData = try NSJSONSerialization.dataWithJSONObject(jsonBody, options: NSJSONWritingOptions.PrettyPrinted)
            theJSONText = NSString(data: theJSONData, encoding: NSUTF8StringEncoding)
        } catch {
            XCTFail("failed to serialize json body for mock setup")
        }

        let params = [
            "statuscode": "200",
            "location": "",
            "contenttype": "application/json",
            "charset": "UTF-8",
            "body": theJSONText!
        ]
        let body = params.map({
            let key = $0.0.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
            let value = $0.1.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
            return "\(key!)=\(value!)"
        }).joinWithSeparator("&")
        request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)
        request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")

        var url: String?

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in

            XCTAssertNil(error)

            do {
                let json: NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as! NSDictionary
                XCTAssertNotNil(json["url"])
                url = json["url"] as? String
            } catch {
                XCTFail("failed to parse mock setup json")
            }

            expectation.fulfill()
        }

        task.resume()

        self.waitForExpectationsWithTimeout(5, handler: nil)
        XCTAssertNotEqual(url, "")

        return url!
    }

    func testCall() {
        let app = XCUIApplication()
        app.launchArguments.append("MOCK_API")
        app.launchEnvironment = [
            "MOCK_API_URL": self.setupAPIMockWith([
                    "msg": [
                        "code": -99,
                        "text": "yoyo"
                    ]
                ])
        ]
        app.launch()

        app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Button).elementBoundByIndex(0).tap()
        app.staticTexts["yoyo"].tap()
    }

}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33629875

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档