当前位置: 首页 > article >正文

移动端自动化测试Appium-java

一、Appium的简介

移动端的自动化测试框架

模拟人的操作进行功能自动化常用于功能测试、兼容性测试

跨平台的自动化测试

二、Appium的原理

核心是web服务器,接受客户端的连接,接收客户端的命令,在手机设备上执行命令,收集命令执行的结果。

Session,客户端初始化一个 session 会话,发送 POST/session 请求到服务器端,这些请求里面都会带有一个对象:desiredCapabilities,这个时候服务器端会启动自动化 session 然后返回一个 session ID,以后的命令都会用这个 seesion ID 去匹配。

desired capabilities 对象其实是一个 key-value 的集合,里面包含了各种各样的信息,发送到服务器端后,服务器解析这些信息就知道了客户端对哪种 session 感兴趣,然后就会启动相应的session,这里面的信息会影响着服务器端启动 session 的类型。

Appium Server,Appium 是一个用 Node.js 编写的 HTTP server,它创建、并管理多个 WebDriver sessions 来和不同平台交互。

Apium ClientsAppium 开始一个测试后,就会在被测设备(手机)上启动一个 server ,监听来
自 Appium server 的指令,每种平台(如 iOS 和 Android)都有不同的运行和交互方式,所以 Appium 会用某个桩程序“侵入”该平台,并接受指令,来完成测试用例的运行。

三、Appium的使用

3.1导入依赖

<dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.0.0-beta1</version>
        </dependency>
        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>8.5.1</version>
            <!--            <scope>test</scope>-->
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.9.1</version>
        </dependency>

3.2 创建测试类

使用@test注解,类的名字要符合标识符命名规则即可


import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.annotations.Test;

public class NewTest {
  //定义 AppiumDriver 对象  
  AppiumDriver driver;
    @Test
    public void f(){

    }

}

3.3 @BeforeClass 注解

@BeforeClass 注解 用于设定进入测试类后,在所有测试之前首先要执行的代码,指定测试设备平台。

 @BeforeClass
  public void beforeClass() throws MalformedURLException  {
	  //指定测试设备信息
	  DesiredCapabilities device=new  DesiredCapabilities();
	  //使用移动设备或模拟器种类
	  device.setCapability("deviceName","Android Emulator");
	  //指定哪个移动操作平台
	  device.setCapability("platformName", "Android");
	  //指定移动操作系统版本
	  device.setCapability("platformVersion", "4.4.2");
	  //指定 app 程序包名,即被测程序名
      device.setCapability("appPackage","com.android.calculator2");
      //指定 app 启动页名称
      device.setCapability("appActivity",".Calculator");
     //启动app
      driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"),device);
      
  }

3.4  @AfterClass 注解

在所有测试之后再执行的代码,可在该注解写入app退出代码

 @AfterClass
  public void afterClass() {
	  //退出app
	  driver.quit()
  }

3.5 @Test(属性) 注解

指定要测试的方法,方法名只要符合标识符命名规则。
属性

description="测试描述"
priority=优先级,从 0 开始
timeout=?ms,超时时间
dataProvider="Dataprovider 的名称或方法名"
dataProviderClass=产生测试数据的类
 

 @Test(description="123",priority=0)
  public void f() {
	  System.out.println("hello");
  }

3.6 识别操作元素

通过\Android\android-sdk\tools\ uiautomatorviewer 去探操作元素的id

driver.findElementById(resource-id 属性)

driver.findElementByClassName(class 属性)

  @Test(description="123",priority=0)
  public void f() {
	  //点击
	  driver.findElementById("com.android.calculator2:id/digit3").click();
	  driver.findElementById("com.android.calculator2:id/plus").click();
	  driver.findElementById("com.android.calculator2:id/digit2").click();
	  driver.findElementById("com.android.calculator2:id/equal").click();
	  //获取文本
	  String sum=driver.findElementByClassName("android.widget.EditText").getText();
	  //输入
	  driver.findElementById("属性").sendKeys("数据");
	  //清空
	  driver.findElementById("属性").clear();
	  System.out.print(sum);
	  
  }

3.7 断言

断言主要用于判断预期结果与实际结果是否一致

 //用于测试期望结果的断言,即测试两个对象是否相等
	  Assert.assertEquals("实际值", "期望值");
	  //布尔值断言
      Boolean rs=sum.contains("");
      Assert.assertTrue(rs);

3.8 模拟按键

driver.pressKeyCode(keycode),必须使用 AmdroidDriver

AndroidDriver driver;
  @Test
public void f() {
	  driver.pressKeyCode("按键");
}

3.9 参数化

使用数组返回参数

@DataProvider
public Object[][] getData(){
	  Object data[][]={ {"234","+","56","290"},{"256","-","40","216"}, {"3","*","6","18"} };
	  return data;
}

  @Test(dataProvider="getData")
public void f(String one,String two,String exact) {
	  
}

读参数文件

@DataProvider
	public Object[][] getParam() throws Exception{
	List<String[]> rows=new ArrayList<String[]>();
	//导入文件
	File file=new File("文件路径");
	//读取字节文件
	FileReader reader=new FileReader(file);
   //字节转字符
	BufferedReader buffer=new BufferedReader(reader);
	String row=null;
	//读取每一行数据
	while((row=buffer.readLine())!=null){
	String columns[]=row.split("\t");
	rows.add(columns);
	}
	//文件关闭
	reader.close();
	//转换Object
	Object[][] data=new Object[rows.size()][];
	for(int i=0;i<rows.size();i++)
	data[i]=rows.get(i);
	return data;
	}

定义获得参数的单独类和方法

使用@DataProvider(name="测试数据集名")修饰获取参数的方法名。
@Test(dataProvider="getParam",dataProviderClass=参数类名.class)

public class Parm {
	@DataProvider()
	public Object[][] getParam() throws Exception{
		  Object data[][]={ {"234","+","56","290"},{"256","-","40","216"}, {"3","*","6","18"} };
		  return data;
	}

}

3.10 测试报告

java项目加入TestReport

package app;	//修改为正确的包名
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
 
public class TestReport extends TestListenerAdapter{
	private String reportPath;
 
	@Override
	public void onStart(ITestContext context) {
		File htmlReportDir = new File("test-output");
        if (!htmlReportDir.exists()) {
        	htmlReportDir.mkdirs();
        }
        String reportName = formateDate()+"_result.html";
        reportPath = htmlReportDir+"/"+reportName;
        File report = new File(htmlReportDir,reportName);
        if(report.exists()){
            try {
                report.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        StringBuilder sb = new StringBuilder("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
				+ "<title>自动化测试报告</title></head><body style=\"background-color:#99FFCC;\">"
				+ "<div id=\"top\" align=\"center\"><p style=\"font-weight:bold;\">测试用例运行结果列表</p>"			
        		+ "<table width=\"90%\" height=\"80\" border=\"1\" align=\"center\" cellspacing=\"0\" rules=\"all\" style=\"table-layout:relative;\">"
        		+ "<thead>"
        		+ "<tr>"
        		+ "<th>测试用例名</th>"
        		+ "<th>测试用例结果</th>"
        		+ "</tr>"
        		+ "</thead>"
        		+ "<tbody style=\"word-wrap:break-word;font-weight:bold;\" align=\"center\">");
        String res = sb.toString();
        try {  
            Files.write((Paths.get(reportPath)),res.getBytes("utf-8"));  
        } catch (IOException e) {  
            e.printStackTrace();  
        } 
	}
	
	@Override
	public void onTestSuccess(ITestResult result) {
		StringBuilder sb = new StringBuilder("<tr><td>");
		sb.append(result.getMethod().getRealClass()+"."+result.getMethod().getMethodName());
		sb.append("</td><td><font color=\"green\">Passed</font></td></tr>");
		String res = sb.toString();
		try {
			Files.write((Paths.get(reportPath)),res.getBytes("utf-8"),StandardOpenOption.APPEND);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
 
	@Override
	public void onTestSkipped(ITestResult result) {
		StringBuilder sb = new StringBuilder("<tr><td>");
		sb.append(result.getMethod().getRealClass()+"."+result.getMethod().getMethodName());
		sb.append("</td><td><font color=\"yellow\">Skipped</font>");
		sb.append("<p align=\"left\">测试用例<font color=\"red\">跳过</font>,原因:<br>");
		sb.append("<br><a style=\"background-color:#CCCCCC;\">");
		Throwable throwable = result.getThrowable(); 
                sb.append(throwable.getMessage()); 
                sb.append("</a></p></td></tr>");
		String res = sb.toString();
		try {
			Files.write((Paths.get(reportPath)),res.getBytes("utf-8"),StandardOpenOption.APPEND);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void onTestFailure(ITestResult result) {
		StringBuilder sb = new StringBuilder("<tr><td>");
        sb.append(result.getMethod().getRealClass()+"."+result.getMethod().getMethodName());
        sb.append("</td><td><font color=\"red\">Failed</font><br>");
        sb.append("<p align=\"left\">测试用例执行<font color=\"red\">失败</font>,原因:<br>");
        sb.append("<br><a style=\"background-color:#CCCCCC;\">");
        Throwable throwable = result.getThrowable();
        sb.append(throwable.getMessage());
        sb.append("</a></p></td></tr>");
        String res = sb.toString();
        try {
            Files.write((Paths.get(reportPath)),res.getBytes("utf-8"),StandardOpenOption.APPEND);
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
 
	@Override
	public void onFinish(ITestContext testContext) {
		StringBuilder sb = new StringBuilder("</tbody></table><a href=\"#top\">返回顶部</a></div></body>");
		sb.append("</html>");
		String msg = sb.toString();
		try {
			Files.write((Paths.get(reportPath)),msg.getBytes("utf-8"),StandardOpenOption.APPEND);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
 
	public static String formateDate(){
		SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd HHmmss");
		Calendar cal = Calendar.getInstance();
		Date date = cal.getTime();
		return sf.format(date);
	}
}

测试类中添加监听器@Listeners({TestReport.class})即可,class 是固定关键字,放到测试类名的上一行

@Listeners({TestReport.class})
public class NewTest {

}

测试报告默认存储位置Java 项目名\test-output

四、adb命令使用

android debug bridge 即 android 调试桥,在cmd输入

查看版本 adb version

显示所有的设备 adb devices

安装程序  adb install .apk 文件名,apk 文件名即程序包名,程序名中不要使用汉字

卸载 App adb uninstall 完整包名,包名不是 apk 文件名,可以使用uiautomatorviewer寻找package

查看手机操作系统的版本号  adb shell getprop ro.build.version.release

显示设备中的包以及包的启动项 

所有包 adb shell getprop ro.build.version.release

查找某个包 adb shell pm list packages | findstr 查找关键字

查找启动项  adb shell dumpsys window w | findstr \/ | findstr name= 

需要提前启用app

在指定设备中运行命令 adb -s 设备名 shell 命令

adb 服务器的开关 adb start-server adb kill-server

五、


http://www.kler.cn/a/470035.html

相关文章:

  • 获取IP地区
  • 微信小程序中 “页面” 和 “非页面” 的区别
  • python requests 415
  • 微服务-Eureka
  • Go Ebiten随机迷宫生成示例
  • 前端(十二)jquery(2)
  • AI大模型的搭建和训练
  • Linux——修改文件夹的所属用户组和用户
  • 【A-Lab】部署手册:开源AAA解决方案 —FreeRADIUS
  • Git - 记录一次由于少输入了一个命令导致的更改丢失
  • HTML——72. 下拉列表分组(下拉列表中多选)
  • STM32 I2C通信外设
  • 用ResNet50+Qwen2-VL-2B-Instruct+LoRA模仿Diffusion-VLA的论文思路,在3090显卡上训练和测试成功
  • frameworks 之 Winscope 工具
  • 5. CSS引入方式
  • 安装和配置 Apache 及 PHP
  • 【蓝桥杯——物联网设计与开发】Part1:GPIO
  • AWS ELB基础知识
  • 题库刷题知识点总结
  • 如何用gunicorn部署python的web应用
  • LLM - 使用 LLaMA-Factory 部署大模型 HTTP 多模态服务 教程 (4)
  • 三甲医院等级评审八维数据分析应用(八)--数据治理的持续改进与反馈机制篇
  • 桌面运维岗面试三十问
  • vue3中onUpdated钩子函数和nextTick的具体使用场景和区别
  • Unix 域协议汇总整理
  • 我用Ai学Android Jetpack Compose之Text