背景
公司主业务是做跨境电商的,每次发版本都需要回归测试。大部分固定的业务逻辑没怎么变动,但是耗时耗力。由此,今年我们尝试构建自动化测试。
环境搭建
项目需要集成Appium环境,如果需要在本机执行自动化测试,需要安装Appium相关的环境。安装可以通过appium-doctor来检测appium是否正确配置。目前移动端的环境配置如下:
- iOS环境配置:
- Xcode
- Carthage (可使用brew安装, brew install Carthage)
- Node iOS检测环境是否配置成功(必须配置项目):
➜ ~ appium-doctor --ios
info AppiumDoctor Appium Doctor v.1.16.0
info AppiumDoctor ### Diagnostic for necessary dependencies starting ###
info AppiumDoctor ✔ The Node.js binary was found at: /usr/local/bin/node
info AppiumDoctor ✔ Node version is 14.17.0
info AppiumDoctor ✔ Xcode is installed at: /Applications/Xcode.app/Contents/Developer
info AppiumDoctor ✔ Xcode Command Line Tools are installed in: /Applications/Xcode.app/Contents/Developer
info AppiumDoctor ✔ DevToolsSecurity is enabled.
info AppiumDoctor ✔ The Authorization DB is set up properly.
info AppiumDoctor ✔ Carthage was found at: /usr/local/bin/carthage. Installed version is: 0.36.0
info AppiumDoctor ✔ HOME is set to: /Users/taohongyu
info AppiumDoctor ### Diagnostic for necessary dependencies completed, no fix needed. ###
复制代码
- 安卓环境配置:
- AndroidStudio
- ANDROID_HOME(环境变量配置)
- JAVA_HOME(环境变量配置)
安卓检测是否配置成功:
➜ ~ appium-doctor --android
info AppiumDoctor Appium Doctor v.1.16.0
info AppiumDoctor ### Diagnostic for necessary dependencies starting ###
info AppiumDoctor ✔ The Node.js binary was found at: /usr/local/bin/node
info AppiumDoctor ✔ Node version is 14.17.0
info AppiumDoctor ✔ ANDROID_HOME is set to: /Users/taohongyu/Library/Android/sdk
info AppiumDoctor ✔ JAVA_HOME is set to: /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home
info AppiumDoctor Checking adb, android, emulator
info AppiumDoctor 'adb' is in /Users/taohongyu/Library/Android/sdk/platform-tools/adb
info AppiumDoctor 'android' is in /Users/taohongyu/Library/Android/sdk/tools/android
info AppiumDoctor 'emulator' is in /Users/taohongyu/Library/Android/sdk/emulator/emulator
info AppiumDoctor ✔ adb, android, emulator exist: /Users/taohongyu/Library/Android/sdk
info AppiumDoctor ✔ 'bin' subfolder exists under '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'
info AppiumDoctor ### Diagnostic for necessary dependencies completed, no fix needed. ###
复制代码
- Appium环境配置:
- Appium Desktop or Appium Server(Getting Started - Appium)
具体配置过程可参考: Setup Appium on MacOS for Android and iOS Automation Appium Big Sur Update | Medium
编写测试用例
Appium 的核心理念之一是,你不应该为了测试而改变被测的应用程序。出自:appium.io/docs/cn/wri…
我们编写用例,其实就是编写单元测试代码。一个用例即一个单元或一个方法;因此,它的输入与输出是独立的。我在实现过程中,选择将应用的底部Tab作为用例的起点与终点。简单说,一条用例的执行是从底部的Tab开始,执行结束后,返回至底部Tab。
比如:选择地址的用例。 操作步骤: 1.进入Account,选择切换国家至”Aruba”。 2.进入购物车,添加商品,点击下单。
结果: 地址显示正确。
@Test
fun selectAddress() {
val case = allCases.case(PlaceOrderTestCaseIds.SelectAdrress)
/// 切换至未知国家, 选择AccountTab
tabbarActor.selectAt(LITBTab.Account)
accountActor.changeCountry("Aruba")
tabbarActor.selectAt(LITBTab.Cart)
cartActor.deleteAll()
cartActor.onRecommendationProductAddToCart()
cartActor.onCheckoutToPreOrder()
val isLegal = checkoutActor.isSelectedAddressLegal()
/// 返回值Tab页
checkoutActor.backToRoot()
assertTestCase(case, isLegal, Error("选择了不可用的地址!"))
}
复制代码
App是以底部Tabbar为主的页面结构,每个Tab具有导航栏的结构;因此,在进行页面操作时,对底部的Tab和导航栏功能进行了封装。
Tabbar结构选中要操作的某个Tab
class TabbarActor: Actor {
private val tabbarItems get() = driver.findViewsById(HomeViewId.TabBar)
...
选中某个Tab
fun selectAt(tab: LITBTab) {
val isSelected = tabbarItemsSelected[tab.index]?:false
var sleep: SleepDuration = SleepDuration.Regular
if (!isSelected) {
sleep = SleepDuration.Medium
}
if (Emulator.isAndroid()) {
/// 购物车Tab调取接口较多,选中时,应给足时长。
if (tab == LITBTab.Cart || tab == LITBTab.Account) {
tabbarItems?.elementAtOrNull(tab.index)?.clickThen(SleepDuration.Medium)
} else {
tabbarItems?.elementAtOrNull(tab.index)?.clickThen(sleep)
}
} else {
val tabs = listOf<String>(HomeViewId.tabHome, HomeViewId.tabCategory, HomeViewId.tabCommunity, HomeViewId.tabCart, HomeViewId.tabAccount)
driver.clickBy(tabs.elementAtOrNull(tab.index)?:"", sleep.seconds)
}
/// 存储 当前选中的状态
tabbarItemsSelected[tab.index] = true
}
}
复制代码
导航栏返回操作
open class NavigationActor: Actor {
private val back: MobileElement?
get() = driver.findViewById(NavigationViewId.Back)
private val ivBack: MobileElement?
get() = driver.findViewById(NavigationViewId.IVBack)
private val grayBack: MobileElement?
get() = driver.findViewById(NavigationViewId.GrayBack)
private val backBtn get() = driver.findViewById(NavigationViewId.BackBTN)
private val whiteBack get() = driver.findViewById(NavigationViewId.WhiteBack)
fun goNavigationBack(): Boolean {
val all = mutableListOf(back, ivBack, grayBack, backBtn, whiteBack)
val backItems = all.filterNotNull()
if (backItems.isNotEmpty()) {
backItems.firstOrNull()?.clickThen()
return true
} else {
return false
}
}
/*
* 返回至最底部*/
fun backToRoot() {
while (goNavigationBack()) {
println("返回按钮点击!")
}
return
}
}
复制代码
每一步操作都需要时间,尤其是涉及网络请求的交互。因此,我们的操作步骤(click,setValue等事件)每执行一次,就需要让线程睡眠几秒或几十秒,等待视图创建或请求的返回。
/*通过Name - 点击*/
fun <MobileElement : WebElement?> AppiumDriver<MobileElement>.clickByName(name: String, sleep: Int = SleepDuration.Regular.seconds): MobileElement? {
val element: MobileElement? = this.findViewByName(name)
element?.click()
/// 线程睡眠
Util.sleep(sleep)
return element
}
public class Util {
/**
* 线程等待timeSecond 秒
*/
public static void sleep(int timeSecond) {
try {
Thread.sleep(timeSecond * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
除去通用的操作,剩余的操作都是与测试的业务相关。但基本每条测试用例都是这样的一个执行过程:入口(选择某个Tab) - 用例执行步骤 - 获取到期望值 - 返回到最底部 - 断言结果
。这里有个细节,返回到最底部需要在断言结果前执行;是因为,每条用例执行完后应该尽可能的不影响App的状态(如:当前处在哪个业务页面,登入与登录状态等等)。这样是便于下一条用例的执行。
Jenkins一键构建
我们期望自动化的用例能够通过Jenkins部署在服务器上,固定时段自动执行。每天只需查看结果和报告。因此我们通过Jenkins的日程表定时构建,配置了日程表,项目即定时执行了。
当我们在运行Appium时,需要配置DesiredCapabilities。这样Appium才能知道,此时使用哪个测试框架,测试哪个App。DesiredCapabilities必需的四个参数automationName
,platformName
,platformVersion
,deviceName
。 为了能一键构建,在Jenkins上配置了个可选参数Platform,最后在Build的时候,依据当前Platform不同,生成不同的配置文件,最后在执行测试。 安卓平台生成配置文件的Shell脚本:
# 0.初始化配置
# ${Platform} 参数从Jenkins 读取
rm ./src/main/cmd/Platform.yml
rm ./*.out
echo "当前配置文件目录如下:"
ls ./src/main/cmd/
echo "正在执行的配置文件目录如下:"
cp ./src/main/cmd/Platform_android.yml ./src/main/cmd/Platform.yml
ls ./src/main/cmd/
# 读取Jenkins Job的配置
echo "buildNumber: ${BUILD_NUMBER}
gitBranch: ${GIT_BRANCH}
enableWebhook: ${EnableWebhook}
webhookURL: ${WebhookURL}"> src/main/cmd/jenkins_configure.yml
# 复制打包好的apk文件至指定测试目录
android_apk_path="UI_Android_Packing/litb/build/outputs/apk/**/**/*.apk"
cp ~/Downloads/workspace/${android_apk_path} src/test/resources/xxxx.apk
# 1.启动Appium
appium &
# 2.启动模拟器
# 进入emulator目录
$ANDROID_HOME/emulator/emulator -avd 'Pixel_XL_API_31'
复制代码
执行用例测试Shell脚本
nohup ./auto_start_android.sh &
sleep 30
./gradlew cleanProject
./gradlew universalTest
复制代码
至此,我们便完成了Jenkins的一键构建与自动构建。
棘手问题
- 滑动手势 有时候程序当前页面所展示的元素需要滑动才显示出来时,就需要模拟滑动的操作。官网上所提到的Swipe操作与Scroll操作均 使用起来不是特别顺手。最后在Appium的GitHub issue里找到了通过Actions API,来模拟滑动完美的解决了问题。并对AppiumDriver进行了滑动操作的封装。
/*
* 垂直方向上的scroll
* */
fun AppiumDriver<MobileElement>.verticalScrollToFind(scrollView: MobileElement?, id: String, yOffset: Int, maxOffset: Int, extented: Boolean = false): List<MobileElement>? {
if (scrollView == null) {
return null
}
var driverYOffset = yOffset
var driverMaxOffset = maxOffset
if (this is IOSDriver) {
driverYOffset = yOffset / 10
driverMaxOffset = maxOffset / 10
}
var f: Int = 0
var v: List<MobileElement>? = mutableListOf()
while ((driverMaxOffset < 0 && f > driverMaxOffset) || (driverMaxOffset > 0 && f < driverMaxOffset)) {
v = findViewsById(id)
if (!v.isNullOrEmpty()) {
if (extented) {
/// 返回前,多执行一次滚动,防止被遮挡
Actions(this).clickAndHold(scrollView).moveByOffset(0, driverYOffset).perform()
}
return v
}
Actions(this).clickAndHold(scrollView).moveByOffset(0, driverYOffset).perform()
f += driverYOffset
Util.sleep(SleepDuration.Regular.seconds)
}
if (extented) {
/// 返回前,多执行一次滚动,防止被遮挡
Actions(this).clickAndHold(scrollView).moveByOffset(0, driverYOffset).perform()
}
return v
}
复制代码
- 键盘操作 Appium提供的键盘API实测未响应。目前仅通过executeScript的方式来执行键盘操作。如搜索按钮。
/*
* 键盘搜索按钮*/
fun <MobileElement : WebElement?> AppiumDriver<MobileElement>.KeyboardSearch(sleep: Int = 5) {
if (this is AndroidDriver<MobileElement>) {
this.executeScript("mobile: performEditorAction", mutableMapOf("action" to "search"))
Util.sleep(sleep)
} else {
this.clickByName("Search")
Util.sleep(sleep)
}
}
复制代码
终
项目基于Appium,使用TestNG和Jenkins完成了自动化构建,实现了:
- 跨平台(目前仅Android与iOS)
- Jenkins 一键构建,参数化构建。
- 日志企业微信推送与Jenkins报告
- App端的部分测试用例已采用自动构建测试
最后,项目通过Jenkins在服务器上默默的执行;在企业微信群和Jenkins上里查看报告就完了。
企业微信群的报告(某个模块)
Jenkins上的TestNG 报告(部分截图)
最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
下方这份完整的软件测试视频学习教程已经上传CSDN官方认证的二维码,朋友们如果需要可以自行免费领取 【保证100%免费】
这些资料,对于想进阶【自动化测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助……基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等配套学习资源免费分享