UITesting 界面测试

1. 创建界面测试视图 UITestingBootcampView.swift

Swift 复制代码
import SwiftUI

/// 界面测试 ViewModel
class UITestingBootcampViewModel: ObservableObject{
    let placeholderText: String = "Add name here..."
    @Published var textFiledText: String = ""
    @Published var currentUserIsSignedIn: Bool
    
    init(currentUserIsSignedIn: Bool) {
        self.currentUserIsSignedIn = currentUserIsSignedIn
    }
    
    /// 登录按钮处理,模拟测试
    func signUpButtonPressed() {
        guard !textFiledText.isEmpty else { return }
        currentUserIsSignedIn = true
    }
}

/// 界面测试
struct UITestingBootcampView: View {
    @StateObject private var viewModel: UITestingBootcampViewModel
    
    init(currentUserIsSignedIn: Bool) {
        //"_" 下划线表示: 正在引用状态对象  wrappedValue: 表示包装值
        _viewModel = StateObject(wrappedValue: UITestingBootcampViewModel(currentUserIsSignedIn: currentUserIsSignedIn))
        print("description: " + currentUserIsSignedIn.description)
    }
    
    var body: some View {
        ZStack {
            LinearGradient(
                gradient: Gradient(colors: [Color.blue, Color.black]),
                startPoint: .topLeading,
                endPoint: .bottomTrailing)
            .ignoresSafeArea()
            
            ZStack {
                if viewModel.currentUserIsSignedIn {
                    SignedInHomeView()
                        .frame(maxWidth:.infinity, maxHeight: .infinity)
                        .transition(.move(edge: .trailing))
                }
                
                if !viewModel.currentUserIsSignedIn{
                    signUpLayer
                        .frame(maxWidth:.infinity, maxHeight: .infinity)
                        .transition(.move(edge: .leading))
                }
            }
        }
    }
}

/// 扩展 View
extension UITestingBootcampView{
    // 登录布局
    private var signUpLayer: some View{
        VStack {
            TextField(viewModel.placeholderText, text: $viewModel.textFiledText)
                .font(.headline)
                .padding()
                .frame(height: 55)
                .background(Color.white)
                .cornerRadius(10)
                .accessibilityIdentifier("SignUpTextField")
            Button {
                withAnimation(.spring()) {
                    viewModel.signUpButtonPressed()
                }
            } label: {
                Text("Sign Up")
                    .font(.headline)
                    .padding()
                    .frame(maxWidth: .infinity)
                    .foregroundColor(.white)
                    .background(Color.accentColor)
                    .cornerRadius(10)
            }
            .accessibilityIdentifier("SignUpButton")
        }
        .padding()
    }
}

/// 登录入主页
struct SignedInHomeView: View{
    @State private var showAlert: Bool = false
    
    var body: some View{
        NavigationView {
            VStack(spacing: 20) {
                Button {
                    showAlert.toggle()
                } label: {
                    Text("Show welcome alert!")
                        .font(.headline)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .foregroundColor(.white)
                        .background(Color.red)
                        .cornerRadius(10)
                }
                .accessibilityIdentifier("ShowAlertButton")
                .alert(isPresented: $showAlert) {
                    return Alert(title: Text("Welcome to the app!"))
                }
                
                NavigationLink(destination: Text("Destination")) {
                    Text("Navigate")
                        .font(.headline)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .foregroundColor(.white)
                        .background(Color.blue)
                        .cornerRadius(10)
                }
                .accessibilityIdentifier("NavigationLinkToDestination")
            }
            .padding()
            .navigationTitle("Welcome")
        }
    }
}

struct UITestingBootcampView_Previews: PreviewProvider {
    static var previews: some View {
        UITestingBootcampView(currentUserIsSignedIn: true)
    }
}

2. 测试类及自动操作效果图

2.1 添加界面测试类

添加方法与单元测试类添加方法一样,注意的是 Test 栏下 选择 UI Testing Bundle。

创建界面测试文件 UITestingBootcampView_UITests.swift

Swift 复制代码
import XCTest

// 《Testing Swift》 测试书籍
// 书籍网址: https://www.hackingwithswift.com/store/testing-swift
// Naming Structure: test_UnitOfWork_StateUnderTest_ExpectedBehavior -  结构体命名: 测试_工作单元_测试状态_预期的行为
// Naming Structure: test_[struct]_[ui component]_[expected result] - 测试_[结构体]_[界面 组件]_[预期结果 预期值]
// Testing Structure: Given, When, Then - 测试结构: 给定,什么时候,然后

final class UITestingBootcampView_UITests: XCTestCase {
    let app = XCUIApplication()
    
    override func setUpWithError() throws {
        continueAfterFailure = false
        //app.launchArguments = ["-UITest_startSignedIn"]
        //app.launchEnvironment = ["-UITest_startSignedIn2" : "true"]
        app.launch()
    }
    
    override func tearDownWithError() throws {
    }
    
    /// 测试_界面测试视图_注册按钮_不能登录
    func test_UITestingBootcampView_signUpButton_shouldNotSignIn(){
        // Given
        signUpAndSignIn(shouldTypeOnKeyboard: false)
    
        // When
        let navBar = app.navigationBars["Welcome"]
        
        // Then 断言导航栏不存在
        XCTAssertFalse(navBar.exists)
    }
    
    /// 测试_界面测试视图_注册按钮_能登录
    func test_UITestingBootcampView_signUpButton_shouldSignIn(){
        // Given
         signUpAndSignIn(shouldTypeOnKeyboard: true)
        
        // When
        let navBar = app.navigationBars["Welcome"]
        
        // Then 断言导航栏存在
        XCTAssertTrue(navBar.exists)
    }
    
    /// 测试_登录到主页_显示警告按钮_能显示警告弹框
    func test_SignedInHomeView_showAlertButton_shouldDisplayAlert(){
        // Given
        signUpAndSignIn(shouldTypeOnKeyboard: true)
        
        // When
        tapAlertButton(shouldDismissAlert: false)
        
        // Then
        // 查找第一个警告框
        let alert = app.alerts.firstMatch
        // 警告框是否存在
        XCTAssertTrue(alert.exists)
    }
    
    /// 测试_登录到主页_显示警告按钮_能显示并关闭警告弹框
    func test_SignedInHomeView_showAlertButton_shouldDisplayAndDismissAlert(){
        // Given
        signUpAndSignIn(shouldTypeOnKeyboard: true)
        
        // When
        tapAlertButton(shouldDismissAlert: true)
        
        // Then 断言
        // sleep(1)
        // 查找第一个警告框,存不存在
        let alertExists = app.alerts.firstMatch.waitForExistence(timeout: 5)
        XCTAssertFalse(alertExists)
    }
    
    /// 测试_登录到主页_导航连接器_能够跳转到
    func test_SignedInHomeView_navigationLinkToDestination_shouldNavigateToDestination(){
        // Given
        signUpAndSignIn(shouldTypeOnKeyboard: true)
        
        // When
        tapNavigationLink(shouldDismissDestination: false)
        //then 断言文本是否存在
        let destinationText = app.staticTexts["Destination"]
        XCTAssertTrue(destinationText.exists)
    }
    
    /// 测试_登录到主页_导航连接到目标视图_能显示并关闭警告弹框
    func test_SignedInHomeView_navigationLinkToDestination_shouldNavigateToDestinationAndGoBack(){
        // Given
         signUpAndSignIn(shouldTypeOnKeyboard: true)
        
        // When
        tapNavigationLink(shouldDismissDestination: true)
        
        //then 断言 Home View 导航栏是否存在
        let navBar = app.navigationBars["Welcome"]
        // 导航栏是否存在
        XCTAssertTrue(navBar.exists)
    }
    
    /// 测试_登录到主页_导航连接到目标视图_能显示并关闭警告弹框
//    func test_SignedInHomeView_navigationLinkToDestination_shouldNavigateToDestinationAndGoBack2(){
//        // Given
//
//        // When
//        tapNavigationLink(shouldDismissDestination: true)
//
//        //then 断言 Home View 导航栏是否存在
//        let navBar = app.navigationBars["Welcome"]
//        // 导航栏是否存在
//        XCTAssertTrue(navBar.exists)
//    }
}

// MARK: 也许可能函数
extension UITestingBootcampView_UITests{
    
    /// 提取公共部分代码 注册并登录,键盘输入: trur false
    func signUpAndSignIn(shouldTypeOnKeyboard: Bool){
        let textfield = app.textFields["SignUpTextField"]
        textfield.tap()
        
        if shouldTypeOnKeyboard {
            let keyA = app.keys["A"]
            keyA.tap()
            let keyA1 = app.keys["a"]
            keyA1.tap()
            keyA1.tap()
            
            let returnButton = app.buttons["Return"]
            returnButton.tap()
        }
        
        let signUpButton = app.buttons["SignUpButton"]
        signUpButton.tap()
    }
    
    /// 提取提示框按钮,是否关闭提示框
    func tapAlertButton(shouldDismissAlert: Bool){
        let showAlertButton = app.buttons["ShowAlertButton"]
        showAlertButton.tap()
        
        if shouldDismissAlert{
            // 查找第一个警告框 中的 OK 按钮
            let alertOKButton = app.alerts.firstMatch.buttons["OK"]
            // sleep(1)
            // 等待至少 5 秒,让 alertOKButton 存在
            let alertPKButtonExists =  alertOKButton.waitForExistence(timeout: 5)
            // 断言按钮是否存在
            XCTAssertTrue(alertPKButtonExists)
            alertOKButton.tap()
        }
    }
    
    /// 提取导航连接器 ,是否关闭目标视图
    func tapNavigationLink(shouldDismissDestination: Bool){
        // 导航器按钮
        let navLinkButton = app.buttons["NavigationLinkToDestination"]
        navLinkButton.tap()
        
        if shouldDismissDestination{
            // 返回按钮
            let backButton = app.navigationBars.buttons["Welcome"]
            backButton.tap()
        }
    }
}

2.2 点击 test_UITestingBootcampView_signUpButton_shouldNotSignIn 测试方法前 方形运行按钮,得到测试效果,如图:

2.3 如上测试方法 test_SignedInHomeView_showAlertButton_shouldDisplayAlert 效果图:

2.4 其他测试方法操作,如上一致。

3. 从 App 启动类中加载界面测试 View,界面测试时,App 启动类中也需要添加界面测试 View

3.1 可配启动参数,菜单栏点击 Product -> Scheme -> Edit Scheme... -> Run 选项卡页添加参数,如图:

3.2 获取配置参数,加载测试界面视图 SwiftfulThinkingAdvancedLearningApp.swift

Swift 复制代码
import SwiftUI

@main
struct SwiftfulThinkingAdvancedLearningApp: App {
    let currentUserIsSignedIn: Bool
    
    init() {
        // 获取启动参数,判断是否有此字符串: UITestingBootcampView
        // let userIsSignedIn:Bool = CommandLine.arguments.contains("-UITest_startSignedIn") ? true : false
        let userIsSignedIn:Bool = ProcessInfo.processInfo.arguments.contains("-UITest_startSignedIn") ? true : false
        // 获取环境变量配置值
        // let value = ProcessInfo.processInfo.environment["-UITest_startSignedIn2"]
        // let userIsSignedIn:Bool =  value == "true" ? true : false
        self.currentUserIsSignedIn = userIsSignedIn
        //print("USER IS SIGNED IN: \(userIsSignedIn)")
        //print("USER IS SIGNED IN2: \(value ?? "")")
    }
    
    var body: some Scene {
        WindowGroup {
            UITestingBootcampView(currentUserIsSignedIn: self.currentUserIsSignedIn)
        }
    }
}
相关推荐
MediaTea3 小时前
七次课掌握 Photoshop:选区与抠图
ui·photoshop
DisonTangor5 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -5 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
不秃头的UI设计师7 小时前
UI界面设计入门:打造卓越用户体验
ui·ux·ui设计
未来的嗒嘀嗒8 小时前
Axure是什么软件?全方位解读助力设计入门
ui·photoshop
wrx繁星点点9 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
SoraLuna18 小时前
「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
macos·ui·harmonyos
2401_8658548821 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
martian6651 天前
QT开发:掌握现代UI动画技术:深入解析QML和Qt Quick中的动画效果
开发语言·c++·qt·ui
初九之潜龙勿用1 天前
C#结合JS解决Word添加无效位图导致进程停滞的问题
javascript·ui·c#·word·asp.net