djk3000 / ME

4 stars 2 forks source link

Swift-UITest #113

Open djk3000 opened 1 year ago

djk3000 commented 1 year ago

Swift中的UITest

UITest和UnitTest又完全不一样了,UITest可以测试用户的界面流程。而不会去测试所有的代码逻辑。

实现

创建步骤

和UnitTest一样在我们的要测试项目中 File->New->Targer->UnitTest Bundle->命名为(项目名_UITest)->Finish

初始Test文件

import XCTest

final class DJKAdvancedLearning_UITests: XCTestCase {
    //把app的创建提取出来
    let app = XCUIApplication()

    override func setUpWithError() throws {
        // 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.
        continueAfterFailure = false

        // 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.

       //初始化创建
        app.launch()
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // UI tests must launch the application that they test.
        //不需要每个测试都去初始化一遍,直接用全局的app
        // let app = XCUIApplication()
        // app.launch()

        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTApplicationLaunchMetric()]) {
                XCUIApplication().launch()
            }
        }
    }
}

这里和UnitTest不一样的地方在于,不需要每个测试都去创建Application,只要在初始化的时候创建一次。

命名规则

这里参考UnitTest一样

编写用例

    func test_UITestBootcampView_signUpButton_shouldSign() {

        let app = XCUIApplication()
        app.keys["A"].tap()

        let aKey = app.keys["a"]
        aKey.tap()
        aKey.tap()
        app.buttons["SignUpButton"].tap()
        app.buttons["ShowAlertButton"].tap()
        app.alerts["Welcome to app"].scrollViews.otherElements.buttons["OK"].tap()

    }

这里的代码是我通过XCode自带的模拟器操作来自动编写出来的,然后再自己修改这个用例,这个是原始的,操作如下:

https://user-images.githubusercontent.com/9426603/208841968-894d823e-7d21-41a7-a321-c02e6f6d6f09.mov

修改后的例子

    func test_UITestBootcampView_signUpButton_shouldSignIn() {
        //Given
//        let addYourNameTextField = app.textFields["Add Your Name"]
        let addYourNameTextField = app.textFields["SignUpTextField"]
        addYourNameTextField.tap()
        //When
        app.keys["A"].tap()

        let aKey = app.keys["a"]
        aKey.tap()
        aKey.tap()
        aKey.tap()
        aKey.tap()
        app.buttons["Return"].tap()
        app.buttons["SignUpButton"].tap()
        let navigationBar = app.navigationBars["Welcome"]

        //Then
        XCTAssertTrue(navigationBar.exists)
    }

一些小技巧

  1. 可以通过在View上添加accessibilityIdentifier来讲Test中的Name变为固定的控件,而不是通过Text的String来判断,不然如果这个String更改了就需要更改测试用例。

    TextField(vm.placeFolder, text: “Add Your Name”)
                .font(.headline)
                .padding()
                .background(Color.white)
                .cornerRadius(10)
                .accessibilityIdentifier("SignUpTextField")
    测试使用:
    //        let addYourNameTextField = app.textFields["Add Your Name"]
        let addYourNameTextField = app.textFields["SignUpTextField"]
  2. 判断的话同样是使用XCTAssert()的一些方法来进行判断,增加了一些UI的判断比如:

    //在屏幕上组件是否存在(显示)
    let buttonIsExists = app.buttons["back"].exists
    //组件存在且当前位置可以点击,若被覆盖則為false
    let buttonIsHittable = app.buttons["back"].isHittable 
  3. 当点击框出现和消失的时候,可能太快了测试会捕捉不到,这时候可以添加sleep(1)时间,或者等待超时来等待我们的控件显示或者消失

    
    func test_signInHomeView_showAlertButton_shouldDisplayAndDismissAlert() {
        //Given
        app.textFields["SignUpTextField"].tap()
    
        //When
        let aKey = app.keys["A"]
        aKey.tap()
    
        let aKey2 = app.keys["a"]
        aKey2.tap()
        aKey2.tap()
        aKey2.tap()
        aKey2.tap()
        app.buttons["Return"].tap()
        app.buttons["SignUpButton"].tap()
        app.buttons["ShowAlertButton"].tap()
    
        //Then
        let alert = app.alerts.firstMatch
        XCTAssertTrue(alert.exists)
    
        let okButton = alert.buttons["OK"]
    
        //等待button的显示,如果5秒不显示测测试失败
        let existOKButtonAlert = okButton.waitForExistence(timeout: 5)
        XCTAssertTrue(existOKButtonAlert)
    
        okButton.tap()
    
        let existAlert = alert.waitForExistence(timeout: 3)

// sleep(1) 也可以使用sleep等待,如果控制不好时间的话还是超时比较方便 XCTAssertFalse(existAlert) XCTAssertFalse(alert.exists)

}

4. 可以在App启动的时候添加参数和环境变量,可以对环境变量和参数进行判断增加测试的便捷性。
在Product->Scheme->Edit Scheme -> Run或者在选择ios模拟器的旁边也有这个选项。具体也可以看[这里](https://nshipster.cn/launch-arguments-and-environment-variables/)
<img width="933" alt="image" src="https://user-images.githubusercontent.com/9426603/208845430-2bd20763-0aaf-46ba-9409-237ed7cdd494.png">

```swift
import SwiftUI

@main
struct DJKAdvancedLearningApp: App {

    init() {
        let userSignIn1: Bool = CommandLine.arguments.contains("-UITest_startSigIn")
        let userSignIn2: Bool = ProcessInfo.processInfo.arguments.contains("-UITest_startSigIn")

        let value = ProcessInfo.processInfo.environment["-UITest_startSigIn2"]
        print("User Sign IN1 \(userSignIn1)")
        print("User Sign IN2 \(userSignIn2)")
        print("User Sign IN3 \(value)")
    }

    var body: some Scene {
        WindowGroup {
            UITestingBootcampView()
        }
    }
}
  1. 因为每次机器编写的代码很多都是重复的,有洁癖的可以把一些公共方法提取出来,具体就看每个人怎么写了,这里就不举例了。

参考资料