【2023最新教程】从0到1构建移动端应用自动化测试(建议收藏)

1562 篇文章 62 订阅
1475 篇文章 54 订阅
本文介绍了在跨境电商公司如何构建自动化测试流程,包括iOS和安卓环境的Appium配置,使用appium-doctor检查环境,编写测试用例,以及利用Appium的核心理念编写单元测试。还展示了如何通过Jenkins实现一键构建与自动执行测试,解决滑动和键盘操作等棘手问题,最终达到跨平台自动化测试的目标。
摘要由CSDN通过智能技术生成

 

背景

公司主业务是做跨境电商的,每次发版本都需要回归测试。大部分固定的业务逻辑没怎么变动,但是耗时耗力。由此,今年我们尝试构建自动化测试。

环境搭建

项目需要集成Appium环境,如果需要在本机执行自动化测试,需要安装Appium相关的环境。安装可以通过appium-doctor来检测appium是否正确配置。目前移动端的环境配置如下:

  1. 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. ###
复制代码
  1. 安卓环境配置:
  • 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. ###
复制代码
  1. 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必需的四个参数automationNameplatformNameplatformVersiondeviceName。 为了能一键构建,在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的一键构建与自动构建。

棘手问题

  1. 滑动手势 有时候程序当前页面所展示的元素需要滑动才显示出来时,就需要模拟滑动的操作。官网上所提到的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
}

复制代码
  1. 键盘操作 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自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等配套学习资源免费分享

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值