编写脚本
恭喜你!来到这里就说明你已经知道怎么通过代码来打开和控制应用了!但如果需要让这些代码能稳定地运行上千遍,并有不错的测试报告告诉你用例是否通过,我们还需要加入一些额外的代码。
在这一章,我们将会学习下面的内容:
- 编写你的第一个测试脚本
- 使用 Junit 组织你的脚本
- 在脚本中加入隐式等待,应对不稳定的网络环境
准备工作
首先,在我们之前在 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