软件测试自动化Java篇【Selenium+Junit 5】
文章目录
- Selenium
- 环境部署
- 自动化测试例子
- 常见的元素操作
- 窗口
- 等待
- 浏览器的操作
- 弹窗
- 选择器
- 执行脚本
- 文件上传
- 浏览器参数
- Junit 5
- 导入依赖
- Junit 4 和 Junit5 注解对比
- 断言
- 测试顺序
- 参数化
- 单参数
- 多参数
- 动态参数
- 测试套件
- 指定类来运行测试用例
- 指定包名来运行包下测试用例
Selenium
为什么选择selenium作为我们的web自动化测试工具?
- 开源免费
- 支持多浏览器
- 支持多系统
- 支持多语言【Java,Python,C#,Rubby,JavaScript,Kolin】
- selenium包提供了很多可供测试使用的API
环境部署
Chrome浏览器

Chrome驱动【驱动器版本要和浏览器版本对应越详细越好】

然后把驱动包放在安装jdk的bin目录下

selenium工具包

自动化测试例子
分为接口自动化测试和UI自动化测试
UI自动化测试包含界面测试
我们都知道用户的设备很多,有手机,平板和电脑。这些设备运行的项目在发布之初都需要经过测试,这些测试又分为web自动化测试 和 移动端自动化测试。这里以 web自动化测试 为主。
自动化测试的工具有很多,比如 QTP、Selenium,Jmeter、Loadrunner。本期就以 Selenium 为主进行介绍最简单的自动化测试
测试点击百度
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;private class AutoTest {// 模拟百度搜索关键词public static void baiduSearchKey() {String url = "https://www.baidu.com";String keyWords = "新闻";ChromeDriver chromeDriver=new ChromeDriver();try {Thread.sleep(5000);// 输入框输入文字chromeDriver.findElement(new By.ByXPath("//*[@id=\"kw\"]")).sendKeys(keyWords);Thread.sleep(5000);// 点击搜索按钮chromeDriver.findElement(new By.ByXPath("//*[@id=\"su\"]")).click();Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();}public static void main(String[] args) {baiduSearchKey();}
}
获取元素方法有很多,但经常用的是 ByXPath和ByCssSelector

ByXpath
/:相当于子级一层一层的完整xpath路径
//:相当于越级,直接越过之前的html标签,根据id来查询标签
后文中只有之前的几个操作时获取部分xpath,以后的获取都是完整的获取xpath路径【看//数量即可知道是否根据带通配符或者id查询来区别】
常见的元素操作
- 输入文本
sendKeys(str)
对元素操作的前提是元素能够被找到,如果查询的元素不是可编辑的文本标签,那么前端不受影响,后端也不报错。按照程序的设定正常退出 - 点击操作
click()&提交操作submit()
private static void TestBlogLogin() {// 输入邮箱String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(3000);// 输入邮箱chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).sendKeys(email);Thread.sleep(3000);// 输入密码chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[2]/input")).sendKeys(password);Thread.sleep(3000);// 点击登录【也可以回车登录】chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div/form[1]/button")).click();//chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div/form[1]/button")).submit();Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
提交操作
submit仅适用于form表单之类的submit操作,一般用的少
Selenium更推荐使用click,功能比submit更丰富
- 清除操作
clear()
private static void TestBlogLogin() {// 输入正确的邮箱和密码String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).sendKeys("手滑输入错了");Thread.sleep(3000);// 清除文本chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).clear();Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).sendKeys(email);Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[2]/input")).sendKeys(password);Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div/form[1]/button")).click();Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
- 获取文本值
getText()
private static void TestBlogLogin() {// 输入正确的邮箱和密码String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {// 获取第一道题目的标题String text = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[2]")).getText();System.out.println("getText()获取到的文本:" + text);Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}


发现即使 URL 跳转到了别的网页,由于线程休眠3s的缘故,并不会很快就去获取页面元素而是给了页面一个加载的时间,因此可以完全获取到跳转到其它页面的标签
- 获取属性
getAttribute(attr)

private static void TestBlogLogin() {// 输入正确的邮箱和密码String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {// 获取属性String role = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("role");Thread.sleep(3000);String aria_valuenow = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("aria-valuenow");Thread.sleep(3000);String aria_valuemin = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("aria-valuemin");Thread.sleep(3000);String aClass = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("class");Thread.sleep(3000);String style = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("style");Thread.sleep(3000);System.out.printf("role=%s\naria_valuenow=%s\naria_valuemin=%s\naClass=%s\nstyle=%s\n", role, aria_valuenow, aria_valuemin, aClass, style);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

6. 获取标题getTitle()和urlgetCurrenturl()
// 模拟百度搜索关键词
private static void baiduSearchKey() {String url = "https://www.baidu.com";String keyWords = "新闻";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);HashMap<String, List<String>> info = new HashMap<>();try {Thread.sleep(5000);ArrayList<String> before = new ArrayList<String>() {{add(chromeDriver.getTitle());add(chromeDriver.getCurrentUrl());}};// 输入框输入文字chromeDriver.findElement(new By.ByXPath("//*[@id=\"kw\"]")).sendKeys(keyWords);Thread.sleep(5000);// 点击搜索按钮chromeDriver.findElement(new By.ByXPath("//*[@id=\"su\"]")).click();Thread.sleep(5000);System.out.println("搜索之后");ArrayList<String> after = new ArrayList<String>() {{add(chromeDriver.getTitle());add(chromeDriver.getCurrentUrl());}};info.put("before", before);info.put("after", after);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();System.out.println(info);
}

窗口
- 窗口大小
窗口大小的设置:最大/小化,全屏窗口,手动设置窗口大小
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 窗口最大化chromeDriver.manage().window().maximize();Thread.sleep(1500);// 窗口最小化chromeDriver.manage().window().minimize();Thread.sleep(1500);// 全屏chromeDriver.manage().window().fullscreen();Thread.sleep(1500);// 手动设置窗口大小chromeDriver.manage().window().setSize(new Dimension(1024, 768));Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
- 切换窗口
一般的窗口切换可能会导致找不到元素的BUG
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 点击准备跳转chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/a[6]")).click();String value = chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/div/div/div/div/div/div[2]/form/span[2]/input")).getAttribute("value");System.out.println(value);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

由于无法获取跳转页面的元素,所以就会报错。因此需要一个页面切换功能
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 获取当前页面句柄String currentHandle = chromeDriver.getWindowHandles().toString();System.out.println("当前首页句柄:"+currentHandle);Thread.sleep(1500);// 点击准备跳转chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/a[6]")).click();// 获取跳转之后所有标签页的句柄Set<String> handles = chromeDriver.getWindowHandles();for (String handle : handles) {if (currentHandle != handle){chromeDriver.switchTo().window(handle);}}String value = chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/div/div/div/div/div/div[2]/form/span[2]/input")).getAttribute("value");System.out.println(value);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
运行的退出码已经恢复正常

3. 屏幕截图
需要导入一个常用的commons-io库
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version>
</dependency>
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 点击准备跳转chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/a[6]")).click();File screenshotAs = chromeDriver.getScreenshotAs(OutputType.FILE);// 把屏幕截图好的文件放在指定的路径下String fileName = "my.png";try {FileUtils.copyFile(screenshotAs, new File(fileName));} catch (IOException e) {throw new RuntimeException(e);}String value = chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/div/div/div/div/div/div[2]/form/span[2]/input")).getAttribute("value");System.out.println(value);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
这里会发生报错,由于屏幕截图原因。所以会保存当时的获取状态。

会把当前的现场截图保存下来【由于没有切换页面,所以也就会报错,截图代码发生在报错之前的代码,所以也就保留了事故现场】
等待
程序执行的速度比页面渲染速度快很多,因此之前的等待都是使用线程的强制等待,这并不合理。等待一共分为四种。
强制等待、隐式等待、显示等待、流畅等待
- 强制等待
程序阻塞运行,Thread.sleep()。会用到,但是自动化里不能用的特别多,否则拖慢速度 - 隐式等待
隐式等待会一直轮询判断元素是否存在,如果不存在就等待设置好的时间里不断地进行轮询知道元素能够被找到
private static void waitController(){String url = "https://www.baidu.com";ChromeDriver chromeDriver=new ChromeDriver();// 添加隐式等待:会作用于chromeDriver整个生命周期chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));chromeDriver.get(url);chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")).sendKeys("迪丽热巴");chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input")).click();chromeDriver.findElement(new By.ByXPath("/html/body/div[2]/div[3]/div[1]/div[3]/div[1]/div[1]/div/div/div/div/div[2]/div/a/div/p/span/span"));chromeDriver.quit();
}
- 显示等待
隐式等待针对的是 driver的整个生命周期,所以对全部元素有效,但有时候并非全部都需要等待,我们可以只针对其中一个元素进行等待操作,其余元素交给程序快速运行进而提高自动化测试的效率
private static void webDriverWait() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")).sendKeys("迪丽热巴");chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input")).click();// 只针对某个元素显示等待new WebDriverWait(chromeDriver, Duration.ofSeconds(3)).until(lambda -> chromeDriver.findElement(new By.ByXPath("/html/body/div[2]/div[3]/div[1]/div[3]/div[1]/div[1]/div/div/div/div/div[2]/div/a/div/p/span/span")));chromeDriver.get(url);
}
这里使用了 lambda表达式 的语法,我们查看一下 until的源码

参数是一个泛型,所以可以传入任何泛型函数。然后就是执行的循环,获取页面元素操作。
- 隐式等待 和 显式等待 并不能同时使用,可能会出现意想不到的结果
- 隐式等待 和 显示等待 均不出现效果的时候可以使用 强制等待
浏览器的操作
浏览器的回退,前进和刷新操作
private static void navigateControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();// 简写
// chromeDriver.get(url);// 非简写chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));// 想要回退到访问百度网址之前的状态chromeDriver.navigate().back();// 前进,有进入到了百度首页chromeDriver.navigate().forward();// 刷新百度首页chromeDriver.navigate().refresh();chromeDriver.quit();
}
弹窗
弹窗一共有3大类型,如下所示分别为:alert、confirm和prompt

但是却发现,浏览器进入检查时候无法获取到弹窗的元素
处理弹窗的步骤
- 将driver对象作用到弹窗上(切换到弹窗)
- 选择确认/取消(提示弹窗输入文本)
作用到弹窗上:driver.switchTo.alert()
确认:driver.accept()
取消:`driver.dismiss()
输入:driver.sendKeys()
前端代码
<div id="alertVue"><button v-on:click="f()">{{value}}</button>
</div>
<div id="confirmVue"><button v-on:click="f()">{{value}}</button>
</div>
<div id="promptVue"><button v-on:click="f()">{{value}}</button>
</div><script type="text/javascript" src="vue.js"></script>
<script>const alertVue = new Vue({el: '#alertVue',data: {value: "Alert弹窗",},methods: {f() {window.alert("alert弹窗");}}});const confirmVue = new Vue({el: '#confirmVue',data: {value: "Confirm确认框",},methods: {f() {if (window.confirm("是否确认?")) {confirmVue.value = "true";} else {confirmVue.value = "false";}}}});const promptVue = new Vue({el: '#promptVue',data: {value: "输入框",},methods: {f() {promptVue.value = window.prompt("输入框");}}});
</script>
测试代码
private static void alertController(){String url = "file:///D:/Documents/Program/Java/SeleniumTest/Autotest/src/test/java/alertHTML.html";ChromeDriver chromeDriver=new ChromeDriver();chromeDriver.navigate().to(url);try {Thread.sleep(1500);// 点击 alertchromeDriver.findElement(new By.ByXPath("/html/body/div[1]/button")).click();Thread.sleep(1500);// 切换到弹窗Alert alert = chromeDriver.switchTo().alert();// 确认操作:accept/取消操作:dismissalert.dismiss();Thread.sleep(1500);// 点击 confirmchromeDriver.findElement(new By.ByXPath("/html/body/div[2]/button")).click();Thread.sleep(1500);// 切换到弹窗alert = chromeDriver.switchTo().alert();// 确认操作alert.accept();Thread.sleep(1500);// 点击 promptchromeDriver.findElement(new By.ByXPath("/html/body/div[3]/button")).click();Thread.sleep(1500);// 切换到弹窗alert = chromeDriver.switchTo().alert();Thread.sleep(1500);// 在页面上看不到输入的文本效果,但是其实已经输入了alert.sendKeys("阿斯顿马丁");// 接受文本alert.accept();Thread.sleep(1500);// 确认操作} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
- prompt 弹窗进行
sendKeys(str)的时候不会在页面出现效果,但是程序已经输入有文本信息 - alert 警告弹窗虽然只有确认选项,但是也可以用
dismiss和accept都可以进行取消操作 - url 一定要复制浏览器中选择而不是从资源路径中选择【浏览器的url前边会多一个
file:///】
选择器
选择框的处理困难在于无法将下拉框的元素进行定位,每当点击检查的时候,下拉框就会消失
这个时候我们可以根据三个条件进行选择
- 文本
- 属性
- 下标
html代码
<div id="selectVue"><select><option v-for="(month, index) in monthes" v-bind:value="index+1">{{month}}</option></select>
</div><script>
const selectVue = new Vue({el: '#selectVue',data: {monthes: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December",],},
});
</script>
Java代码
private static void alertController() {String url = "file:///D:/Documents/Program/Java/SeleniumTest/Autotest/src/test/java/alertHTML.html";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.navigate().to(url);try {// option选择器WebElement element = chromeDriver.findElement(new By.ByXPath("/html/body/div[4]/select"));Thread.sleep(1500);// 先创建选择框对象Select select = new Select(element);Thread.sleep(1500);// 根据文本来选择【并不会像正常操作一样点击一下选择框而是直接选中下拉框】select.selectByVisibleText("September");Thread.sleep(1500);// 根据属性值选择select.selectByValue("1");Thread.sleep(1500);// 根据下标选择【0开始】,选取九月select.selectByIndex(8);Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
XPath的选择器是从1开始;而下标选择从0开始
执行脚本
执行 js 代码,有时候会遇到输入文本不完全或者不生效的情况下,可以使用执行原生js脚本进行解决
private static void scriptControl(){String url = "https://image.baidu.com/";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.navigate().to(url);try {// 执行 js 代码让页面置底chromeDriver.executeScript("document.documentElement.scrollTop = 500");Thread.sleep(1500);// 执行 js 代码让页面置顶chromeDriver.executeScript("document.documentElement.scrollTop = 0");Thread.sleep(1500);// 百度输入框输入指定文本chromeDriver.navigate().to("https://www.baidu.com/");chromeDriver.executeScript("var ss=document.querySelector('#kw'); ss.value='英雄钢笔'");Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
文件上传
文件上传的难点在于当点击上传文件时的弹窗是系统自己的弹窗,并不是浏览器的弹窗。对于Selenium而言并不能控制系统。

private static void fileUploadControl(){String url = "file:///D:/Documents/Program/Java/SeleniumTest/Autotest/src/test/java/alertHTML.html";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.navigate().to(url);try {Thread.sleep(1500);chromeDriver.findElement(new By.ByXPath("/html/body/div[5]/input")).sendKeys("D:\\Documents\\Program\\Java\\SeleniumTest\\Autotest\\src\\test\\java\\alertHTML.html");Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
这里运行完毕之后并不是把文件上传到服务器而是要把上传的文件路径+文件以名称的方式放到这里
浏览器参数
实际工作中,测试人员将自动化部署在机器上自动的执行,测试人员不会每次都一直盯着自动化执行的过程,而是直接查看自动化执行的结果。
无头模式:类似于Linux的&模式运行命令挂载在后台进程,不在前台显示。浏览器而言的话就是没有画面的运行一些自动化测试脚本,这样可以节约一定的机器内存资源。
private static void paramControl() {// 百度搜索 ”测试开发工程师“// 先创建选项对象然后设置浏览器参数String url = "https://www.baidu.com/";ChromeOptions chromeOptions = new ChromeOptions();chromeOptions.addArguments("-headless");// 添加浏览器参数设置ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")).sendKeys("测试开发工程师");chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input")).click();chromeDriver.quit();
}
Junit 5
自动化是Selenium脚本来实现的,Junit是java的单元测试工具.只不过我们在实现自动化的时候需要借用Junit库里面提供的一些方法
Junit 5支持最低支持jdk8
目前Java领域内最为流行的单元测试框架 ------ JUnit
Junit 5 = Junit Platform + Junit Jupiter + Junit Vintage
Junit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
Junit Jupiter: Junit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行
Junit Vintage: 由于JUnit已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎

导入依赖
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.2</version><scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite -->
<dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite</artifactId><version>1.9.2</version><scope>test</scope>
</dependency>
Junit 4 和 Junit5 注解对比
| Junit 5 | Junit 4 | 说明 |
|---|---|---|
| @Test | @Test | 被注解的是一个测试方法,和Junit 4相同 |
| @BeforeAll | @BeforeClass | 被注解的(静态)方法将在当前类中的所有@Test方法前执行一次 |
| @BeforeEach | @Before | 被注解的方法将在当前类中的么欸个@Test方法前执行 |
| @AffterAll | @AfterClass | 被注解的(静态)方法将在当前类中的所有@Test方法后执行一次 |
| @AffterEach | @After | 被注解的方法将在当前类中的么欸个@Test方法后执行 |
| @DIsable | @Ignore | 被注解的方法不会执行(将被跳过),但会报告为已执行 |
Junit 4 中@Test 是 org.junit.Test;
Junit 5 中@Test 是 org.junit.jupiter.api.Test;
@BeforeAll注解必须用在static修饰的代码块上,否则会报错
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Testvoid t1() {System.out.println("@Test1");}@Testvoid t2() {System.out.println("@Test2");}@Testvoid t3() {System.out.println("@Test3");}
}

After效果和Before效果相反
import org.junit.jupiter.api.*;public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Testvoid t1() {System.out.println("@Test1");}@Testvoid t2() {System.out.println("@Test2");}@Testvoid t3() {System.out.println("@Test3");}@AfterEachvoid afterEach() {System.out.println("@AfterEach");}@AfterAllstatic void afterAll() {System.out.println("@AfterAll");}
}

断言
| 断言方法 | 说明 |
|---|---|
| assertEquals(expected, actual) | 如果 expected 不等于 actual ,则断言失败 |
| assertFalse(booleanExpression) | 如果 booleanExpression 不是 false ,则断言失败 |
| assertNull(actual) | 如果 actual 不是 null ,则断言失败 |
| assertNotNull(actual) | 如果 actual 是 null ,则断言失败 |
| assertTrue(booleanExpression) | 如果 booleanExpression 不是 true ,则断言失败 |
@Test
void testAttributeValue() {String url = "https://www.baidu.com/";ChromeOptions chromeOptions = new ChromeOptions();chromeOptions.addArguments("-headless");ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));String value = chromeDriver.findElement(By.cssSelector("#su")).getAttribute("value");// 假如这里获取到的属性值不是 百度一下 而是百度两下System.out.println("value:" + value);Assertions.assertEquals("百度两下", value);chromeDriver.quit();
}
断言失败

断言成功
@Test
void testAttributeValue() {String url = "https://www.baidu.com/";ChromeOptions chromeOptions = new ChromeOptions();chromeOptions.addArguments("-headless");ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));String value = chromeDriver.findElement(By.cssSelector("#su")).getAttribute("value");// 假如这里获取到的属性值不是 百度一下 而是百度两下System.out.println("value:" + value);Assertions.assertNotEquals("百度两下", value);chromeDriver.quit();
}

不会提示错误地方
测试顺序
import org.junit.jupiter.api.*;public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Testvoid ae() {System.out.println("@Test1");}@Testvoid bc() {System.out.println("@Test2");}@Testvoid ab() {System.out.println("@Test3");}@AfterEachvoid afterEach() {System.out.println("@AfterEach");}@AfterAllstatic void afterAll() {System.out.println("@AfterAll");}
}@BeforeAll
@BeforeEach
@Test3
@AfterEach
@BeforeEach
@Test1
@AfterEach
@BeforeEach
@Test2
@AfterEach
@AfterAll
发现执行顺序按照方法名排序一样,这里的顺序并不是按照程序中代码的顺序执行。有时候我们需要按照顺序执行代码,比如测试系统的时候必须要用户先登陆才可以后续的一些操作的时候就需要按照顺序执行测试代码
添加一个 @TestMethodOrder 注解,然后添加参数 MethodOrderer.OrderAnnotation.class,最后再给每个执行的代码编辑顺序即可
import org.junit.jupiter.api.*;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Test@Order(1)void ae() {System.out.println("@Test1");}@Test@Order(2)void bc() {System.out.println("@Test2");}@Test@Order(3)void ab() {System.out.println("@Test3");}@AfterEachvoid afterEach() {System.out.println("@AfterEach");}@AfterAllstatic void afterAll() {System.out.println("@AfterAll");}
}
@BeforeAll
@BeforeEach
@Test1
@AfterEach
@BeforeEach
@Test2
@AfterEach
@BeforeEach
@Test3
@AfterEach
@AfterAll
参数化
- 尽可能地通过一个用例,多组参数来模拟用户的行为
- 使用了参数化注解之后就不能再使用
@Test注解
单参数
@ParameterizedTest// 使用参数化注解之前需要先声明该方法为参数化方法
@ValueSource(strings = {"1969612859@qq.com", "1969612858@qq.com"})// 通过注解提供数据源
void SingleParamsTest(String email){System.out.println(email);
}

可以看到 @ValueSource(数据类型={参数1, 参数2, 参数3...}) 提供的数据源类型有很多

多参数
@ParameterizedTest
@CsvSource({"1969612859@qq.com, admin", "1969612858@qq.com, root"})
void muchParamsTest(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}email:1969612859@qq.com<==>password:admin
email:1969612858@qq.com<==>password:root
当参数很多的时候,我们可以把参数放入文件中然后通过文件读取获取参数
@ParameterizedTest
@CsvFileSource(files = "D:\\Documents\\Program\\Java\\SeleniumTest\\Autotest\\src\\test\\resources\\userData.csv")
void csvFileParamsTest(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}

发现 @CsvFileSource 的源码可以放入很多参数来设置文件,这里只放了 files 一个参数的值,文件默认编码已经是 UTF-8 所以就可以不用管编码问题

动态参数
这里导入一个随机工具类 common-util–使用方法
// 动态参数
static Stream<Arguments> methodParams(){// 构造动态参数String[] arr = new String[10];for (int i = 0; i < arr.length; i++) {arr[i] = RandomUtil.randomNumbers(10)+"@qq.com";}return Stream.of(Arguments.arguments(arr[0], RandomUtil.randomString(8)),Arguments.arguments(arr[1], RandomUtil.randomString(8)),Arguments.arguments(arr[2], RandomUtil.randomString(8)),Arguments.arguments(arr[3], RandomUtil.randomString(8)),Arguments.arguments(arr[4], RandomUtil.randomString(8)),Arguments.arguments(arr[5], RandomUtil.randomString(8)),Arguments.arguments(arr[6], RandomUtil.randomString(8)),Arguments.arguments(arr[7], RandomUtil.randomString(8)),Arguments.arguments(arr[8], RandomUtil.randomString(8)),Arguments.arguments(arr[9], RandomUtil.randomString(8)));
}
@ParameterizedTest
@MethodSource(value = "methodParams")
void dynamicMethodParams(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}
email:0150592006@qq.com<==>password:5knqh9nd
email:6506175266@qq.com<==>password:jndm1vx6
email:4815105218@qq.com<==>password:9e6yaky2
email:5072613647@qq.com<==>password:56vjv9ff
email:1471676761@qq.com<==>password:0uq2mx9r
email:0637284991@qq.com<==>password:k5xcauzb
email:8646939279@qq.com<==>password:q9zltwfd
email:7903224405@qq.com<==>password:wrgn7fxr
email:2771169159@qq.com<==>password:f3l255bc
email:8080867273@qq.com<==>password:mnpveuxj

这里为了方便,就直接使用
Arguments来替代
小技巧
如果数据源与测试方法同名,则无需执行数据源是来自哪里的
// 动态参数
static Stream<Arguments> dynamicMethodParams(){// 构造动态参数String[] arr = new String[10];for (int i = 0; i < arr.length; i++) {arr[i] = RandomUtil.randomNumbers(10)+"@qq.com";}return Stream.of(Arguments.arguments(arr[0], RandomUtil.randomString(8)),Arguments.arguments(arr[1], RandomUtil.randomString(8)),Arguments.arguments(arr[2], RandomUtil.randomString(8)),Arguments.arguments(arr[3], RandomUtil.randomString(8)),Arguments.arguments(arr[4], RandomUtil.randomString(8)),Arguments.arguments(arr[5], RandomUtil.randomString(8)),Arguments.arguments(arr[6], RandomUtil.randomString(8)),Arguments.arguments(arr[7], RandomUtil.randomString(8)),Arguments.arguments(arr[8], RandomUtil.randomString(8)),Arguments.arguments(arr[9], RandomUtil.randomString(8)));
}
@ParameterizedTest
@MethodSource// 自动寻找与方法名同名的数据源
void dynamicMethodParams(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}
email:7264428849@qq.com<==>password:wewp75wh
email:4884099760@qq.com<==>password:q57n49kf
email:2779525807@qq.com<==>password:dkyfns0t
email:7260421546@qq.com<==>password:kfxow9gw
email:9750978471@qq.com<==>password:a5dwh5g4
email:7595861999@qq.com<==>password:g9gszroo
email:5860224630@qq.com<==>password:er521mby
email:7009711558@qq.com<==>password:qhftn0rh
email:9374899761@qq.com<==>password:zsjkkyns
email:6637232897@qq.com<==>password:fu8g79om
测试套件
指定类来运行测试用例
package TestSuite;import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;// 测试套件:通过 @Suite 注解
@Suite
@SelectClasses(value = {aaa.class, bbb.class})
public class runSuite {
}
测试套件测试 aaa.java, bbb.java, ccc.java 的测试结果,类下想要运行的用例必须要被 @Test 注解修饰

@Suite 注解标识该类是测试套件类而不是测试类
@SelectClasses 部分源码告知我们需要填上 class 类名

指定包名来运行包下测试用例
package TestSuite;import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;// 测试套件:通过 @Suite 注解
@Suite
//@SelectClasses(value = {aaa.class, bbb.class})
@SelectPackages("TestSuite")
public class runSuite {
}
发现连 ccc.java 的测试也打印输出了

最好是把要测试的文件以 xxxTest 结尾比较好,但是因为文件夹是
TestSuite的缘故,所以现在即使不是 xxxTest 结尾的包文件也能被扫描到并执行@Test注解的方法,否则就会执行报错。
报错问题如下所示

解决方案如下所示

相关文章:
软件测试自动化Java篇【Selenium+Junit 5】
文章目录Selenium环境部署自动化测试例子常见的元素操作窗口等待浏览器的操作弹窗选择器执行脚本文件上传浏览器参数Junit 5导入依赖Junit 4 和 Junit5 注解对比断言测试顺序参数化单参数多参数动态参数测试套件指定类来运行测试用例指定包名来运行包下测试用例Selenium 为什么…...
Clip:学习笔记
Clip 文章目录Clip前言一、原理1.1 摘要1.2 引言1.3 方法1.4 实验1.4.1 zero-shot Transfer1.4.2 PROMPT ENGINEERING AND ENSEMBLING1.5 局限性二、总结前言 阅读论文: Learning Transferable Visual Models From Natural Language Supervision CLIP 论文逐段精读…...
STM32CubexMX与FreeRTOS学习
目录 LED与EXTI配置 基本定时器使用 软件定时器 在HAL库中实现printf 重点--记得自己添加头文件 队列实现 二值信号量实现 计数信号量实现 DMA实现 ADC配置 RTC配置 看门狗 窗口看门狗 FreeRTOS结合MX软件开发,基础配置直接生成,我们只…...
Master Slave 主从同步错误 Slave_IO_Running:NO/Slave_SQL_Running: No
Master Slave 主从同步错误 Slave_IO_Running:NO Slave_SQL_Running:Yes #在Slave库上查看状态 mysql> show slave status\G Slave_IO_Running: No Slave_SQL_Running: Yes #重启master库:service mysqld restart mysql> show master status; ------------…...
JavaScript函数之prototype原型和原型链
文章目录1. 原型2. 显式和隐式原型3. 原型链3.1 访问顺序4. instanceof4.1 如何判断1. 原型 函数的prototype属性 每个函数都有一个prototype属性,它默认指向一个Object空对象(即:原型对象)。原型对象中有一个属性constructor&a…...
从上海分时电价机制调整看转供电用户电能计费
安科瑞 耿敏花2022年12月16日,上海市发改委发布《关于进一步完善我市分时电价机制有关事项的通知》(沪发改价管〔2022〕50号)。通知明确上海分时电价机制,一般工商业及其他两部制、大工业两部制用电夏季(7、8、9月)和冬季…...
TypeScript类型体操:获取数组中元素对象属性的值作为新类型
title: TypeScript类型体操:获取数组中元素对象属性的值作为新类型 date: 2023-03-03 20:58:24 categories: TypeScript类型体操 tags: TypeScript类型体操TypeScript 首先先说获取数组中元素对象属性的值作为新类型的解决方案 使用 as const 强调不可变数组使用 …...
npm,yarn和pnpm
npm扁平的node_modules结构比如项目依赖了A 和 C,而 A 和 C 依赖了不同版本的 B1.0 和 B2.0,D也依赖B1.0, node_modules 结构如下:node_modules ├── A1.0.0 ├── B1.0.0 └── C1.0.0└── node_modules└── B2.0.0C依赖的B2.0因为版…...
【算法】【数组与矩阵模块】在排好序的矩阵中找数,时间复杂度O(M+N)
目录前言问题介绍解决方案代码编写java语言版本c语言版本c语言版本思考感悟写在最后前言 当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~ 在此感谢左大神让我对算法有了新的感悟认识! 问题介绍 …...
【Java|基础篇】计算机中数据的存储规则
文章目录前言:1.计算机中的数据2.二进制的介绍二进制的运算规则常见的进制3.字符的存储4.汉字的存储5.图片的存储6.音频的存储总结:前言: 本篇文章只是为了科普 计算机中数据的存储规则 1.计算机中的数据 计算机的数据大致分为三类:文本数据,图片和音频 注:视频是图片和音频…...
RestTemplate使用HttpClient连接池
文章目录RestTemplate使用HttpClient连接池ClientHttpRequestFactorySimpleClientHttpRequestFactorySimpleClientHttpRequestFactory 设置超时时间HttpURLConnection的缺点HttpComponentsClientHttpRequestFactoryPoolingHttpClientConnectionManager配置连接池HttpClient总结…...
Python 操作Redis
在 Python中我们使用 redis库来操作 Redis数据库。Redis数据库的使用命令这里就不介绍了。 需要安装 redis库。检查是否安装redis: pip redis 如果未安装,使用 pip命令安装 redis。 pip install redis #安装最新版本 一、Redis连接 Redis提供两个类 Re…...
CEC2020:鱼鹰优化算法(Osprey optimization algorithm,OOA)求解CEC2020(提供MATLAB代码
一、鱼鹰优化算法简介 鱼鹰优化算法(Osprey optimization algorithm,OOA)由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出,其模拟鱼鹰的捕食行为。 鱼鹰是鹰形目、鹗科、鹗属的仅有的一种中型猛禽。雌雄相似。体长51-64厘米…...
词对齐 - MGIZA++
文章目录关于 MGIZAgiza-py安装 MGIZA命令说明mkclsd4normhmmnormplain2sntsnt2coocsnt2coocrmpsnt2plainsymalmgizageneral parameters:No. of iterations:parameter for various heuristics in GIZA for efficient training:parameters for describing the type and amount o…...
GUI 之 Tkinter编程
GUI 图形界面,Tkinter 是 Python 内置的 GUI 库,IDLE 就是 Tkinter 设计的。 1. Tkinter 之初体验 import tkinter as tkroot tk.Tk() # 创建一个窗口root.title(窗口标题)# 添加 label 组件 theLabel tk.Label(root, text文本内容) theLabel.p…...
【软件测试】性能测试面试题都问什么?面试官想要什么?回答惊险避坑......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 1、你认为不同角色关…...
后端开发基础能力以及就Java的主流开发框架介绍
前言:java语言开发转后端,必须了解后端主流的一些东西,共勉。 后端开发需要具备以下基础能力: 1.编程语言:熟练掌握至少一门编程语言,如Java、Python、Ruby、PHP、C#等。 2.数据结构和算法:具…...
H2数据库连接时用户密码错误:Wrong user name or password [28000-214] 28000/28000 (Help)
H2数据库连接时用户密码错误: 2023-03-03 08:25:07 database: wrong user or password; user: "SA" org.h2.message.DbException: Wrong user name or password [28000-214]出现的问题配置信息原因解决办法org.h2.message.DbException: Wrong user name or password …...
青岛诺凯达机械盛装亮相2023济南生物发酵展,3月与您相约
BIO CHINA生物发酵展,作为生物发酵产业一年一度行业盛会,由中国生物发酵产业协会主办,上海信世展览服务有限公司承办,2023第10届国际生物发酵展(济南)于2023年3月30-4月1日在山东国际会展中心(济…...
【JAVA程序设计】【C00111】基于SSM的网上图书商城管理系统——有文档
基于SSM的网上图书商城管理系统——有文档项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架开发的网上在线图书售卖商城项目,本项目分为三种权限:系统管理员、卖家、买家 管理员角色包含以下功能: 用户信息管理、权限管理、订…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
