JUnit 是个啥 ? 他跟 Selenium 有什么关系 ?
- 一 . 相关注解
- 1.1 @Test
- 1.2 @BeforeEach
- 1.3 @BeforeAll
- 1.4 @AfterEach
- 1.5 @AfterAll
- 二 . 断言
- 2.1 assertEquals
- 2.2 assertNotEquals
- 2.3 assertTrue / assertFalse : 断言结果为真/假
- 2.4 assertNull / assertNotNull : 断言结果为空 / 不为空
- 三 . 用例的执行顺序
- 3.1 通过 Order 注解来排序
- 四 . 参数化
- 4.1 单参数注解手动编写数据源
- 4.2 多参数从 csv 文件获取数据来源
- 4.3 动态参数方法
- 五 . 测试套件
- 5.1 指定类来运行包下所有用例
- 5.2 指定包来运行包下所有用例
Hello , 大家好 , 又给大家带来新的专栏喽 ~
这个专栏是专门为零基础小白从 0 到 1 了解软件测试基础理论设计的 , 虽然还不足以让你成为软件测试行业的佼佼者 , 但是可以让你了解一下软件测试行业的相关知识 , 具有一定的竞争实力 .
那这篇文章 , 就开始 Selenium 实战了 , 我们可以通过代码来操控浏览器来去做一些操作了 , 这个过程非常有趣 , 敬请期待
那也欢迎大家订阅此专栏 : https://blog.csdn.net/m0_53117341/category_12427509.html
希望大家都能够拿到好的 Offer
我们之前讲过 , 自动化不就是通过 selenium 去实现的吗 , 怎么又引入了个 JUnit 呢 ?
JUnit 是 Java 中的单元测试工具 , 但是我们可以借用 JUnit 库中一些非常好的方法让我们的自动化锦上添花
我们给大家介绍的是 JUnit 5 , 需要我们至少是 JDK 1.8 版本才可以使用
先把 JUnit 的依赖贴在这里
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version><scope>test</scope>
</dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite</artifactId><version>1.8.2</version><scope>test</scope>
</dependency>
如果之前使用过 JUnit4 的话 , 那么这次下载 Junit5 可能就会失败
解决方法是找到之前下载好的 JUnit4 依赖 , 删掉之后再重新下载 JUnit5
那引入了 JUnit 之后 , 我们就不需要再创建 main 去调用各个方法了
一 . 相关注解
JUnit 中提供了非常强大的注解功能
我们常用的注解都有 : @Test、@BeforeEach、@BeforeAll、@AfterEach、@AfterAll
1.1 @Test
在方法上面加上 @Test 就代表该方法是测试方法
我们看看加上这个注解有什么用处 ?
@Test
表示该方法是测试方法 , 执行当前这个类的时候 , 会自动执行该类下所有带 @Test
注解的用例
在写自动化的时候 , 我们通常把每个方法都叫做每个自动化的测试用例
package com.ethan3;import org.junit.jupiter.api.Test;public class junitTest {@Testpublic void testAnnotation() {System.out.println("测试 @Test 注解");}
}
我们通过控制台也能发现好多信息
1.2 @BeforeEach
@BeforeEach 指的是当我们的方法被 @BeforeEach 修饰的时候 , 就意味着当前的方法需要在每个用例执行之前都执行一次
package com.ethan3;import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;public class junitTest {@BeforeEachpublic void testAnnotation() {System.out.println("测试 @Test 注解");}@Testpublic void test1() {System.out.println("test1");}@Testpublic void test2() {System.out.println("test2");}
}
1.3 @BeforeAll
@BeforeAll
的作用是当前的方法需要在当前类下所有用例之前执行一次
但是加到方法上面 , 运行还报错了
它提示我们要想使用 @BeforeAll 的话 , 这个方法需要是静态的
package com.ethan3;import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;public class junitTest {@BeforeAllpublic static void testAnnotation() {System.out.println("测试 @Test 注解");}@Testpublic void test1() {System.out.println("test1");}@Testpublic void test2() {System.out.println("test2");}
}
我们也可以参考 类变量 , 整个类只有一个 , 所以只会打印一次
1.4 @AfterEach
@AfterEach 指的是 当前的方法需要在每个用例执行之后都执行一次
package com.ethan3;import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;public class junitTest {@AfterEachpublic void testAnnotation() {System.out.println("测试 @Test 注解");}@Testpublic void test1() {System.out.println("test1");}@Testpublic void test2() {System.out.println("test2");}
}
1.5 @AfterAll
@AfterAll 指的是 当前的方法在当前类下所有的用例执行之后执行一次
注意 : 被该注解修饰的方法必须是静态的
二 . 断言
2.1 assertEquals
当我们获取到文本的时候 , 我们可以判断该文本对不对
比如这个案例 :
那我们就可以通过断言来站在机器的角度上看一下到底是否正确
package com.ethan3;import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;public class junitTest {@Testpublic void review() {ChromeDriver driver = new ChromeDriver();driver.get("https://www.baidu.com");String text = driver.findElement(By.cssSelector("#su")).getAttribute("value");// 得到"百度一下"// 假如我们获取到的不是百度一下,而是百度两下// 对于测试人员来说,就是 Bug// 但是对于程序来说,是正确的,因为他并未报错System.out.println(text);// 使用 Assertions.assertEquals 进行断言Assertions.assertEquals("期盼值", text);driver.quit();}
}
如果跟我们设定的预期值不符 , 就会报错 , 测试人员就会立马排查问题
2.2 assertNotEquals
assertNotEquals 的作用就是当实际值和预期值不符的时候 , 程序才不报错 , 运行成功
package com.ethan3;import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;public class junitTest {@Testpublic void review() {ChromeDriver driver = new ChromeDriver();driver.get("https://www.baidu.com");String text = driver.findElement(By.cssSelector("#su")).getAttribute("value");// 得到"百度一下"// 假如我们获取到的不是百度一下,而是百度两下// 对于测试人员来说,就是 Bug// 但是对于程序来说,是正确的,因为他并未报错System.out.println(text);// 使用 Assertions.assertEquals 进行断言Assertions.assertNotEquals("期盼值", text);driver.quit();}}
2.3 assertTrue / assertFalse : 断言结果为真/假
package com.ethan3;import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;public class junitTest {@Testpublic void assertDemo() {Assertions.assertTrue(1 == 1);}
}
2.4 assertNull / assertNotNull : 断言结果为空 / 不为空
package com.ethan3;import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;public class junitTest {@Testpublic void assertDemo2() {Assertions.assertNull("","你好");// 断言失败}@Testpublic void assertDemo3() {Assertions.assertNotNull("","你好");// 断言成功}
}
三 . 用例的执行顺序
官方网站没有明确说明默认的执行顺序的规则 , 测试用例的执行并不会按照我们编写用例的顺序来执行
那就会存在一定问题 :
比如我们的博客系统 , 我们想要对登录功能进行测试 , 需要编写测试用例
- 检查登录页面展现是否正确
- 检查正常登录
- 检查异常登陆
如果先执行了第二个测试用例 , 页面就跳转到了博客列表页 . 那接下来再去执行第一条测试用例 , 就肯定会报错
我们只有先检查登录页面展现是否正确之后 , 再去检查第三条
3.1 通过 Order 注解来排序
我们的当前类中 , 有非常多的用例 , 那么在这个类中 , 按照什么顺序来执行呢 ?
我们首先需要在类上加上一个注解 : @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
这个注解的作用是强调(声明)用 order 来进行排序
接下来在每个用例上面加上 @Order(顺序) 来决定每个用例的执行顺序
package com.ethan4;import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class junitTest {@Test@Order(1)public void loginTest() {System.out.println("1::loginTest");}@Test@Order(2)public void editTest() {System.out.println("2::editTest");}@Test@Order(3)public void indexTest() {System.out.println("3::indexTest");}}
注意 : 用例上面的 @Test 注解是不能删掉的
四 . 参数化
针对于博客系统的登录功能 , 每个用户的用户名和密码是各不相同的 . 这样的话就会导致参数各不相同
这样的话我们就需要写出无穷无尽的方法去解决他
我们就可以使用参数化的方式来处理各式各样的参数问题 , 尽可能通过一个用例 , 多组参数来模拟用户的行为
4.1 单参数注解手动编写数据源
在使用参数化注解之前 , 需要先声明该方法为参数化方法 , 使用注解 : @ParameterizedTest
那这个参数从哪里来 , 我们还需要声明一个注解 : @ValueSource(strings = {“甄嬛”,“沈眉庄”,“安陵容”})
这就是我们的参数化 , 通过参数化注解 , 我们就把参数传递给测试用例中 , 供测试用例使用
package com.ethan4;import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class junitTest {@ParameterizedTest@ValueSource(strings = {"甄嬛","沈眉庄","安陵容"})public void sparamTest(String name) {System.out.println("name:" + name);}
}
那这个 @ValueSource() 到底是怎么回事 ?
@ValueSource(数据类型方法 = {“参数1”,“参数2”,“参数3”…})
其中 , 数据类型支持 : short、byte、int、long、double、char…
这几个数据类型在 JUnit 中不单单是简单的数据类型 , JUnit 将这些数据类型封装成了一个方法
我们回到 IDEA 也可以看一下
但是 @ValueSource() 只支持单参数
4.2 多参数从 csv 文件获取数据来源
package com.ethan4;import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;public class junitTest {@ParameterizedTest@CsvSource({"甄嬛,20", "沈眉庄,25", "安陵容,26"})public void moreParamsTest(String name,int age) {System.out.println("name:" + name + " age:" + age);}}
那要是我们有非常多的数据 , 我们难不成要写非常多的 “” , 这肯定不行
我们就可以在其他文件写好 , 让我们的注解直接调用到这个文件 , 执行里面的数据
我们创建一个 csv 文件 , 在里面写好数据
然后使用 @CsvFileSource(files = “路径名”) 导入文件
package com.ethan4;import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class junitTest {@ParameterizedTest // 表示该方法是参数化方法@CsvFileSource(files = "D:\\study\\JavaCode\\selenium_demo\\test.csv") // 导入第三方文件public void csvFileParamsTest(String name,int age) {System.out.println("name:" + name + " age:" + age);}}
如果发生乱码导致报错的话 , 手动创建一个 txt 文件 , 按照这种格式来编辑数据 , 然后再修改后缀名为 csv
zhangsan,18
lisi,20
wangwu,26
4.3 动态参数方法
通过动态方法提供数据源 , 构造出动态的数据
package com.ethan4;import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;import java.util.stream.Stream;public class junitTest {@ParameterizedTest // 先声明该方法是参数化方法@MethodSource("methodParams") // 指定动态数据来源public void dynamicParamsTest(String name,int age) {System.out.println("name:" + name + " age:" + age);}/*** 1. 方法必须是静态的* 2. 返回值是 Stream<Arguments>* 3. 返回 Stream.of 对象* @return* @throws InterruptedException*/static Stream<Arguments> methodParams() throws InterruptedException {// 构造动态参数String[] arr = new String[5];for (int i = 0; i < arr.length; i++) {Thread.sleep(500);arr[i] = System.currentTimeMillis() + "";}return Stream.of(Arguments.arguments(arr[0],20),Arguments.arguments(arr[1],20),Arguments.arguments(arr[2],20),Arguments.arguments(arr[3],20),Arguments.arguments(arr[4],20));}}
我们运行一下
那我们把刚才写好的数据来源注释掉 , 然后编写一个新的数据来源 , 与我们打印的用例同名 , 但是上面的 @MethodSource() 注解 , 我们就不写括号里面的数据来源了 , 这样也是可以的
package com.ethan4;import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;import java.util.stream.Stream;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class junitTest {@ParameterizedTest // 先声明该方法是参数化方法@MethodSource // 指定动态数据来源public void dynamicParamsTest(String name,int age) {System.out.println("name:" + name + " age:" + age);}static Stream<Arguments> dynamicParamsTest() throws InterruptedException {// 构造动态参数String[] arr = new String[5];for (int i = 0; i < arr.length; i++) {Thread.sleep(500);arr[i] = System.currentTimeMillis() + "";}return Stream.of(Arguments.arguments(arr[0],20),Arguments.arguments(arr[1],20),Arguments.arguments(arr[2],20),Arguments.arguments(arr[3],20),Arguments.arguments(arr[4],20));}
}
也是可以运行的
最后注意一点 , 使用了参数化注解的方法不能再用 @Test 注解了 , 也就意味着 @Test 只能作用在非参数化的用例上
五 . 测试套件
为了模拟测试套件 , 我们新创建三个类 , 每个类中都写上一些最基础的测试方法
我们之前都是运行一个类中的所有方法 , 那我们要是想运行多个类中的多个方法 , 意思就是我们怎么一起运行 test1 test2 test3 呢 ?
我们就可以使用测试套件
5.1 指定类来运行包下所有用例
我们首先需要创建一个测试套件 , 实际上就是创建一个新的类 , 加上注解 : @Suite , 标识这是一个测试套件类而不是测试类
接下来 , 我们需要指定我们需要跑哪些类 , 使用注解 : @SelectClasses() , 里面使用 {} 把我们需要测试的类加进去
运行一下
那如果我们 @SelectClasses() 里面的某一个类里面的某个用例没加 @Test , 那么就不会执行这个方法
我们刚刚还介绍了参数化的用例 , 他并没有用 @Test 注解啊 ?
其实参数化用例 , 测试套件也是能扫描到的
5.2 指定包来运行包下所有用例
使用 : @SelectPackages("包的路径) 来指定要执行的方法集合
运行一下
他为什么只跑了 junitTest 了呢 ?
这是因为我们指定包来运行范围的话 , 文件名命名规则需要以 Test/Tests 结尾 (T 还得必须大写)