UITesting 界面测试

news/2024/7/23 15:10:20 标签: iOS, Swift, UI, 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)
        }
    }
}

http://www.niftyadmin.cn/n/5098531.html

相关文章

【yolov8系列】yolov8的目标检测、实例分割、关节点估计的原理解析

1 YOLO时间线 这里简单列下yolo的发展时间线,对每个版本的提出有个时间概念。 2 yolov8 的简介 工程链接:https://github.com/ultralytics/ultralytics 2.1 yolov8的特点 采用了anchor free方式,去除了先验设置可能不佳带来的影响借鉴Genera…

智能垃圾桶丨悦享便捷生活

垃圾桶是人们日常生活所必不可少的必需品,它让生活中所产生的垃圾有了一个正确的存放地方。随着生产技术的迅速发展,垃圾桶也得以更新换代。由最初的简单式的圆筒式垃圾桶,到现在出现的感应式垃圾桶、智能语音控制垃圾桶,垃圾桶也…

uniapp使用uQRCode绘制二维码,下载到本地,调起微信扫一扫二维码核销

1.效果 2.在utils文件夹下创建uqrcode.js // uqrcode.js //--------------------------------------------------------------------- // github https://github.com/Sansnn/uQRCode //---------------------------------------------------------------------let uQRCode {…

面向切面:AOP

文章目录 简介相关术语①横切关注点②通知(增强)③切面④目标⑤代理⑥连接点⑦切入点 场景模拟代理模式静态代理动态代理 基于注解的AOP(重点)准备工作各种通知切入点表达式语法重用切入点表达式获取通知的相关信息 环绕通知 切面…

input的一些输入限制

1、input输入框只能输入正整数和0 <el-input v-model"value"onkeyup"value(value.replace(/\D/g,)?:parseInt(value))"placeholder"请输入设备数量" /> 1-1、只能输入大于0的正整数 valuevalue.replace(/^0|[^0-9]/g, ) 2、input输入…

Map中key和value值是否可以为null或空字符串?

Map中key和value值是否可以为null或空字符串? 答案&#xff1a; HashMap既支持分别为空/null&#xff0c;也支持key和value同时为空/nullHashtable不支持key和value存储null&#xff0c;但支持存空字符串** HashMap HashMap是中支持空键和空值的&#xff0c;不论存入null或…

基于MATLAB的图像条形码识别系统(matlab毕毕业设计2)

摘要 &#xff1a; 本论文旨在介绍一种基于MATLAB的图像条形码识别系统。该系统利用计算机视觉技术和图像处理算法&#xff0c;实现对不同类型的条形码进行准确识别。本文将详细介绍系统学习的流程&#xff0c;并提供详细教案&#xff0c;以帮助读者理解和实施该系统。 引言…

嵌入式实时操作系统的设计与开发(调度策略学习)

将调度分为两层&#xff0c;上层为策略&#xff0c;下层为机制&#xff0c;并且采用策略与机制分离的设计原则&#xff0c;可以方便灵活地扩展调度策略&#xff0c;而不改变底层的调度机制。 调度策略就是如何确定线程的CPU、优先级prio等参数&#xff0c;线程是按照FIFO&…