从0到1,3小时搭建Appium双平台自动化测试框架,效率提升较高_排版测试_千锋教育

来源:千锋教育
发布时间:2026-05-12 10:58:47

千锋教育品牌logo

01 为什么Appium能成为移动端自动化测试的王者?

移动端自动化测试,不是新鲜事。但90%的测试工程师,还在手动点点点。

为什么?因为他们害怕技术门槛。Android用Java,iOS用Swift,两套代码,两套框架,一个人学两门语言?想想就头大。

Appium解决了这个痛点。它用一套API,搞定双平台。

Appium的核心原理

Appium是基于WebDriver协议的扩展。Selenium能做的事,Appium基本都能做。区别在于:Selenium操作浏览器,Appium操作手机。

text
Appium架构:
Client (Python脚本) → HTTP请求 → Appium Server → 设备端
                    ↓
            移动端自动化操作

一个协议,两个平台,三行代码,零门槛。

环境搭建(Windows/Mac通用)

第一步:安装Node.js

Appium Server基于Node.js运行。

bash
# 下载Node.js安装包
https://nodejs.org/en/download/
# 验证安装
node -v
npm -v

第二步:安装Appium

bash
# 全局安装Appium
npm install -g appium

# 安装Appium Doctor(环境检测工具)
npm install -g appium-doctor

# 检测环境
appium-doctor --android
appium-doctor --ios

第三步:安装Python客户端库

bash
pip install Appium-Python-Client
pip install selenium

第四步:配置Android SDK

bash
# 下载Android Studio
# 配置环境变量
export ANDROID_HOME=/path/to/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools

第五步:配置Xcode(仅iOS需要)

bash
# 安装Xcode
# 安装命令行工具
xcode-select --install
# 安装Carthage
brew install carthage

环境验证

python
from appium import webdriver

# 验证环境是否配置成功
desired_caps = {
    "platformName": "Android",
    "deviceName": "emulator-5554",
    "appPackage": "com.android.calculator2",
    "appActivity": ".Calculator"
}

driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
print("环境配置成功!")
driver.quit()

环境搭建不是技术活,是耐心活。3小时搞定,效率提升90%。

02 手把手写出你的第一个Appium测试脚本

环境搭好了,现在开始写代码。别怕,我带你一行一行敲。

连接设备

Android设备连接:

bash
# 查看连接的设备
adb devices

# 输出示例:
List of devices attached
emulator-5554   device

iOS设备连接:

bash
# 查看连接的设备
xcrun xctrace list devices

# 输出示例:
iPhone 13 Pro (15.0) [设备UDID]

配置Desired Capabilities

Desired Capabilities是Appium的核心配置项,告诉Appium要测试什么设备、什么应用。

python
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
import time

# Android配置
android_caps = {
    "platformName": "Android",           # 平台名称
    "platformVersion": "12.0",           # 平台版本
    "deviceName": "emulator-5554",       # 设备名称
    "appPackage": "com.android.calculator2",  # 应用包名
    "appActivity": ".Calculator",        # 应用Activity
    "noReset": True,                     # 不重置应用
    "unicodeKeyboard": True,             # 使用Unicode输入
    "resetKeyboard": True                # 重置键盘
}

# iOS配置
ios_caps = {
    "platformName": "iOS",
    "platformVersion": "15.0",
    "deviceName": "iPhone 13 Pro",
    "app": "/path/to/your/app.app",      # 应用路径
    "automationName": "XCUITest",        # iOS自动化引擎
    "noReset": True
}

# 启动Appium服务
driver = webdriver.Remote('http://localhost:4723/wd/hub', android_caps)

配置写不对,测试两行泪。Desired Capabilities是测试的灵魂。

元素定位实战

Appium支持8种定位方式,和Selenium完全一致。

python
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">1. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">ID定位(最快)</span></div><div style="padding:16px 20px;"><p>element = driver.find_element(AppiumBy.ID, "com.android.calculator2:id/digit_5")</p></div></div>
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">2. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">Class Name定位</span></div><div style="padding:16px 20px;"><p>elements = driver.find_elements(AppiumBy.CLASS_NAME, "android.widget.Button")</p></div></div>
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">3. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">XPath定位(最灵活)</span></div><div style="padding:16px 20px;"><p>element = driver.find_element(AppiumBy.XPATH, "//android.widget.Button[@text='5']")</p></div></div>
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">4. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">Accessibility ID定位(推荐)</span></div><div style="padding:16px 20px;"><p>element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "5")</p></div></div>
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">5. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">Android UIAutomator定位(Android专用)</span></div><div style="padding:16px 20px;"><p>element = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("5")')</p></div></div>
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">6. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">iOS Predicate定位(iOS专用)</span></div><div style="padding:16px 20px;"><p>element = driver.find_element(AppiumBy.IOS_PREDICATE, "label == '5'")</p></div></div>
### 完整测试脚本:计算器自动化

import unittest

from appium import webdriver

from appium.webdriver.common.appiumby import AppiumBy

import time

class CalculatorTest(unittest.TestCase):

@classmethod

def setUpClass(cls):

"""测试前准备"""

desired_caps = {

"platformName": "Android",

"platformVersion": "12.0",

"deviceName": "emulator-5554",

"appPackage": "com.android.calculator2",

"appActivity": ".Calculator",

"noReset": True,

"unicodeKeyboard": True,

"resetKeyboard": True

}

cls.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

cls.driver.implicitly_wait(10)

def test_addition(self):

"""测试加法运算"""

点击数字5

digit_5 = self.driver.find_element(AppiumBy.ID, "com.android.calculator2:id/digit_5")

digit_5.click()

点击加号

plus = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "plus")

plus.click()

点击数字3

digit_3 = self.driver.find_element(AppiumBy.ID, "com.android.calculator2:id/digit_3")

digit_3.click()

点击等号

equals = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "equals")

equals.click()

验证结果

result = self.driver.find_element(AppiumBy.ID, "com.android.calculator2:id/result")

self.assertEqual(result.text, "8")

print(f"5 + 3 = {result.text} ✓")

def test_subtraction(self):

"""测试减法运算"""

点击数字9

digit_9 = self.driver.find_element(AppiumBy.ID, "com.android.calculator2:id/digit_9")

digit_9.click()

点击减号

minus = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "minus")

minus.click()

点击数字4

digit_4 = self.driver.find_element(AppiumBy.ID, "com.android.calculator2:id/digit_4")

digit_4.click()

点击等号

equals = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "equals")

equals.click()

验证结果

result = self.driver.find_element(AppiumBy.ID, "com.android.calculator2:id/result")

self.assertEqual(result.text, "5")

print(f"9 - 4 = {result.text} ✓")

@classmethod

def tearDownClass(cls):

"""测试后清理"""

time.sleep(3)

cls.driver.quit()

if __name__ == "__main__":

unittest.main()

text

<blockquote style="margin:16px 0;padding:12px 20px;background:#F5F5F5;border-left:4px solid #9E9E9E;color:#616161;font-style:italic;border-radius:0 6px 6px 0;">代码不是写出来的,是跑出来的。跑一次,懂一半。</blockquote>

## 03 双平台兼容测试,一套代码通吃

Android和iOS,两个世界。但Appium让它们统一。

### 平台抽象层设计

import os

from appium import webdriver

from appium.webdriver.common.appiumby import AppiumBy

import unittest

class BaseTest:

"""测试基类,封装双平台差异"""

def get_desired_caps(self):

"""根据环境变量获取配置"""

platform = os.getenv("PLATFORM", "Android")

if platform == "Android":

return {

"platformName": "Android",

"platformVersion": "12.0",

"deviceName": "emulator-5554",

"appPackage": "com.example.app",

"appActivity": ".MainActivity",

"noReset": True,

"automationName": "UiAutomator2"

}

else:

return {

"platformName": "iOS",

"platformVersion": "15.0",

"deviceName": "iPhone 13 Pro",

"app": "/path/to/app.app",

"noReset": True,

"automationName": "XCUITest"

}

def find_element_by_text(self, text):

"""跨平台文本定位"""

platform = os.getenv("PLATFORM", "Android")

if platform == "Android":

return self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,

f'new UiSelector().text("{text}")')

else:

return self.driver.find_element(AppiumBy.IOS_PREDICATE,

f'label == "{text}"')

text

<blockquote style="margin:16px 0;padding:12px 20px;background:#F5F5F5;border-left:4px solid #9E9E9E;color:#616161;font-style:italic;border-radius:0 6px 6px 0;">一套代码跑两个平台,不是梦想,是工程。</blockquote>

### 滑动操作封装

class ScrollHelper:

"""滑动操作工具类"""

@staticmethod

def swipe_up(driver, duration=800):

"""向上滑动"""

size = driver.get_window_size()

start_x = size['width'] // 2

start_y = size['height'] * 3 // 4

end_y = size['height'] // 4

driver.swipe(start_x, start_y, start_x, end_y, duration)

@staticmethod

def swipe_down(driver, duration=800):

"""向下滑动"""

size = driver.get_window_size()

start_x = size['width'] // 2

start_y = size['height'] // 4

end_y = size['height'] * 3 // 4

driver.swipe(start_x, start_y, start_x, end_y, duration)

@staticmethod

def swipe_left(driver, duration=800):

"""向左滑动"""

size = driver.get_window_size()

start_x = size['width'] * 3 // 4

start_y = size['height'] // 2

end_x = size['width'] // 4

driver.swipe(start_x, start_y, end_x, start_y, duration)

@staticmethod

def swipe_to_element(driver, element, max_swipes=5):

"""滑动直到元素可见"""

for i in range(max_swipes):

try:

if element.is_displayed():

return True

except:

ScrollHelper.swipe_up(driver)

return False

text

### 实际测试用例:登录功能

class LoginTest(BaseTest, unittest.TestCase):

@classmethod

def setUpClass(cls):

caps = cls.get_desired_caps(cls)

cls.driver = webdriver.Remote('http://localhost:4723/wd/hub', caps)

cls.driver.implicitly_wait(10)

def test_successful_login(self):

"""测试成功登录"""

输入用户名

username_field = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "username")

username_field.send_keys("test_user")

输入密码

password_field = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password")

password_field.send_keys("password123")

点击登录按钮

login_button = self.find_element_by_text("登录")

login_button.click()

验证登录成功

welcome_text = self.find_element_by_text("欢迎回来")

self.assertTrue(welcome_text.is_displayed())

print("登录测试通过 ✓")

def test_failed_login(self):

"""测试登录失败"""

输入错误密码

username_field = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "username")

username_field.send_keys("test_user")

password_field = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password")

password_field.send_keys("wrong_password")

点击登录按钮

login_button = self.find_element_by_text("登录")

login_button.click()

验证错误提示

error_message = self.find_element_by_text("用户名或密码错误")

self.assertTrue(error_message.is_displayed())

print("登录失败测试通过 ✓")

@classmethod

def tearDownClass(cls):

cls.driver.quit()

text

<blockquote style="margin:16px 0;padding:12px 20px;background:#F5F5F5;border-left:4px solid #9E9E9E;color:#616161;font-style:italic;border-radius:0 6px 6px 0;">测试不是检查代码有没有bug,是检查代码有没有灵魂。</blockquote>

## 04 持续集成:让自动化测试跑在CI/CD管道里

手动执行测试?那是上个时代的事。现在,让测试跑在CI/CD管道里。

### 配置Jenkins

// Jenkinsfile

pipeline {

agent any

environment {

PLATFORM = 'Android'

APPIUM_PORT = '4723'

APPIUM_HOST = 'localhost'

}

stages {

stage('Setup Environment') {

steps {

script {

// 启动Appium Server

sh 'nohup appium --port 4723 &'

sleep 5

// 检查设备连接

sh 'adb devices'

}

}

}

stage('Run Tests') {

parallel {

stage('Android Tests') {

environment {

PLATFORM = 'Android'

}

steps {

script {

sh 'python -m pytest android_tests/ --junitxml=android_results.xml'

}

}

}

stage('iOS Tests') {

environment {

PLATFORM = 'iOS'

}

steps {

script {

sh 'python -m pytest ios_tests/ --junitxml=ios_results.xml'

}

}

}

}

}

stage('Report Generation') {

steps {

script {

// 生成Allure报告

sh 'allure generate --clean allure-results'

}

}

}

}

post {

always {

// 清理环境

script {

sh 'pkill appium'

}

}

success {

echo '所有测试通过!'

}

failure {

echo '测试失败,请检查日志'

}

}

}

text

### Docker容器化

Dockerfile

FROM python:3.9-slim

安装Node.js

RUN apt-get update && \

apt-get install -y nodejs npm && \

npm install -g appium

安装Python依赖

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

安装Android SDK

ENV ANDROID_SDK_ROOT=/opt/android-sdk

ENV PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools

复制测试代码

COPY . /app

WORKDIR /app

启动命令

CMD ["python", "-m", "pytest", "tests/"]

text

### 测试报告集成

import allure

import pytest

from appium import webdriver

@allure.feature("登录功能")

class TestLogin:

@allure.story("成功登录")

@allure.severity(allure.severity_level.CRITICAL)

def test_successful_login(self):

"""测试成功登录场景"""

with allure.step("输入用户名"):

username_field = self.driver.find_element(...)

username_field.send_keys("test_user")

with allure.step("输入密码"):

password_field = self.driver.find_element(...)

password_field.send_keys("password123")

with allure.step("点击登录按钮"):

login_button = self.driver.find_element(...)

login_button.click()

with allure.step("验证登录结果"):

result = self.driver.find_element(...)

assert result.text == "登录成功"

截图保存到报告

allure.attach(self.driver.get_screenshot_as_png(),

name="登录成功截图",

attachment_type=allure.attachment_type.PNG)

想系统学习软件测试技术干货?

千锋教育软件测试技术干货课程,零基础入门,企业级项目实战,高薪就业,一站式搞定!

了解课程详情
声明:本站部分稿件版权来源于网络,如有侵犯版权,请及时联系我们。

相关推荐

  • 从0到1,3小时搭建Appium双平台自动化测试框架,效率提升较高_排版测试_千锋教育
  • 从0到1,3小时搭建Appium双平台自动化测试框架,效率提升较高_排版测试_千锋教育
  • API测试_自动发布_千锋教育
  • Selenium vs Playwright:Web自动化测试框架对比_千锋教育
  • 数据库测试:数据验证与数据清理策略_千锋教育
  • 为什么我不用AI写文章?因为写出来的东西“太完美”了_千锋教育