编写脚本

恭喜你!来到这里就说明你已经知道怎么通过代码来打开和控制应用了!但如果需要让这些代码能稳定地运行上千遍,并有不错的测试报告告诉你用例是否通过,我们还需要加入一些额外的代码。

在这一章,我们将会学习下面的内容:

  1. 编写你的第一个测试脚本
  2. 使用 Junit 组织你的脚本
  3. 在脚本中加入隐式等待,应对不稳定的网络环境

准备工作

首先,在我们之前在 Eclipse 里面创建的项目里面添加一个名为 app 的文件夹,并把 ToDoList 应用放到里面。我们接下来将会对这个应用编写自动化测试用例。

编写你的第一个用例

运用前面学到的 Desired Caps 以及元素定位方法,我们来编写一个添加待办事项的用例:

序号 执行步骤 预期结果
1 打开应用
2 输入“使用 Appium 编写测试脚本”
3 点击“添加” 添加成功

首先,我们在 Eclipse 里面新建一个带有 main 方法的类(勾选“public static void main(String[] args)”),类名为 ToDoListTest。如无意外,建立后的文件内容应该如下:

public class ToDoListTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

}

接着,动手时间到!大家运用前面学到的知识来编写自己的第一条用例吧!如果有问题,可以随时咨询你的教练。

好了,你应该写完自己的第一条用例了。下面是检查时间,给你的教练展示一下你跑起来的脚本吧!

为了方便后续描述,这里给出一个可运行的版本。注意,这不是唯一的编写方法,只要你的脚本能够跑起来,那么都是没问题的~

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;

public class ToDoListTest {

    public static void main(String[] args) throws MalformedURLException {

        // Install and open application
        File classpathRoot = new File(System.getProperty("user.dir"));
        File appDir = new File(classpathRoot, "app/");
        File app = new File(appDir, "ToDoList.apk");

        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability("deviceName","Android Device");
        capabilities.setCapability("platformVersion", "4.4");
        capabilities.setCapability("app", app.getAbsolutePath());
        capabilities.setCapability("unicodeKeyboard", true);
        capabilities.setCapability("resetKeyboard", true);
        AndroidDriver driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);

        // Add new item
        String itemText="使用 Appium 编写测试脚本";
        WebElement editText = driver.findElement(By.id("com.testerhome.appiumgirl.todolist:id/etNewItem"));
        editText.sendKeys(itemText);
        WebElement addItemBtn = driver.findElement(By.id("com.testerhome.appiumgirl.todolist:id/btnAddItem"));
        addItemBtn.click();

        // Check if item is added
        List<AndroidElement> appiumItems = driver.findElementsByXPath("//android.widget.TextView[@text='"+itemText+"']");
        if (appiumItems.isEmpty()) {
            System.out.println("测试失败");
        }else{
            System.out.println("测试通过");
        }

        // exist
        driver.quit();

    }
}

用 Junit 组织你的用例

如果你已经在上面的脚本用上 Junit ,请直接略过这一节

好了,我们的脚本编写好了,但总觉得缺了点什么?对的,我们缺少了报告!如果每一个测试用例都是通过 print 输出测试结果,那么当我们有100个用例的时候,岂不是看得眼花缭乱?不用急,Junit 来搭救你了!

Junit 是采用 Java 语言编写的一个单元测试框架。通过它,我们可以有效地组织我们的用例,把用例的不同部分区分开。

正常情况下,我们的测试用例总会有前提条件。它不属于测试范围,若无法创造此条件则测试用例无法进行,测试结果为 blocked 。同样,自动化测试里面也有类似的概念,只是名字换成了 setUp 和 tearDown。其中 setUp 负责准备前提条件,它会在每个用例执行前被执行。tearDown 负责收尾,它会在每个用例执行后执行。值得注意的是,tearDown 无论在用例执行结果是什么的时候都会被执行。

现在,我们来重新整理一下我们前面的用例。它应该有两部分:

  • 前提条件

ToDoList 应用已经装上手机并启动

  • 执行步骤及预期结果
序号 步骤 预期结果
1 输入“使用 Appium 编写测试脚本”
2 点击“添加” 添加成功

现在,我们用 Junit 改写我们的测试用例。

第一步,增加 setUp 方法,把 driver 的初始化放入其中:

...
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
...

public class ToDoListTest{
    private AndroidDriver driver;

    @Before
    public void setUp() throws Exception {
        // Install and open application
        File classpathRoot = new File(System.getProperty("user.dir"));
        File appDir = new File(classpathRoot, "app/");
        File app = new File(appDir, "ToDoList.apk");
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability("deviceName","Android Device");
        capabilities.setCapability("platformVersion", "4.4");
        capabilities.setCapability("app", app.getAbsolutePath());
        capabilities.setCapability("unicodeKeyboard", true);
        capabilities.setCapability("resetKeyboard", true);
        driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
    }
    ...

第二步,把执行步骤及预期结果放到测试用例中,方法命名为 addItem

    ...
    @Test
    public void addItem(){
        String itemText = "使用 Appium 编写测试脚本";    

        // Add new item
        WebElement editText = driver.findElement(By.id("com.testerhome.appiumgirl.todolist:id/etNewItem"));
        editText.sendKeys(itemText);
        WebElement addItemBtn = driver.findElement(By.id("com.testerhome.appiumgirl.todolist:id/btnAddItem"));
        addItemBtn.click();

        // Check if item is added
        List<AndroidElement> appiumItems = driver.findElementsByXPath("//android.widget.TextView[@text='"+itemText+"']");
        Assert.assertEquals("找不到待办事项 '"+itemText+"'", false, appiumItems.isEmpty());
    }

第三步,把我们最后的关闭 session 操作放在 tearDown ,防止后续的用例由于会话冲突无法启动:

    ...
        driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
    }

    @After
    public void tearDown() throws Exception {
        driver.quit();
    }

    @Test
    public void addItem(){
        // Add new item
    ...

第四步,删除我们之前的 main 方法。现在我们已经不需要它了。

改造完成!现在,在 Eclipse 里面再运行一下这个用例,执行完毕后你会看到这样的结果:

是不是比之前好看多了!

隐式等待

由于我们的被测应用是纯本地操作,逻辑也比较简单,因此速度很快,添加后立即就出现了。但实际项目中大多数应用由于逻辑复杂、网络不稳定的因素,添加后会需要等待一段时间才能显示。此时,我们需要加入隐式等待。

隐式等待是指在所有查找元素方法中加入固定的等待时间。例如上面用例中我们只会查找一次添加后的待办事项 “使用 Appium 编写测试脚本” ,找不到元素就会直接执行失败。而加入隐式等待后,查找元素将会指定的等待时间中不断寻找,直到找到元素或者超时。

但需要注意,隐式等待一旦加入,直到修改隐式等待时间或 driver 退出,否则隐式等待将一直生效。

多说无用,Let's show code!

首先,我们添加一个新的用例。在这个用例中我们添加的事项内容改为 “模拟弱网” 。此时应用将会模拟弱网络下的行为,在点击添加按钮5秒后才出现待办事项:

    @Test
    public void addItemInWeekNetwork(){
        String itemText = "模拟弱网";

        // Add new item
        WebElement editText = driver.findElement(By.id("com.testerhome.appiumgirl.todolist:id/etNewItem"));
        editText.sendKeys(itemText);
        WebElement addItemBtn = driver.findElement(By.id("com.testerhome.appiumgirl.todolist:id/btnAddItem"));
        addItemBtn.click();

        // Check if item is added
        List<AndroidElement> appiumItems = driver.findElementsByXPath("//android.widget.TextView[@text='"+itemText+"']");
        Assert.assertEquals("找不到待办事项 '"+itemText+"'", false, appiumItems.isEmpty());

    }

此时你会发现,新的用例将会失败。此时,我们可以通过添加隐式等待来解决这个问题。设置隐式等待的方法是:implicitlyWait()

同时,由于 findElementsByXPath 方法即使找不到元素也会立即返回,因此我们需要把它改为使用 findElementByXPath

    @Test
    public void addItemInWeekNetwork(){
        String itemText = "模拟弱网";

        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);//--> Add implicitly wait

        try{
            // Add new item
            ...
            // Check if item is added
            try{
                driver.findElementByXPath("//android.widget.TextView[@text='"+itemText+"']");
            }catch (Exception e){
                throw new AssertionError("找不到待办事项 '"+itemText+"'");
            }
        }finally{
            driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS); //--> Remove implicitly wait
        }
    }

再次运行,你会发现脚本会自动等待直到出现 “模拟弱网” 这个待办事项啦!

试一试

试一下,把 implicitlyWait 的时间缩短到4秒,会发生什么?20秒呢?

拓展

除了隐式等待,其实对应还有显式等待。有兴趣的同学可以了解下:http://www.cnblogs.com/shinhwa/p/3688184.html