UITesting 界面测试

news2025/3/13 10:27:38

1. 创建界面测试视图 UITestingBootcampView.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

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

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.coloradmin.cn/o/1104068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CVE-2021-26084 漏洞分析

基础知识 Velocity .vm 结尾的文件一般为Velocity模板文件$action $action 是 velocity 上下⽂中的⼀个变量,⼀般在进⾏模板渲染前会设置到 context ⾥⾯。$action 是当前访问路由对应的具体 Action 类。$action.xxx 表⽰取对应 Action 类的 xxx 属性值 ${} 和 $!…

Kotlin中的字符串基本操作

字符串定义: val str: String "Hello World"val str1 "Hello World"获取字符串的长度: println(str.length)通过索引方式访问某个字符,索引从0开始: println(str[4])通过for循环迭代字符串: for…

Python-Python高阶技巧:闭包、装饰器、设计模式、多线程、网络编程、正则表达式、递归

版本说明 当前版本号[20231018]。 版本修改说明20231018初版 目录 文章目录 版本说明目录Python高阶技巧闭包简单闭包修改外部函数变量的值实现以下atm取钱的闭包实现了闭包注意事项 装饰器装饰器的一般写法(闭包写法)装饰器的语法糖写法 设计模式单例…

微信小程序中如何使用fontawesome6的免费图标

一、官网下载fontawesome6 Download Font Awesome Free or Pro | Font Awesome 二、使用transfer编码成Base64 transfer打开官网:Online font-face generator — Transfonter 首先先把刚刚下载的fontawesome6解压,将文件夹中的字体上传(点…

发电机组负载测试的必要性

发电机组负载测试是确保发电机组能够在实际运行中稳定工作的重要步骤,负载测试可以模拟发电机组在不同负载条件下的工作情况,评估其性能和稳定性。负载测试可以验证发电机组在不同负载条件下的性能表现,通过模拟实际使用情况评估发电机组的输…

【Flutter】第一篇基础:站在一名web前端开发者的角度看代框架

Flutter Flutter 是一个跨平台的 UI 工具集,它的设计初衷,就是允许在各种操作系统上复用同样的代码,例如 iOS 和 Android,同时让应用程序可以直接与底层平台服务进行交互。如此设计是为了让开发者能够在不同的平台上,…

怎么把m4v转换为mp4?

怎么把m4v转换为mp4?M4V是一种由苹果公司开发的视频文件格式,该格式可以在苹果公司的iTunes和QuickTime软件中播放。M4V格式本质上与MP4格式相似,但M4V通常包括了用于数字版权管理(DRM)的保护措施,以控制该…

【笔记-OrCAD】WARNING(ORCAP-36038)解决办法

问题描述: OrCAD16.6绘制好原理图后,点击“*.dsn”文件可以生成网表,在存放原理图的文件内找到allegro文件夹,用记事本打开netlist.log文件,可以看到具体的警告原因,例如: WARNING(ORCAP-36038)…

优雅而高效的JavaScript—— Class 和模块化

😊博主:小猫娃来啦 😊文章核心:优雅而高效的JavaScript—— Class 和模块化 文章目录 引言Class 的概念和用法Class 的定义Class 的继承Class 的静态方法和属性 模块化的概念和用法模块的导出和导入模块的默认导出和命名导出模块的…

SpringCloud: sentinel链路限流

一、配置文件要增加 spring.cloud.sentinel.webContextUnify: false二、在要限流的业务方法上使用SentinelResource注解 package cn.edu.tju.service;import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockExcept…

CVPR、ICCV、ECCV论文获取

CVPR每年召开,ICCV两年一次 链接地址 ECCV两年一开 链接地址

10. 机器学习-评测指标

Hi,你好。我是茶桁。 之前的课程中,我们学习了两个最重要的回归方法,一个线性回归,一个逻辑回归。也讲解了为什么学习机器学习要从逻辑回归和线性回归讲起。因为我们在解决问题的时候,有限选择简单的假设,越复杂的模型…

十七、文件(1)

本章概要 文件和目录路径 选取路径部分片段路径分析Paths 的增减修改 目录 在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。 打开并读取文件对于大多数编程语言来说是非常常用的,由于 I/O 糟糕的设计以至于很少有人能够在不…

第七章 排序

第七章 排序 概述插入排序交换排序冒泡排序快速排序 选择排序直接选择排序堆排序 归并排序有序序列合并二路归并排序 小试牛刀 概述 排序就是将一组对象按照规定的次序(升序或降序等)重新排列的过程,往往为检索服务相同键值的两个记录在排序…

索引背后的数据结构——B+树

为什么要使用B树? 可以进行数据查询的数据结构有二叉搜索树、哈希表等。对于前者来说,树的高度越高,进行查询比较的时候访问磁盘的次数就越多。而后者只有在数据等于key值的时候才能进行查询,不能进行模糊匹配。所以出现了B树来解…

SQL数据库管理工具RazorSQL mac中文版特点与功能

RazorSQL mac是一款功能强大的SQL数据库管理工具,它支持多种数据库,包括MySQL、Oracle、Microsoft SQL Server、SQLite、PostgreSQL等。 RazorSQL mac 软件特点和功能 多种数据库支持:RazorSQL支持多种数据库,用户可以通过一个工…

故障预测与健康管理(PHM)在工业领域的发展前景

故障预测与健康管理(PHM)作为一种关键技术,已经在工业领域引起了广泛的关注和应用。PHM利用传感器、数据科学和智能算法等技术手段,通过实时监测和分析设备和系统的状态,提前发现潜在故障,并采取适当的维修…

制作linux系统内部yum源仓库

需求说明 制作内网linux系统yum源仓库,比较简单的方式就是添加系统镜像,此种yum配置方式可参考文章 https://blog.csdn.net/d1240673769/article/details/108477661 如果无法提供系统镜像,那该如何创建内网的yum源仓库呢?本文提…

互联网Java工程师面试题·Java 总结篇·第六弹

目录 56、TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素? 57、Thread 类的 sleep()方法和对象的 wait()方法都可以让线程暂停执行,它们有什么区别? 58、线程的 sleep()方法和 yield()方法有什…

在nodejs中实现双重身份验证机制

在nodejs中实现双重身份验证机制 双重身份验证(Two-factor authentication)是一种安全机制,它要求用户提供两种不同的身份验证因素来访问他们的帐户:密码和发送到他们的移动设备的验证码。在本文中,我们将一步步通过使用speakeasy在nodejs中实…