Android - 自动化测试框架Appium

122 阅读7分钟

Appium的整体流程框架

将 Appium 的整个工作机制用一个详细的流程图串联起来:

graph TD
    A[Appium Client] -->|1. 发送自动化指令| B[Appium Server]
    
    B -->|2. 解析指令| C{命令类型}
    
    C -->|3.1 元素查找| D[UiAutomator2]
    C -->|3.2 操作执行| E[事件注入]
    C -->|3.3 状态获取| F[状态监控]
    
    D -->|4.1 启动| G[Bootstrap.jar]
    G -->|4.2 建立连接| H[TCP Server]
    
    D -->|5.1 获取| I[AccessibilityService]
    I -->|5.2 解析| J[UI层级树]
    J -->|5.3 查找| K[目标元素]
    
    E -->|6.1 构造事件| L[MotionEvent/KeyEvent]
    L -->|6.2 注入| M[InputManager]
    M -->|6.3 执行| N[Android System]
    
    F -->|7.1 监控| O[ActivityManager]
    F -->|7.2 监控| P[ViewMonitor]
    F -->|7.3 监控| Q[PerformanceMonitor]
    
    K -->|8.1 返回| R[元素信息]
    N -->|8.2 返回| S[操作结果]
    O -->|8.3 返回| T[状态数据]
    
    R -->|9.1 响应| B
    S -->|9.2 响应| B
    T -->|9.3 响应| B
    
    B -->|10. 返回结果| A

详细流程说明:

  1. 初始化阶段
  • Appium Client 初始化并连接到 Appium Server
  • Server 启动 Bootstrap.jar
  • 建立与 UiAutomator2 的通信
  1. 命令处理阶段
  • Client 发送自动化指令
  • Server 解析指令类型
  • 根据类型分发到不同处理模块
  1. 元素查找流程
  • 通过 AccessibilityService 获取 UI 层级
  • 解析层级树结构
  • 根据选择器匹配目标元素
  1. 操作执行流程
  • 构造相应的事件(触摸/按键)
  • 通过 InputManager 注入事件
  • 系统执行相应操作
  1. 状态监控流程
  • ActivityManager 监控页面状态
  • ViewMonitor 监控元素状态
  • PerformanceMonitor 监控性能数据
  1. 结果返回流程
  • 收集操作结果
  • 获取状态数据
  • 返回给 Appium Client

关键节点说明:

  1. 通信层
// Appium Server 与 Client 通信
WebDriverProtocol.handleRequest(Request request)

// Server 与 Bootstrap.jar 通信
BootstrapServer.handleCommand(Command command)
  1. 元素处理层
// 元素查找
UiAutomator.findElement(Selector selector)

// 元素操作
ElementOperation.performAction(UiElement element, Action action)
  1. 事件处理层
// 事件注入
EventInjector.inject(InputEvent event)

// 状态监控
StateMonitor.collectState()
  1. 结果处理层
// 结果封装
ResponseBuilder.build(OperationResult result)

// 结果返回
Server.sendResponse(Response response)

数据流转示例:

// 1. 点击操作的完整流程
public void clickElement(String elementId) {
    // 查找元素
    UiElement element = UiAutomator.findElement(elementId);
    
    // 构造点击事件
    MotionEvent event = EventBuilder.buildTouchEvent(
        element.getCenter()
    );
    
    // 注入事件
    InputManager.injectEvent(event);
    
    // 等待操作完成
    StateMonitor.waitForIdle();
    
    // 返回结果
    return new OperationResult(SUCCESS);
}

// 2. 获取文本的完整流程
public String getText(String elementId) {
    // 查找元素
    UiElement element = UiAutomator.findElement(elementId);
    
    // 获取文本
    String text = element.getText();
    
    // 验证结果
    StateMonitor.validateElementState(element);
    
    // 返回文本
    return text;
}

这个流程展示了 Appium 如何将各个组件协同工作,实现自动化测试的完整功能。每个环节都有其特定的职责,共同保证了自动化测试的可靠性和效率。

Appium的基本使用

一、Appium 是什么?

想象 Appium 是一个"机器人测试员":

  • 它可以像人一样操作手机
  • 能自动点击、滑动、输入文字
  • 还能检查界面是否正确
  • 24小时不知疲倦地工作
graph TD
    A[Appium机器人] --> B[点击操作]
    A --> C[滑动操作]
    A --> D[输入文字]
    A --> E[截图验证]
    A --> F[自动化测试]

二、基本操作示例

# 假设我们在测试一个购物App
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy

class ShoppingTest:
    def test_buy_product(self):
        # 1. 打开App(就像人点击App图标一样)
        self.driver.launch_app()
        
        # 2. 搜索商品(像人一样在搜索框输入)
        search_box = self.driver.find_element(
            MobileBy.ID, "search_input")
        search_box.send_keys("iPhone")
        
        # 3. 点击搜索按钮(像人用手指点击)
        search_button = self.driver.find_element(
            MobileBy.ID, "search_button")
        search_button.click()
        
        # 4. 选择商品(像人滑动查看并点击)
        product = self.driver.find_element(
            MobileBy.XPATH, "//android.widget.TextView[@text='iPhone 15']")
        product.click()
        
        # 5. 加入购物车(像人点击加入购物车)
        add_cart = self.driver.find_element(
            MobileBy.ID, "add_to_cart")
        add_cart.click()

三、模拟真实场景

# 模拟用户真实购物流程
def test_shopping_flow(self):
    # 1. 先等等广告关闭(像人要等广告)
    self.wait_for_element("close_ad_button").click()
    
    # 2. 看看有什么优惠(像人浏览优惠信息)
    self.swipe_up()  # 向上滑动
    
    # 3. 挑选商品(像人仔细挑选)
    products = self.find_elements("product_list")
    for product in products:
        price = product.find_element("price_text").text
        if float(price) < 1000:  # 预算1000以内
            product.click()
            break
    
    # 4. 查看详情(像人查看商品详情)
    self.swipe_up()  # 滑动查看详情
    
    # 5. 选择规格(像人选择喜欢的款式)
    self.click("color_black")  # 选择黑色
    self.click("size_large")   # 选择大号

四、检查功能是否正常

# 像质检员一样检查功能
def test_app_functions(self):
    # 1. 检查登录功能
    def check_login():
        self.input_text("username", "test123")
        self.input_text("password", "123456")
        self.click("login_button")
        # 检查是否登录成功
        assert self.is_element_present("user_avatar")
    
    # 2. 检查购物车功能
    def check_cart():
        self.click("cart_icon")
        # 确认购物车页面已打开
        assert self.get_text("page_title") == "购物车"
        # 检查商品数量
        items = self.find_elements("cart_items")
        assert len(items) > 0
    
    # 3. 检查支付功能
    def check_payment():
        self.click("checkout_button")
        # 选择支付方式
        self.click("alipay_option")
        # 确认跳转到支付页面
        assert self.is_element_present("pay_qrcode")

五、处理各种意外情况

# 像经验丰富的测试员一样处理意外
class ExceptionHandler:
    def handle_popup(self):
        try:
            # 1. 处理更新提示
            if self.is_element_present("update_popup"):
                self.click("cancel_update")
            
            # 2. 处理广告弹窗
            if self.is_element_present("ad_popup"):
                self.click("close_ad")
            
            # 3. 处理网络错误
            if self.is_element_present("network_error"):
                self.click("retry_button")
                
        except Exception as e:
            print(f"遇到意外情况:{e}")
            self.take_screenshot("error.png")

六、生成测试报告

# 像写测试总结一样生成报告
class TestReport:
    def generate_report(self):
        report = {
            "测试概况": {
                "总用例数": 100,
                "通过数": 95,
                "失败数": 5
            },
            "测试详情": [
                {
                    "用例": "登录功能",
                    "结果": "通过",
                    "耗时": "2.5秒"
                },
                {
                    "用例": "购物流程",
                    "结果": "通过",
                    "耗时": "15秒"
                }
            ],
            "问题记录": [
                "支付页面偶尔加载超时",
                "商品列表滑动不流畅"
            ]
        }
        return report

七、实用的测试小技巧

class TestingTips:
    # 1. 等待元素出现(像人要有耐心)
    def wait_for_element(self, element_id, timeout=10):
        return WebDriverWait(self.driver, timeout).until(
            EC.presence_of_element_located((MobileBy.ID, element_id))
        )
    
    # 2. 智能滑动(像人一样智能判断)
    def smart_swipe(self):
        screen_size = self.driver.get_window_size()
        start_y = screen_size['height'] * 0.8
        end_y = screen_size['height'] * 0.2
        
        while not self.is_element_present("target_element"):
            self.driver.swipe(
                start_x=screen_size['width']/2,
                start_y=start_y,
                end_x=screen_size['width']/2,
                end_y=end_y,
                duration=800
            )
    
    # 3. 记录关键步骤(像写日记一样)
    def log_step(self, step_name):
        print(f"执行步骤:{step_name}")
        self.take_screenshot(f"{step_name}.png")

八、最佳实践建议

  1. 像写剧本一样写测试用例
  • 场景要完整
  • 步骤要清晰
  • 预期要明确
  1. 像导演一样设计测试流程
  • 考虑各种场景
  • 处理各种意外
  • 合理安排顺序
  1. 像演员一样执行测试
  • 按步骤执行
  • 注意细节
  • 记录问题
  1. 像观众一样验证结果
  • 检查是否符合预期
  • 关注用户体验
  • 提出改进建议

九、使用建议

  1. 新手入门
  • 从简单场景开始
  • 多看官方文档
  • 参考示例代码
  1. 进阶使用
  • 设计测试框架
  • 优化测试代码
  • 提高测试效率
  1. 高级应用
  • 持续集成
  • 性能测试
  • 自动化报告

通过这样的方式,Appium 就像一个不知疲倦的测试员,帮我们完成大量重复的测试工作,提高测试效率和质量!

Appium原理详解

一、整体架构

graph TD
    A[Appium Client] --> B[Appium Server]
    B --> C[UiAutomator2/XCUITest]
    C --> D[Native App]
    B --> E[Bootstrap.jar]
    E --> F[Android/iOS System]

二、核心通信机制

public class CommunicationBridge {
    // 1. WebDriver协议通信
    public class AppiumServer {
        public void handleRequest(Request request) {
            // REST API 请求处理
            switch (request.getCommand()) {
                case "findElement":
                    // 转发给 UiAutomator2
                    return uiautomator.findElement(request.getSelector());
                case "click":
                    // 执行点击操作
                    return uiautomator.click(request.getElementId());
                case "getText":
                    // 获取文本内容
                    return uiautomator.getText(request.getElementId());
            }
        }
    }
    
    // 2. Bootstrap.jar 通信
    public class BootstrapServer {
        private final ServerSocket server;
        
        public void startServer() {
            // 启动 TCP 服务器
            server = new ServerSocket(8888);
            // 等待 UiAutomator 连接
            handleConnection(server.accept());
        }
    }
}

三、UI 元素获取机制

public class ElementFinder {
    // 1. AccessibilityService 方式
    public class UiAutomatorBridge {
        public UiElement findElement(Selector selector) {
            // 获取当前窗口的 Accessibility 层级
            AccessibilityNodeInfo root = 
                UiAutomator.getInstance().getRootInActiveWindow();
            
            // 根据选择器查找元素
            switch (selector.getType()) {
                case ID:
                    return findById(root, selector.getValue());
                case XPATH:
                    return findByXPath(root, selector.getValue());
                case CLASS_NAME:
                    return findByClassName(root, selector.getValue());
            }
        }
        
        private UiElement findById(AccessibilityNodeInfo root, String id) {
            // 遍历 Accessibility 树
            List<AccessibilityNodeInfo> nodes = 
                root.findAccessibilityNodeInfosByViewId(id);
            return convertToUiElement(nodes.get(0));
        }
    }
}

四、元素操作实现

public class ElementOperation {
    // 1. 点击操作
    public void click(UiElement element) {
        // 获取元素中心点坐标
        Point center = element.getCenter();
        
        // 构造点击事件
        MotionEvent downEvent = MotionEvent.obtain(
            SystemClock.uptimeMillis(),
            SystemClock.uptimeMillis(),
            MotionEvent.ACTION_DOWN,
            center.x,
            center.y,
            0
        );
        
        // 注入事件
        injectMotionEvent(downEvent);
    }
    
    // 2. 输入文本
    public void sendKeys(UiElement element, String text) {
        // 确保元素可编辑
        if (element.isEditable()) {
            // 通过 InputConnection 输入文本
            InputConnection ic = element.getInputConnection();
            ic.commitText(text, 1);
        }
    }
}

五、状态监控实现

public class StateMonitor {
    // 1. Activity 监控
    public class ActivityMonitor {
        public String getCurrentActivity() {
            // 通过 ActivityManager 获取当前 Activity
            ActivityManager am = (ActivityManager) 
                context.getSystemService(Context.ACTIVITY_SERVICE);
            
            ComponentName cn = am.getRunningTasks(1)
                .get(0).topActivity;
            return cn.getClassName();
        }
    }
    
    // 2. 界面状态监控
    public class ViewMonitor {
        public boolean waitForElement(Selector selector, long timeout) {
            long endTime = System.currentTimeMillis() + timeout;
            
            while (System.currentTimeMillis() < endTime) {
                if (findElement(selector) != null) {
                    return true;
                }
                SystemClock.sleep(500);
            }
            return false;
        }
    }
}

六、事件注入机制

public class EventInjector {
    // 1. Touch 事件注入
    public void injectTouchEvent(MotionEvent event) {
        // 通过 InputManager 注入事件
        InputManager.getInstance().injectInputEvent(
            event, 
            InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
        );
    }
    
    // 2. Key 事件注入
    public void injectKeyEvent(KeyEvent event) {
        // 通过 InputManager 注入按键事件
        InputManager.getInstance().injectInputEvent(
            event,
            InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
        );
    }
}

七、性能数据采集

public class PerformanceCollector {
    // 1. CPU 使用率
    public float getCpuUsage(String packageName) {
        // 读取 /proc/stat 获取 CPU 信息
        return calculateCpuUsage(readProcStat());
    }
    
    // 2. 内存使用
    public long getMemoryInfo(String packageName) {
        ActivityManager am = (ActivityManager) 
            context.getSystemService(Context.ACTIVITY_SERVICE);
        
        // 获取进程内存信息
        Debug.MemoryInfo[] memInfo = am.getProcessMemoryInfo(
            new int[]{getPid(packageName)});
        return memInfo[0].getTotalPss();
    }
}

八、原理总结

  1. 核心实现机制
  • 基于 Android Instrumentation
  • 使用 Accessibility 服务
  • 通过事件注入实现操作
  1. 元素定位原理
  • 解析 UI 层级树
  • 匹配选择器条件
  • 返回元素信息
  1. 操作实现原理
  • 构造 MotionEvent
  • 通过 InputManager 注入
  • 监听操作结果
  1. 状态获取原理
  • 使用 ActivityManager
  • 监听 Accessibility 事件
  • 分析界面层级

九、技术要点

public class TechnicalPoints {
    // 1. UI 层级获取
    private AccessibilityNodeInfo getUiHierarchy() {
        // 通过 UiAutomator 获取
        return UiAutomator.getInstance().getRootInActiveWindow();
    }
    
    // 2. 事件构造
    private MotionEvent createTouchEvent(Point point) {
        return MotionEvent.obtain(
            SystemClock.uptimeMillis(),
            SystemClock.uptimeMillis(),
            MotionEvent.ACTION_DOWN,
            point.x,
            point.y,
            0
        );
    }
    
    // 3. 元素属性获取
    private Map<String, String> getElementAttributes(
            AccessibilityNodeInfo node) {
        Map<String, String> attributes = new HashMap<>();
        attributes.put("text", node.getText().toString());
        attributes.put("className", node.getClassName().toString());
        attributes.put("packageName", node.getPackageName().toString());
        return attributes;
    }
}

总结:

  1. Appium 通过多层架构实现自动化
  2. 底层依赖系统 API 和服务
  3. 使用事件注入模拟用户操作
  4. 通过 Accessibility 获取界面信息
  5. 实现了完整的监控和控制机制