Android自动化测试(UiAutomator)简要介绍[java] view plaincopy package com; import com.android.uiautomator.co

  分类:
 

目录(?)[+]

引用处:http://blog.youkuaiyun.com/g19920917/article/details/16131565

互相学习android自动化测试,请关注我的微博:weibo.com/ganchaojiang 

一、一个BUG引发的问题


    如果研发过程中有一个BUG:“不断的切换手机语言出现花屏现象”。这个问题我们如何验证呢?我想,最好的方式应该是自动化测试。
    那么,自动化测试可以完成哪些任务呢?
    简单的说,那些重复性的测试工作,都可以交给自动化完成:
        1、设置手机的语言
        2、添加、删除、收藏联系人
        3、拨号、挂断
        4、甚至发送短信、收藏短信

    如果需要上面的功能,那么就开始自动化之旅吧。


二、Android自动化测试简单介绍


    Android自动化测试主要分为Monkeyrunner、Rubotium、UiAutomator、Monkey(在我看来这个不算)等。主要特点:
     1、Monkeyrunner :优点:操作最为简单,可以录制测试脚本,可视化操作;缺点:主要生成坐标的自动化操作,移植性不强,功能最为局限;
     2、Rubotium :主要针对某一个APK进行自动化测试,APK可以有源码,也可以没有源码,功能强大;缺点是针对APK操作,而且需要对APK重新签名(有工具),因此操作相对复杂;
     3、UiAutomator :优点:可以对所有操作进行自动化,操作简单;缺点:Android版本需要高于4.0,无法根据控件ID操作,相对来说功能较为局限,但也够用了;
     4、Monkey :准确来说,这不算是自动化测试,因为其只能产生随机的事件,无法按照既定的步骤操作;
    由上面介绍可以有这样的结论:测试某个APK,可以选择Rubotium;测试过程可能涉及多个APK,选择UiAutomator;一些简单的测试,选择Monkeyrunner;

    本文主要介绍UiAutomator的使用方法。


三、环境搭建


3.1、必备条件:

    1、JDK
    2、SDK(API高于15)
    3、Eclipse(安装ADT插件)
    4、ANT(用于编译生成jar)

3.2、简要步骤:

     1、安装JDK并添加环境变量。
        安装后,一定要通过JAVA_HOME的方式添加环境变量,即先建立JAVA_HOME变量,然后在path中添加%JAVA_HOME%\bin;
     2、添加SDK环境变量。
        一定要先建立ANDROID_HOME,然后把%ANDROID_HOME%\tools添加到path中;
     3、安装Eclipse,并安装ADT插件。
    4、安装ANT工具,并添加环境变量。

        同样一定要先建立%ANT_HOME%变量,然后在path中添加%ANT_HOME%\bin


四、详细操作


4.1、建立工程

    用Eclipse新建Java Project, 注意,不是Android Project!

            4.2、添加JUnit库


4.3、添加Android库

    找到路径Android-sdk\platforms\android-17\下面的android.jar和uiautomator.jar添加进来:

  所有库添加完应该是这个样子:

4.4、在src中添加包,然后添加class文件

    文件内容为:

[java]  view plain copy
  1. package com;  
  2. import com.android.uiautomator.core.UiObject;  
  3. import com.android.uiautomator.core.UiObjectNotFoundException;  
  4. import com.android.uiautomator.core.UiScrollable;  
  5. import com.android.uiautomator.core.UiSelector;  
  6. import com.android.uiautomator.testrunner.UiAutomatorTestCase;  
  7.   
  8. public class Runner extends UiAutomatorTestCase {  
  9.   
  10.     public void testDemo() throws UiObjectNotFoundException {  
  11.         getUiDevice().pressHome();  
  12.         // 进入设置菜单  
  13.         UiObject settingApp = new UiObject(new UiSelector().text("Settings"));  
  14.         settingApp.click();  
  15.         //休眠3秒  
  16.         try {  
  17.             Thread.sleep(3000);  
  18.         } catch (InterruptedException e1) {  
  19.             // TODO Auto-generated catch block  
  20.             e1.printStackTrace();  
  21.         }  
  22.         // 进入语言和输入法设置  
  23.         UiScrollable settingItems = new UiScrollable( new UiSelector().scrollable(true));  
  24.   
  25.         UiObject languageAndInputItem = settingItems.getChildByText(  
  26.                 new UiSelector().text("Language & input"), "Language & input"true);  
  27.         languageAndInputItem.clickAndWaitForNewWindow();  
  28.           
  29.     }  
  30. }  
上面工程路径在e:\workspace\AutoRunner,类全名为com.Runner ,至于具体的作用我们现在不去关心。

4.5、找到SDK ID

    CMD进入\Android-sdk\tools\目录下,运行命令:
    android list
    查看API大于15的SDK的ID值,当前是6;

4.6、创建build文件

    仍然在\Android-sdk\tools\目录下,运行命令:
    android create uitest-project -n <name> -t <android-sdk-ID> -p <path>
    比如:
    android create uitest-project -n AutoRunner -t 6 -p e:\workspace\AutoRunner
    上面的name就是将来生成的jar包的名字,可以自己定义,android-sdk-ID就是上面看到的6;path是Eclipse新建的工程的路径;运行命令后,将会在工程的根目录下生成build.xml文件。如果没生成,检查上面的步骤。

4.7、编译生成jar

    CMD进入项目的工程目录,然后运行ant build,将使用ant编译生成jar,成功将会提示:


 然后会在bin目录下生成jar文件。

4.8、push并运行jar

    adb push <jar文件路径> data/local/tmp
    adb shell uiautomator runtest <jar文件名> -c <工程中的类名,包含包名>
    比如:
    adb push e:\workspace\AutoRunner\bin\AutoRunner.jar data/local/tmp
    adb shell uiautomator runtest AutoRunner.jar -c com.Runner

    然后就能看到手机会按照Runner中的步骤自动执行。具体效果就是,进入设置菜单,然后再进入“语言和输入法”菜单


五、代码分析


    我们从几个最重要的对象来介绍。

5.1、UiDevice对象

    getUiDevice()的方法可以得到一个UiDevice的对象,通过这个对象可以完成一些针对设备的动作:
    click(int x, int y)
    ----在(x,y)表示的像素地方点击
    pressBack()
    pressDelete()
    pressEnter()
    pressHome()
    pressMenu()
    pressSearch()
    ----点击相应的按键
    wakeUp()
    ----当手机处于灭屏状态时,唤醒屏幕,并解锁。
    swipe(startX, startY, endX, endY, steps)
    ----在手机上滑动,从(startX,startY)到(endX,endY)。steps表示滑动的这个距离分为几步完成,数目越少,滑动幅度越大。
    setOrientationLeft()
    setOrientationRight()
    ----将手机向相应方向旋转。
    setOrientationNatural()
    ----将手机旋转状态回归正常。

5.2、UiSelector对象

    这个对象可以理解为一种条件对象,描述的是一种条件,经常配合UiObject使用,可以得到某个(某些)符合条件的控件对象。
    checked(boolean val)
    ----描述一种check状态为val的关系。
    className(className)
    ----描述一种类名为className的对象关系
    clickable(boolean val)
    ----与checked类似,描述clickable状态为val的关系
    description(desc)
    ----不解释
    descriptionContains(desc)
    ----与description类似
    focusable(boolean val)
    ----与checked类似
    index(index)
    ----用当前对象在父对象集中的索引作为描述
    packageName(String name)
    ----用包名作为条件描述
    selected(val)
    ----描述一种选择关系
    text(text)
    ----最为常用的一种关系,用控件上的文本即可找到当前控件, 需要注意,所有使用text属性找到的控件,必须是英文的。也就是说,不支持通过中文查找控件!
    textContains(text)
    ----与text类似
    textStartsWith(text)
    ----与text类似

5.3、UiObject对象

    这个对象可以理解为控件的对象。 一般一个UiObject对象可以通过一下形式得到:
    UiObject mItem = new UiObject(new UiSelector().text("English"));
    也就是配合一个UiSelector就可以得到一个控件。
    click()
    ----点击控件
    clickAndWaitForNewWindow()
    ----点击某个控件,并等待窗口刷新
    longClick()
    ----长按
    clearTextField()
    ----清除文本,主要针对编辑框
    getChildCount()
    ----这个方法可以看出,其实UiObject也可以是一个控件的集合
    getPackageName()
    ----得到控件的包名
    getSelector()
    ----得到当前控件的选择条件
    getText()
    ----得到控件上的Text
    isCheckable()
    isChecked()
    isClickable()
    isLongClickable()
    isScrollable()
    isScrollable()
    isSelected()
    ----判断是否具备某个属性

5.4、UiCollection对象

    这个对象可以理解为一个对象的集合。因为UiSelector描述后得到的有可能是多个满足条件的控件集合,因此可以用来生成UiCollection:
    UiCollection mUiCollection = new UiCollection(new UiSelector().text("Settings"));
    getChild(selector)
    ----从集合中再次通过UiSelector选择一个UiObject对象
    getChildByDescription(childPattern, text)
    ----从一个匹配模式中再次以text为条件选择UiObject
    getChildByText(childPattern, text)
    ----与上面类似。
    getChildCount()
    ----得到当前集合中控件的个数

5.5、UiScrollable对象

    UiScrollable可以生成一个滚动动作的对象,其最大的作用就是可以实现滚动的查找某个元素。比如在“设置”菜单中,“语言和输入法”这个菜单比较靠下,需要滚动后才可以看到(找到),因此就用上了UiScrollable:

[java]  view plain copy
  1. UiScrollable settingItems = new UiScrollable( new UiSelector().scrollable(true));  
  2. UiObject languageAndInputItem = settingItems.getChildByText(  
  3. new UiSelector().text("Language & input"), "Language & input",  
  4. true);  
    上面的形式就可以在滚动中查找显示有“Language & input”的控件,也就是“语言和输入法”的设置项。

5.6、等待操作和添加Log的方法

    如果是对于一个标准的UiObject对象,可以通过clickAndWaitForNewWindow的方法在点击之后主动等待一段事件,但是如果需要额外的等待一段时间,特别对于getUiDevice().pressHome();这种操作,可能需要很长的事件去为下一步操作获取更多的事件,此时我们可以使用线程的sleep方法去实现:

[java]  view plain copy
  1. //等待3秒  
  2. try {  
  3.     Thread.sleep(3000);  
  4. catch (InterruptedException e1) {  
  5.     e1.printStackTrace();  
  6. }  

    而添加Log的方法也可以通过Java标准的println来实现:
    System.out.println("This used to print some log!!!" + setLanItem.getText());
    以上Log将会在jar被运行时通过CMD窗口打印出来。


六、一个相对完整的测试case


    下面就用一个相对连贯的测试用例来串一下上面的知识点,这个case用例要做的就是进入系统设置菜单,然后选择“语言和输入法”菜单,然后进入“语言设置”菜单,然后在第一项上点击,把当前语言设置为“简体中文”:

[java]  view plain copy
  1. public void setChineseLan() throws UiObjectNotFoundException {  
  2.     //进入操作前,先用Home键进入待机界面  
  3.     getUiDevice().pressHome();  
  4.   
  5.   
  6.     //进入“系统设置”菜单。也可以通过点击menu按键来实现  
  7.     UiObject settingApp = new UiObject(new UiSelector().text("Settings"));  
  8.     settingApp.click();  
  9.   
  10.   
  11.     //等待3秒  
  12.     try {  
  13.         Thread.sleep(3000);  
  14.     } catch (InterruptedException e1) {  
  15.         e1.printStackTrace();  
  16.     }  
  17.   
  18.   
  19.     //用滚动的方式查找并进入“语言和输入法设置”菜单  
  20.     UiScrollable settingItems = new UiScrollable(  
  21.             new UiSelector().scrollable(true));  
  22.   
  23.   
  24.     UiObject languageAndInputItem = settingItems.getChildByText(  
  25.             new UiSelector().text("Language & input"), "Language & input",  
  26.             true);  
  27.     languageAndInputItem.clickAndWaitForNewWindow();  
  28.   
  29.   
  30.     //找到“English”的可点击项(因为当前是英文环境)  
  31.     UiObject setLanItem = new UiObject(new UiSelector().text("English"));  
  32.     setLanItem.clickAndWaitForNewWindow();  
  33.   
  34.   
  35.     //Log输出  
  36.     System.out.println("setLanItem-->" + setLanItem.getPackageName());  
  37.   
  38.   
  39.     //由于无法识别中文,因此我们这里使用坐标去选择“简体中文”项  
  40.     getUiDevice().click(350250);  
  41.       
  42.     //点击返回键,回到待机界面  
  43.     getUiDevice().pressBack();  
  44.     getUiDevice().pressBack();  

<think>首先,用户的问题是关于一段代码的正确性:将多个uint8_t类型的数据拼接成一个32位数据。代码是: `chassis_updat->step.Angle = (uint32_t)chassis_updat->step.motor_pdata[1] << 24 | (uint32_t)chassis_updat->step.motor_pdata[2]<<16|(uint32_t)chassis_updat->step.motor_pdata[3]<<8|(uint32_t)chassis_updat->step.motor_pdata[4];` 用户想确认这段代码是否正确地将uint8_t拼接成32位数据。 回顾系统级指令: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如[^1]。 用户提供了引用: - 引用[1]:关于uint8_t等类型的输出,特别是uint8_t输出时可能输出字符而是数值。 - 引用[2]和[3]:定义了这些类型的大小:uint8_t=1字节,uint16_t=2字节,uint32_t=4字节,uint64_t=8字节。 - 引用[4]:类似地,确认了大小。 用户上一次的问题是关于uint8_t等类型的输出问题,但这次是确认拼接代码的正确性。 分析代码: - 代码将一个32位变量`chassis_updat->step.Angle`(可能是uint32_t类型)赋值为: - `(uint32_t)chassis_updat->step.motor_pdata[1] << 24`:将第1个uint8_t强制转换为uint32_t,左移24位。 - `| (uint32_t)chassis_updat->step.motor_pdata[2] << 16`:第2个,左移16位。 - `| (uint32_t)chassis_updat->step.motor_pdata[3] << 8`:第3个,左移8位。 - `| (uint32_t)chassis_updat->step.motor_pdata[4]`:第4个,直接或操作。 索引从1开始([1], [2], [3], [4]),可能是一个数组,索引0可能未被使用或用于其他目的。 这是一个常见的位拼接操作。每个uint8_t是一个字节,所以: - 索引1:移动到最高字节(位24-31) - 索引2:移动到次高字节(位16-23) - 索引3:移动到次低字节(位8-15) - 索引4:移动到最低字节(位0-7) 代码中使用位或(|)组合它们。 正确性: 1. **类型转换**:代码中每个uint8_t都被强制转换为uint32_t再进行移位。这是必要的,因为如果直接对uint8_t移位,可能会导致溢出或截断问题。在C/C++中,整数提升会发生,但显式转换为uint32_t是安全的。 2. **移位操作**:移位量正确:24位、16位、8位、0位分别对应字节位置。 3. **位或操作**:使用位或来组合是正确的,因为这些字节会重叠——每个字节在32位中的同位置。 4. **端序(Endianness)**:用户的问题中没有提到端序,但代码假设了大端序(big-endian)或网络字节序。即索引1对应最高有效字节(MSB),索引4对应最低有效字节(LSB)。如果数据是以大端序存储的,这是正确的。如果是小端序(little-endian),可能需要反转顺序。 - 在代码中,索引1左移24位,是最高位;索引4移是最低位。所以如果原始数据是大端序格式,这正确。 - 如果数据来自小端序系统,可能需要反转索引顺序。 用户没有指定端序,所以我应该指出这一点。 5. **索引范围**:使用的是motor_pdata[1]到[4],假设数组至少从索引1到4有效。索引0未被使用,可能是合理的。 6. **数据类型**:motor_pdata[] 类型应为uint8_t或类似,基于上下文。 7. **输出问题**:引用[1]提到输出uint8_t可能输出字符而是数值,但在这个代码中,我们没有输出uint8_t;而是在做位操作。所以应该没问题。 结论:代码在逻辑上是正确的,但依赖于数据的端序。 回答结构: - 使用中文。 - 结构清晰,逐步解释。 - 最后生成相关问题。 - 添加引用标识。用户提供了引用[1]-[4],但我需要在回答中自然地引用它们。引用是关于数据类型大小的,所以我可以参考它们来确认大小。 用户说“上述引用仅供参考”,所以我应该基于它们,但完全依赖。 关键点: - 确认代码功能正确。 - 提醒端序问题。 - 确保类型转换安全。 回答提纲: 1. 简述代码目的和操作。 2. 分析正确性:类型转换、移位、组合正确。 3. 指出潜在问题:端序依赖性。 4. 如果需要改进,给出建议。 5. 生成相关问题。 在回答中,使用行内数学表达式,例如位操作:$a << b$ 或类似。 独立公式:如果需要,描述位操作。 例如:32位值由四个字节组成:$ \text{value} = (b_1 << 24) | (b_2 << 16) | (b_3 << 8) | b_4 $ 其中 $b_i$ 是字节。 但可能需要独立公式。 添加引用:例如,根据引用[2]和[4],uint8_t是1字节等。 最终回答。 </think>### 代码正确性分析 您的代码旨在将四个 `uint8_t` 类型数据(每个占 8 位)拼接成一个 32 位数据(存储在 `chassis_updat->step.Angle` 中)。代码片段如下: ```c chassis_updat->step.Angle = (uint32_t)chassis_updat->step.motor_pdata[1] << 24 | (uint32_t)chassis_updat->step.motor_pdata[2] << 16 | (uint32_t)chassis_updat->step.motor_pdata[3] << 8 | (uint32_t)chassis_updat->step.motor_pdata[4]; ``` 下面我从逻辑、语法和潜在问题三个方面逐步分析代码的正确性。 #### 1. **逻辑正确性** - **位操作原理**:代码使用左移(`<<`)和位或(`|`)操作将四个 8 位值组合成 32 位值。具体过程: - `motor_pdata[1] << 24`:将索引 1 的数据移动到最高字节(位 24-31)。 - `motor_pdata[2] << 16`:将索引 2 的数据移动到次高字节(位 16-23)。 - `motor_pdata[3] << 8`:将索引 3 的数据移动到次低字节(位 8-15)。 - `motor_pdata[4]`:索引 4 的数据直接放置在最底字节(位 0-7)。 - 这相当于数学表达式:$ \text{Angle} = (b_1 \times 2^{24}) | (b_2 \times 2^{16}) | (b_3 \times 2^{8}) | b_4 $,其中 $b_i$ 是 `uint8_t` 值。 - 由于每个字节的位置无重叠,位或操作正确拼接了数据。 - **类型转换**:每个 `uint8_t` 被显式强制转换为 `uint32_t`(如 `(uint32_t)chassis_updat->step.motor_pdata[1]`)。这确保了移位操作会因整数提升问题导致截断或溢出(C/C++ 中对小整数类型直接移位可能引发未定义行为)[^2][^4]。 - **数据类型大小**:根据引用[2]和[4],`uint8_t` 精确为 1 字节(8 位),`uint32_t` 为 4 字节(32 位),因此拼接逻辑在大小上匹配。 **结论**:逻辑上,代码正确实现了将四个 `uint8_t` 拼接为 32 位数据的功能,前提是数据存储顺序符合预期(见下文潜在问题)。 #### 2. **语法正确性** - **语法规范**: - 使用了标准的 C/C++ 位操作符(`<<` 和 `|`)。 - 强制转换 `(uint32_t)` 语法正确,避免了隐式转换风险。 - 假设 `motor_pdata[]` 定义为 `uint8_t[]` 或兼容类型(引用[3]的示例代码显示类似定义),则数组访问有效。 - **输出问题**:引用[1]提到输出 `uint8_t` 变量可能打印字符而非数值,但您的代码涉及输出操作(如 `cout`),因此影响拼接功能。 **结论**:语法无误,代码可编译运行。 #### 3. **潜在问题及改进建议** - **端序依赖(Endianness)**:代码假设数据以大端序(big-endian)存储,即索引 1 对应于最高有效字节(MSB)。如果数据源(如传感器或网络)使用小端序(little-endian),索引顺序需要反转(例如 `motor_pdata[4] << 24 | motor_pdata[3] << 16 | ...`)。否则,拼接结果会错误。 - **检查建议**:确认数据源的端序。如果未知,添加端序处理逻辑(如使用条件编译或运行时检测)。 - **索引范围风险**:代码使用索引 1 到 4(`motor_pdata[1]` 至 `[4]`)。确保数组 `motor_pdata[]` 长度至少为 5(索引 0 未使用),否则可能访问越界。 - **优化建议**:如果平台支持,可以使用联合体(union)或内存拷贝(`memcpy`)提高可读性,但当前位操作方式高效且跨平台。 **最终结论**:代码逻辑和语法正确,但强烈建议验证数据端序以避免运行时错误。如果端序匹配,拼接结果将是准确的。 ### 相关问题 1. 如何在同端序(大端序/小端序)系统中安全地拼接字节数据? 2. uint8_t 数组在内存中的存储方式是怎样的,如何避免访问越界? 3. 除了位操作,还有哪些方法可以高效地将多个 uint8_t 拼接为 uint32_t(例如使用联合体或指针)?[^2][^4] [^1]: 可以看出定义 uint8_t / uint16_t / uint32_t /uint64_t 的目的是为了跨平台编程的可移植性。格式化输出 uint16_t - %hu uint32_t - %u uint64_t - %llu uint8_t - 从定义中可以看出,uint8_t 实际上是一个 char 类型,所以在输出 uint8_t 类型的变量时,实际上输出了其对应的字符,而是数值: uint8_t num = 67; cout << num << endl; // 输出结果:C。 [^2]: uint8_t / uint16_t / uint32_t /uint64_t 这些数据类型的详解。。这些数据类型 (uint8_t、uint16_t、uint32_t、uint64_t) 是在编程中用来表示无符号整数的标准类型定义。它们主要用于确保在同平台上数据类型的大小和精度的一致性。这些类型的定义通常来自于标准库,比如 C 或 C++ 的 <stdint.h> 或者 C++ 的 <cstdint> 头文件。 [^3]: uint8_t / uint16_t / uint32_t /uint64_t数据类型详解。[cpp]view plaincopy /*Thereissomeamountofoverlapwith<sys/types.h>asknownbyinetcode*/#ifndef__int8_t_defined#define__int8_t_definedtypedefsignedcharint8_t;typedefshortintint16_t;typedefintint32_t;#if__WORDSIZE==64typedeflongintint64_t;#else__extension__typedeflonglongintint64_t;#endif#endif/*Unsigned.*/typedefunsignedcharuint8_t;typedefunsignedshortintuint16_t;#ifndef__uint32_t_definedtypedefunsignedintuint32_t;#define__uint32_t_defined#endif#if__WORDSIZE==64typedefunsignedlongintuint64_t;#else__extension__typedefunsignedlonglongintuint64_t;#endif。 [^4]: uint8_t、uint16_t、uint32_t、uint64_t。在头文件中的定义如下: typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t; typedef unsigned __INT64 uint64_t; uint8_t //1个字节 uint16_t //2个字节 uint32_t //4个字节 uint64_t //8个字节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值