如何让自动化测试更加灵活简洁?
简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题:比如 UI 中的任何更改都需要更新多个文件,测试可能在功能上相互重复,并且支持新功能可能会变成一项耗时且有挑战性的工作来适应现有测试。
页面对象模式如何理顺代码
在为应用程序编写测试时,我们需要在运行各种检查或操作时引用应用程序的视图元素。如果我们总是在编写的每个测试中明确说明元素 ID,这将使我们的代码容易受到 UI 更改的影响:我们必须在使用这些元素的每个测试中更新所有已更改的 ID。
页面对象模式有助于避免这种情况。页面对象模式的理念是将页面(应用程序屏幕)作为一个对象(测试抽象)来呈现,该对象会公布和初始化页面上的所有图形元素,并设置与它们的交互。有关该模式的详细信息可以在此处了解(https://kasperskylab.github.io/Kaspresso/en/Wiki/Page_object_in_Kaspresso/)。
本文中的所有示例都使用我们的开源测试自动化框架 Kaspresso。(https://github.com/KasperskyLab/Kaspresso)为什么不使用Espresso?
首先,Kaspresso使用声明式方法编写测试,这种方法依赖于Kakao,它是Espresso的Kotlin DSL封装器。下面是一个例子:
Espresso
-
@Test -
fun testFirstFeature() { -
onView(withId(R.id.toFirstFeature)) -
.check(ViewAssertions.matches( -
ViewMatchers.withEffectiveVisibility( -
ViewMatchers.Visibility.VISIBLE))) -
onView(withId(R.id.toFirstFeature)).perform(click()) -
}
Kaspresso
-
@Test -
fun testFirstFeature() { -
MainScreen { -
toFirstFeatureButton { -
isVisible() -
click() -
} -
} -
}
其次,在拦截器的帮助下,Kaspresso 避免了测试的不稳定性,从而提高了稳定性。这些拦截器在我们处理异步图形元素或列表时特别有用。
第三,Kaspresso集成了KAutomator,这是一个方便的Kotlin DSL封装器,可用于UI Automator,从而加快UI测试的速度。下面是标准版(右)和加速版(左)UI Automator之间的区别:

除此之外,Kaspresso 允许将测试分解为步骤,类似于手动测试用例的完成方式,并记录每个步骤。如果测试崩溃,日志将帮助你立即查看哪些步骤成功完成,哪些步骤失败。除了日志之外,你还可以访问图形元素的层次结构以及视频、屏幕截图等。Kaspresso 内置的 Android 调试桥 (adb) 支持将帮助你直接使用 Android。Allure集成可清晰显示测试结果。
那么,让我们开始讨论正题。你可以通过下载项目源代码并运行它来重现下面描述的所有步骤。我们将描述 MainActivity 页面并自动化 LoginActivity 测试。结果以及测试可在TECH-tutorial-results分支中找到,因此你可以随时前往那里查看完成的代码。
MainActivity 看起来像这样:

我们创建一个继承自 KScreen 的 MainScreen 对象:
-
object MainScreen : KScreen<MainScreen>() { -
override val layoutId: Int? = null -
override val viewClass: Class<*>? = null -
}
KScreen 实现了页面对象模式,它描述了与测试交互的所有视图元素。
Kaspresso 中的页面对象实现以 layoutId 和 viewClass 变量而闻名,它们可以帮助开发人员立即识别哪个布局文件用于相关页面以及哪个类提供其功能。但手头的任务是讨论页面对象概念本身,因此我们现在将它们设置为 null。
我们使用 Android Studio 中的 UI Automator Viewer 或 Layout Inspector 来查找登录活动按钮的 ID。页面上其余视图元素的标识符可以类似地找到。

主屏幕元素的描述如下所示:
-
object MainScreen : KScreen<MainScreen>() { -
override val layoutId: Int? = null -
override val viewClass: Class<*>? = null -
val titleTextView = KTextView { withId(R.id.title) } -
val simpleActivityButton = KButton { withId(R.id.simple_activity_btn) } -
val wifiActivityButton = KButton { withId(R.id.wifi_activity_btn) } -
val notificationActivityButton = KButton { withId(R.id.notification_activity_btn) } -
val loginActivityButton = KButton { withId(R.id.login_activity_btn) } -
val makeCallActivityButton = KButton { withId(R.id.make_call_activity_btn) } -
val flakyActivityButton = KButton { withId(R.id.flaky_activity_btn) } -
val listActivityButton = KButton { withId(R.id.list_activity_btn) } -
}
现在,我们可以从我们创建的任何测试中引用 MainScreen 对象,并使用此页面的视图元素。
让我们编写第一个测试,它将检查页面上是否有“登录活动”按钮并单击它。
为此,我们创建一个继承自 TestCase 的 LoginActivityTest 类:
-
class LoginActivityTest : TestCase() { -
/** -
* activityScenarioRule is used to invoke MainActivity before running the test. -
* More details on activityScenarioRule are available here: -
* https://developer.android.com/reference/androidx/test/ext/junit/rules/ActivityScenarioRule -
*/ -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
} -
}
![]()

...并创建登录屏幕:
-
object LoginScreen : KScreen<LoginScreen>() { -
override val layoutId: Int? = null -
override val viewClass: Class<*>? = null -
val usernameEditText = KEditText { withId(R.id.input_username) } -
val passwordEditText = KEditText { withId(R.id.input_password) } -
val loginButton = KButton { withId(R.id.login_btn) } -
}
让我们修改 LoginActivityTest 并尝试使用登录名“123456”和密码“123456”获得授权:
-
class LoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
val username = "123456" -
val password = "123456" -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
} -
}
授权后,我们会看到最后一个页面 AfterLoginActivity。

Kaspresso 可以使用Device类从测试内部检查正在显示的活动。我们通过检查授权后设备屏幕上是否出现 AfterLoginActivity 来结束第一个测试:
-
class LoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
val username = "123456" -
val password = "123456" -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
device.activities.isCurrent(AfterLoginActivity::class.java) -
} -
}
这种方法使得动态了解哪些测试字符串与哪些页面交互变得更加困难。添加新的检查和操作可能会使代码难以辨认。因此,我们建议使用页面对象来创建高质量的可扩展测试。
将测试分为几个步骤
任何测试,无论是自动测试还是手动测试,都要遵循一个测试用例--也就是说,测试人员要检查一连串的步骤,以确定页面是否功能齐全。在 step() 函数的帮助下,Kaspresso 将代码分解成多个步骤。步骤还有助于整理测试日志。
要使用步骤,需要在测试中调用 run{} 方法,并在大括号中列出测试中要运行的所有步骤。每个步骤都应在 step() 函数中调用。
让我们试一下:
-
class LoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
run { -
val username = "123456" -
val password = "123456" -
step("Open login screen") { -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
} -
step("Try to login") { -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
} -
step("Check current screen") { -
device.activities.isCurrent(AfterLoginActivity::class.java) -
} -
} -
} -
}
通过这些步骤,标记为“KASPRESSO”的信息级日志如下所示:

如果你对步骤仍有疑问,建议你阅读这些(https://kasperskylab.github.io/Kaspresso/en/Tutorial/Steps_and_sections/)。它还提供了你可能在日志中注意到的之前/之后部分的详细信息。
现在,让我们尝试实施负面测试用例,例如用户输入的登录名或密码少于最小字符数(6 个)。
在创建一组自动测试时,应遵循的规则是为每个测试用例设置一个单独的测试方法。换句话说,我们不会在同一个方法中测试输入无效登录名或密码时的行为,而是在 LoginActivityTest 类中创建单独的方法:
-
@Test -
fun loginUnsuccessfulIfUsernameIncorrect() { -
run { -
val username = "12" -
val password = "123456" -
step("Open login screen") { -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
} -
step("Try to login") { -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
} -
step("Check current screen") { -
device.activities.isCurrent(LoginActivity::class.java) -
} -
} -
}
另一个测试,使用有效的登录名和无效的密码:
-
@Test -
fun loginUnsuccessfulIfPasswordIncorrect() { -
run { -
val username = "123456" -
val password = "1234" -
step("Open login screen") { -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
} -
step("Try to login") { -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
} -
step("Check current screen") { -
device.activities.isCurrent(LoginActivity::class.java) -
} -
} -
}
我建议你在执行第一个测试时重命名它,以便其名称显示我们仅检查是否成功授权。
-
@Test -
fun test()
我们将其更改为:
-
@Test -
fun loginSuccessfulIfUsernameAndPasswordCorrect()
你可能已经注意到,在上面的自动化测试中,用于导航到 LoginActivity 页面并输入登录凭据的字符串会重复。如果能重复使用这些步骤就好了。
使用Scenario
Kaspresso 包含一个名为 Scenario 的工具,它允许将多个步骤组合成有序的操作序列。这在编写重复步骤的测试时非常有用。
让我们创建一个继承自 Scenario 的 LoginScenario 类。为了使其工作,我们需要重写 steps 属性以列出Scenario中的所有步骤。
-
class LoginScenario : Scenario() { -
override val steps: TestContext<Unit>.() -> Unit = { -
step("Open login screen") { -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
} -
step("Try to login") { -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
} -
} -
}
class LoginScenario : Scenario()
更改为:
-
class LoginScenario( -
private val username: String, -
private val password: String -
) : Scenario()
这是生成的Scenario代码:
-
class LoginScenario( -
private val username: String, -
private val password: String -
) : Scenario() { -
override val steps: TestContext<Unit>.() -> Unit = { -
step("Open login screen") { -
MainScreen { -
loginActivityButton { -
isVisible() -
click() -
} -
} -
} -
step("Try to login") { -
LoginScreen { -
usernameEditText { replaceText(username) } -
passwordEditText { replaceText(password) } -
loginButton { click() } -
} -
} -
} -
}
让我们在 LoginActivityTest 测试中使用此Scenario:
-
class LoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun loginSuccessfulIfUsernameAndPasswordCorrect() { -
run { -
step("Try to login with correct username and password") { -
scenario( -
LoginScenario( -
username = "123456", -
password = "123456", -
) -
) -
} -
step("Check current screen") { -
device.activities.isCurrent(AfterLoginActivity::class.java) -
} -
} -
} -
@Test -
fun loginUnsuccessfulIfUsernameIncorrect() { -
run { -
step("Try to login with incorrect username") { -
scenario( -
LoginScenario( -
username = "12", -
password = "123456", -
) -
) -
} -
step("Check current screen") { -
device.activities.isCurrent(LoginActivity::class.java) -
} -
} -
} -
@Test -
fun loginUnsuccessfulIfPasswordIncorrect() { -
run { -
step("Try to login with incorrect password") { -
scenario( -
LoginScenario( -
username = "123456", -
password = "1234", -
) -
) -
} -
step("Check current screen") { -
device.activities.isCurrent(LoginActivity::class.java) -
} -
} -
} -
}
我们研究了一种有利于使用Scenario的案例——在同一页面的不同测试中重复使用相同的步骤。然而,这并不是Scenario的唯一目的。
一个应用程序可以有多个页面,你只能以授权用户的身份访问这些页面。然后,你需要重新描述每个页面的授权步骤。但是,如果你使用Scenario,这将变得非常简单。
目前,AfterLoginActivity 页面在我们登录后打开。让我们为该屏幕编写一个测试。
首先我们创建一个页面对象:
-
object AfterLoginScreen : KScreen<AfterLoginScreen>() { -
override val layoutId: Int? = null -
override val viewClass: Class<*>? = null -
val title = KTextView { withId(R.id.title) } -
}
然后我们添加测试:
-
class AfterLoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
} -
}
我们需要获得授权才能访问该页面。如果没有Scenario,我们将不得不再次重新运行所有步骤:打开主页,单击按钮,输入登录名和密码,然后再次单击按钮。整个过程现在简化为使用 LoginScenario:
-
class AfterLoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
run { -
step("Open AfterLogin screen") { -
scenario( -
LoginScenario( -
username = "123456", -
password = "123456" -
) -
) -
} -
step("Check title") { -
AfterLoginScreen { -
title { -
isVisible() -
} -
} -
} -
} -
} -
}
总而言之,使用Scenario使代码干净、清晰且可重用。如果你想要测试仅授权用户可以访问的页面,则无需再重复大量相同的步骤。重要的是,我们还实现了适当的测试可扩展性。如果 LoginActivity 页面上的 UI 元素的标识符发生更改,则不需要更新测试代码。要使测试再次正常工作,你所需要做的就是修复 LoginScreen。
作为对比,这里是没有以上最佳实践的测试代码。我希望你能像一场噩梦一样忘记这种写作风格。
-
class LoginActivityTest : TestCase() { -
@get:Rule -
val activityRule = activityScenarioRule<MainActivity>() -
@Test -
fun test() { -
val loginActivityButton = KButton { withId(R.id.login_activity_btn) } -
loginActivityButton { -
isVisible() -
click() -
} -
val usernameEditText = KEditText { withId(R.id.input_username) } -
val passwordEditText = KEditText { withId(R.id.input_password) } -
val loginButton = KButton { withId(R.id.login_btn) } -
usernameEditText { replaceText("123456") } -
passwordEditText { replaceText("123456") } -
loginButton { click() } -
device.activities.isCurrent(AfterLoginActivity::class.java) -
pressBack() -
usernameEditText { replaceText("123456") } -
passwordEditText { replaceText("1234") } -
loginButton { click() } -
device.activities.isCurrent(LoginActivity::class.java) -
usernameEditText { replaceText("12") } -
passwordEditText { replaceText("123456") } -
loginButton { click() } -
device.activities.isCurrent(LoginActivity::class.java) -
} -
}
Kaspresso 框架相关链接:
https://github.com/KasperskyLab/Kaspresso
https://kasperskylab.github.io/Kaspresso/en
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取

相关文章:
如何让自动化测试更加灵活简洁?
简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题:比如 UI 中的任何更改都需要更新多个文件,测试可能在功能上相互重复,并且支持新功能可能会变成一项耗时且有挑战性的工作来适应现有测试。 页面对象模式如何理…...
linux 下载依赖慢和访问github代码慢
1 pip install 下载依赖慢,添加清华镜像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 2 git 出现错误 Could not resolve host: github.com 原来是因为github.com没有被主机给解析, 第一步 先 ping 看一下主机地址 …...
奥比中光astra_pro相机使用记录
一、信息获取 1、官网 用于了解产品信息 http://www.orbbec.com.cn/sys/37.html 2、开发者社区 咨询问题下载开发部https://developer.orbbec.com.cn/ 二 、windowvs19 1、相机型号 orbbec_astro_pro 根据对应的型号找到需要的包工具 踩坑1,因为这个相机型号…...
【MindSpore学习打卡】应用实践-计算机视觉-深入解析 Vision Transformer(ViT):从原理到实践
在近年来的深度学习领域,Transformer模型凭借其在自然语言处理(NLP)中的卓越表现,迅速成为研究热点。尤其是基于自注意力(Self-Attention)机制的模型,更是推动了NLP的飞速发展。然而,…...
Debezium系列之:支持在一个数据库connector采集中过滤某些表的删除事件
Debezium系列之:支持在一个数据库connector采集中过滤某些表的删除事件 一、需求二、相关技术三、参数设置四、消费数据一、需求 在一个数据库的connector中采集了多张表,部分表存在数据归档的业务场景,会定期从表中删除历史数据,希望能过滤掉存在数据归档这些表的删除事件…...
SQL Server端口配置指南:最佳实践与技巧
1. 引言 SQL Server通常使用默认端口1433进行通信。为了提高安全性和性能,正确配置SQL Server的端口非常重要。本指南将帮助您了解如何配置和优化SQL Server的端口设置,以满足不同环境和需求。 2. 端口配置基础 2.1 默认端口 SQL Server的默认端口是…...
FastGPT 报错:undefined 该令牌无权使用模型:gpt-3.5-turbo (request id: xxx)
目录 一、FastGPT 报错 二、解决方法 一、FastGPT 报错 进行对话时 FastGPT 报错如下所示。 [Error] 2024-07-01 09:25:23 sse error: undefined 该令牌无权使用模型:gpt-3.5-turbo (request id: xxxxx) {message: 403 该令牌无权使用模型:gpt-3.5-turbo (request id: x…...
springboot系列八: springboot静态资源访问,Rest风格请求处理, 接收参数相关注解
文章目录 WEB开发-静态资源访问官方文档基本介绍快速入门注意事项和细节 Rest风格请求处理基本介绍应用实例注意事项和细节思考题 接收参数相关注解基本介绍应用实例PathVariableRequestHeaderRequestParamCookieValueRequestBodyRequestAttributeSessionAttribute ⬅️ 上一篇…...
# 职场生活之道:善于团结
在职场这个大舞台上,每个人都是演员,也是观众。要想在这个舞台上站稳脚跟,除了专业技能,更要学会如何与人相处,如何团结他人。团结,是职场生存的重要法则之一。 1. 主动团结:多一个朋友&#x…...
go sync包(五) WaitGroup
WaitGroup sync.WaitGroup 可以等待一组 Goroutine 的返回,一个比较常见的使用场景是批量发出 RPC 或者 HTTP 请求: requests : []*Request{...} wg : &sync.WaitGroup{} wg.Add(len(requests))for _, request : range requests {go func(r *Reque…...
基于深度学习的相机内参标定
基于深度学习的相机内参标定 相机内参标定(Camera Intrinsic Calibration)是计算机视觉中的关键步骤,用于确定相机的内部参数(如焦距、主点位置、畸变系数等)。传统的标定方法依赖于已知尺寸的标定板,通常…...
适合金融行业的国产传输软件应该是怎样的?
对于金融行业来说,正常业务开展离不开文件传输场景,一般来说,金融行业常用的文件传输工具有IM通讯、邮件、自建文件传输系统、FTP应用、U盘等,这些传输工具可以基础实现金融机构的文件传输需求,但也存在如下问题&#…...
昇思25天学习打卡营第9天|MindSpore使用静态图加速(基于context的开启方式)
在Graph模式下,Python代码并不是由Python解释器去执行,而是将代码编译成静态计算图,然后执行静态计算图。 在静态图模式下,MindSpore通过源码转换的方式,将Python的源码转换成中间表达IR(Intermediate Repr…...
class类和style内联样式的绑定
这里的绑定其实就是v-bind的绑定,如代码所示,div后面的引号就是v-bind绑定,然后大括号将整个对象括起来,对象内先是属性,属性后接的是变量,这个变量是定义在script中的,后通过这个变量ÿ…...
3033.力扣每日一题7/5 Java
博客主页:音符犹如代码系列专栏:算法练习关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 目录 思路 解题方法 时间复杂度 空间复杂度 Code 思路 首先创建一个与…...
GPT-5:下一代AI如何彻底改变我们的未来
GPT-5 发布前瞻:技术突破与未来展望 随着科技的飞速发展,人工智能领域不断迎来新的突破。根据最新消息,OpenAI 的首席技术官米拉穆拉蒂在一次采访中确认,GPT-5 将在一年半后发布,并描述了其从 GPT-4 到 GPT-5 的飞跃如…...
重载一元运算符
自增运算符 #include<iostream> using namespace std; class CGirl { public:string name;int ranking;CGirl() { name "zhongge"; ranking 5; }void show() const{ cout << "name : "<<name << " , ranking : " <…...
10元 DIY 一个柔性灯丝氛围灯
之前TikTok上特别火的线性氛围灯Augelight刚出来的时候一度卖到80多美金,国内1688也能到400多人民币。 随着各路国内厂商和DIY创客的跟进,功能变多的同时价格一路下滑,虽然有的质感的确感人,但是便宜啊。 甚至关注的up有把成本搞到…...
表单自定义组件 - 可选择卡片SelectCard
import React from react; import styles from ./index.module.less;type OptionsType {/*** 每个item渲染一行,第0项为标题*/labels?: any[];/*** 自定义渲染内容*/label?: string | React.ReactNode;value: any; }; interface IProps {value?: any;onChange?…...
Ubuntu / Debian安装FTP服务
本章教程,记录在Ubuntu中安装FTP服务的具体步骤。FTP默认端口:21 1、安装 pure-ftpd sudo apt-get install pure-ftpd2、修改默认配置 # 与 centos 不同,这里需要在 /etc/pure-ftpd/conf 文件夹下执行下列命令,增加对应配置文件: # 创建 /etc/pure-ftpd/conf/PureDB 文件…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
