如何让自动化测试更加灵活简洁?
简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题:比如 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 文件…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...