自己做的电影网站打开很慢,创办免费企业网站,网龙沧州网站制作,wordpress同步腾讯微博简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题#xff1a;比如 UI 中的任何更改都需要更新多个文件#xff0c;测试可能在功能上相互重复#xff0c;并且支持新功能可能会变成一项耗时且有挑战性的工作来适应现有测试。
页面对象模式如何理…简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题比如 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 : KScreenMainScreen() { 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 : KScreenMainScreen() { 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 activityScenarioRuleMainActivity() Test fun test() { MainScreen { loginActivityButton { isVisible() click() } } } } ...并创建登录屏幕 object LoginScreen : KScreenLoginScreen() { 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 activityScenarioRuleMainActivity() 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 activityScenarioRuleMainActivity() 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 activityScenarioRuleMainActivity() 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: TestContextUnit.() - 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: TestContextUnit.() - 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 activityScenarioRuleMainActivity() 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 : KScreenAfterLoginScreen() { override val layoutId: Int? null override val viewClass: Class*? null val title KTextView { withId(R.id.title) } }
然后我们添加测试 class AfterLoginActivityTest : TestCase() { get:Rule val activityRule activityScenarioRuleMainActivity() Test fun test() { } }
我们需要获得授权才能访问该页面。如果没有Scenario我们将不得不再次重新运行所有步骤打开主页单击按钮输入登录名和密码然后再次单击按钮。整个过程现在简化为使用 LoginScenario class AfterLoginActivityTest : TestCase() { get:Rule val activityRule activityScenarioRuleMainActivity() 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 activityScenarioRuleMainActivity() 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
感谢每一个认真阅读我文章的人礼尚往来总是要有的虽然不是什么很值钱的东西如果你用得到的话可以直接拿走 这些资料对于【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴上万个测试工程师们走过最艰难的路程希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取