文章目录
- 1. 思维导图编写 Web 自动化测试用例
- 2. 创建测试项目
- 3. 根据思维导图设计用户管理系统自动化测试用例
- 3.1 准备工具类
- 3.2 测试登录页面
- 3.3 测试用户列表页
- 3.4 测试添加用户页
- 3.5 测试修改用户页
- 3.6 未登录状态
- 4. 自动化测试项目总结
- 4.1 自动化测试项目实现步骤
- 4.2 当前项目亮点
本篇针对我之前写的项目进行自动化测试。
项目链接: 用户管理系统(用户名:admin;密码:admin)
1. 思维导图编写 Web 自动化测试用例
2. 创建测试项目
- 创建 Maven 项目
- 在 test 包下编写相关测试代码
- 导入自动化测试需要的相关依赖
在 pom.xml 中引入自动化测试相关依赖:
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<!-- 保存屏幕截图文件需要用到的包-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- Junit 中 参数化测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- junit 中 测试套件 -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
3. 根据思维导图设计用户管理系统自动化测试用例
3.1 准备工具类
在 common 包下创建 AutoTestUtils 类,该类需要提供以下功能:
- 创建驱动对象,提供给页面使用。
- 创建隐式等待,作用于驱动对象的整个生命周期。
- 提供屏幕截图方法,以及保存截图的格式。
/**
* @Description: 创建驱动对象;提供屏幕截图方法
* @Date 2023/4/15 9:59
* @Author:
*/
public class AutoTestUtils {
public static EdgeDriver driver;
/**
* @param : 创建驱动对象
* @return EdgeDriver
* @description TODO
*/
public static EdgeDriver createDriver() {
EdgeOptions options = new EdgeOptions();
options.addArguments("-remote-allow-origins=*");
// 驱动对象没有创建
if(driver == null) {
driver = new EdgeDriver(options);
// 创建隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
// 驱动对象已经创建好了
return driver;
}
/**
* @param str: 类名
* @return void
* @description 获取屏幕截图,把所有的用例执行的结果都保存下来
*/
public void getScreenShot(String str) throws IOException {
List<String> list = getTime();
// ./src/test/java/com/usermanagerWebAutoTest/日期/类名_日期_时间.png
String filename = "./src/test/java/com/usermanagerWebAutoTest/" + list.get(0) +
"/" + str + "_" + list.get(1) + ".png";
// 进行屏幕截图
File srcFile = driver.getScreenshotAs(OutputType.FILE);
// 将屏幕截图生成的图片放到指定路径下
FileUtils.copyFile(srcFile,new File(filename));
}
public List<String> getTime() {
// 文件格式 2023-04-15/2023-04-15_12:36:00
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sim2 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
// 以天的维度按文件夹进行保存
String dirname = sim1.format(System.currentTimeMillis());
String filename = sim2.format(System.currentTimeMillis());
List<String> list = new ArrayList<>();
list.add(dirname);
list.add(filename);
return list;
}
}
在 Tests 包下创建 DriverQuitTest 类,该类的主要作用是用来释放驱动
/**
* @Description: 释放驱动
* @Date 2023/4/15 9:59
* @Author:
*/
public class DriverQuitTest extends AutoTestUtils {
public static EdgeDriver driver = createDriver();
@Test
void driverQuit() {
driver.quit();
}
}
在 Tests 包下创建 runSuite 类,该类的就是测试套件
/**
* @Description: 测试套件
* @Date 2023/4/15 10:00
* @Author:
*/
@Suite
@SelectClasses({UserNoLoginTest.class,UserLoginTest.class,UserListTest.class,AddUserTest.class,UpdateUserTest.class,DriverQuitTest.class})
public class runSuite {
}
3.2 测试登录页面
创建 UserLoginTest 类,该类的测试用例有 6 个。
- 页面是否可以正常打开
- 不输入用户名,输入密码,点击登录
- 输入用户名,不输入密码,点击登录
- 用户名和密码都不输入,点击登录
- 输入错误的用户名和密码,点击登录
- 输入正确的用户名和密码,点击登录
/**
* @Description: 测试登录页面
* @Date 2023/4/15 10:00
* @Author:
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserLoginTest extends AutoTestUtils {
// 1. 访问浏览器对象 2. 访问登录页面的 URL
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl() {
driver.get("http://47.108.57.239:8080/login.html");
}
/**
* @param :
* @return void
* @description 检查页面是否可以正常打开
*/
@Test
@Order(1)
void loginPageLoadRight() throws IOException {
driver.findElement(By.cssSelector("#body > div > h3"));
driver.findElement(By.cssSelector("#loginname"));
driver.findElement(By.cssSelector("#submit"));
getScreenShot(getClass().getName());
}
/**
* @param password:
* @return void
* @description 不输入用户名,输入密码,点击登录
*/
@ParameterizedTest
@CsvSource({"111"})
@Order(2)
void loginFailUsernameNull(String password) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#loginname")).clear(); // 先清空一下
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
// 登录失败结果检测
Thread.sleep(1000);
// 切换到弹窗进行处理
Alert alert = driver.switchTo().alert();
// 点击确认
alert.accept();
getScreenShot(getClass().getName());
}
/**
* @param username:
* @return void
* @description 输入用户名,不输入密码,点击登录
*/
@ParameterizedTest
@CsvSource({"111"})
@Order(3)
void loginFailPasswordNull(String username) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#loginname")).clear(); // 先清空一下
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#loginname")).sendKeys(username);
driver.findElement(By.cssSelector("#submit")).click();
// 登录失败结果检测
Thread.sleep(1000);
// 切换到弹窗进行处理
Alert alert = driver.switchTo().alert();
// 点击确认
alert.accept();
getScreenShot(getClass().getName());
}
/**
* @return void
* @description 用户名和密码都不输入,点击登录
*/
@Test
@Order(4)
void loginFailUsernameAndPasswordNull() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#loginname")).clear(); // 先清空一下
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#submit")).click();
// 登录失败结果检测
Thread.sleep(1000);
// 切换到弹窗进行处理
Alert alert = driver.switchTo().alert();
// 点击确认
alert.accept();
getScreenShot(getClass().getName());
}
/**
* @return void
* @description 输入错误的用户名和密码,点击登录
*/
@ParameterizedTest
@CsvSource({"1111,1111"})
@Order(5)
void loginFail(String username,String password) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#loginname")).clear(); // 先清空一下
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#loginname")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
// 登录失败结果检测
Thread.sleep(1000);
// 切换到弹窗进行处理
Alert alert = driver.switchTo().alert();
// 点击确认
alert.accept();
getScreenShot(getClass().getName());
}
/**
* @return void
* @description 输入正确的用户名和密码,点击登录
*/
@ParameterizedTest
@CsvSource({"admin,admin"})
@Order(6)
void loginSucced(String username,String password) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#loginname")).clear(); // 先清空一下
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#loginname")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
// 登录成功检测,如果跳转到用户列表页,才算是成功的
driver.findElement(By.cssSelector("#body > div > h3"));
Thread.sleep(2000);
getScreenShot(getClass().getName());
// 这里页面跳转了,重新返回到登录页面
driver.navigate().back();
}
}
3.3 测试用户列表页
创建 UserListTest 类,该类的测试用例有 4 个(默认登录状态下)。
- 页面内容显示是否正常
- 是否能跳转到添加用户页
- 是否能跳转到修改用户页
- 查询用户功能是否正常
/**
* @Description: 测试用户列表页
* @Date 2023/4/15 10:00
* @Author:
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserListTest extends AutoTestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl() {
driver.get("http://47.108.57.239:8080/list.html");
}
/**
* @return void
* @description 检查页面内容显示是否正常
*/
@Test
@Order(1)
void ListPageLoadRight() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#submit1"));
driver.findElement(By.cssSelector("#body > div > table > tbody:nth-child(1) > tr > th:nth-child(1)"));
driver.findElement(By.cssSelector("#all > li:nth-child(1) > a"));
driver.findElement(By.cssSelector("#body > div > div:nth-child(3) > a:nth-child(1)"));
driver.findElement(By.cssSelector("#delete"));
Thread.sleep(1000);
getScreenShot(getClass().getName());
}
/**
* @return void
* @description: 检测添加用户按钮链接跳转正常
*/
@Test
@Order(2)
void LinkJumpAdd() throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#body > div > div:nth-child(3) > a:nth-child(1)")).click();
Thread.sleep(1000);
getScreenShot(getClass().getName());
driver.navigate().back();
}
/**
* @return void
* @description: 检测修改用户按钮链接跳转正常
*/
@Test
@Order(3)
void LinkJumpUpdate() throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#info > tr:nth-child(1) > th:nth-child(10) > a:nth-child(1)")).click();
Thread.sleep(1000);
getScreenShot(getClass().getName());
driver.navigate().back();
}
/**
* @return void
* @description: 检测查询用户功能是否正常
*/
@ParameterizedTest
@CsvSource({"超级管理员"})
@Order(4)
void SelectUser(String name) throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#ipt_name")).sendKeys(name);
driver.findElement(By.cssSelector("#submit1")).click();
driver.findElement(By.cssSelector("#info > tr > th:nth-child(3)"));
Thread.sleep(1000);
getScreenShot(getClass().getName());
}
}
3.4 测试添加用户页
创建 AddUserTest 类,该类的测试用例有 5 个(默认登录状态下)。
- 页面内容是否显示正常
- 管理员权限为超管,填写用户信息时,如果用户名重复,不能提交。
- 管理员权限为超管,填写用户信息时,如果用户名不重复,可以提交。
- 管理员权限为普通用户,可以正常填写信息,但不可以提交。
- 返回链接跳转正常。
/**
* @Description: 测试添加用户页
* @Date 2023/4/15 10:00
* @Author:
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class AddUserTest extends AutoTestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl() {
driver.get("http://47.108.57.239:8080/add.html");
}
/**
* @return void
* @description 检查页面内容显示是否正常
*/
@Test
@Order(1)
void AddPageLoadRight() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#username"));
driver.findElement(By.cssSelector("#password"));
driver.findElement(By.cssSelector("#btn_sub"));
driver.findElement(By.cssSelector("#btn_back"));
Thread.sleep(1000);
getScreenShot(getClass().getName());
}
/**
* @return void
* @description :管理员权限为超管,填写用户信息时,如果用户名重复,不能提交。
*/
@ParameterizedTest
@CsvSource({"111,111,111,111,20,111,111"})
@Order(2)
void SuperTube_UserRepeatFail(String user,String login,String pass,String pass2,String age,String qq,String em) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#username")).sendKeys(user);
driver.findElement(By.cssSelector("#loginname")).sendKeys(login);
driver.findElement(By.cssSelector("#password")).sendKeys(pass);
driver.findElement(By.cssSelector("#password2")).sendKeys(pass2);
driver.findElement(By.cssSelector("#age")).sendKeys(age);
driver.findElement(By.cssSelector("#qq")).sendKeys(qq);
driver.findElement(By.cssSelector("#email")).sendKeys(em);
driver.findElement(By.cssSelector("#btn_sub")).click();
Thread.sleep(1000);
getScreenShot(getClass().getName());
}
/**
* @return void
* @description :管理员权限为超管,填写用户信息时,如果用户名不重复,可以提交。
*/
@ParameterizedTest
@CsvSource({"111,111,20,111,111"})
@Order(3)
void SuperTube_UserRepeatSucc(String pass,String pass2,String age,String qq,String em) throws InterruptedException, IOException {
Random random = new Random();
int n = random.nextInt(100000);
driver.findElement(By.cssSelector("#username")).sendKeys(String.valueOf(n));
driver.findElement(By.cssSelector("#loginname")).sendKeys(String.valueOf(n));
driver.findElement(By.cssSelector("#password")).sendKeys(pass);
driver.findElement(By.cssSelector("#password2")).sendKeys(pass2);
driver.findElement(By.cssSelector("#age")).sendKeys(age);
driver.findElement(By.cssSelector("#qq")).sendKeys(qq);
driver.findElement(By.cssSelector("#email")).sendKeys(em);
driver.findElement(By.cssSelector("#btn_sub")).click();
Thread.sleep(1000);
Alert alert = driver.switchTo().alert();
alert.accept();
Thread.sleep(1000);
Alert alert2 = driver.switchTo().alert();
alert2.dismiss();
Thread.sleep(1000);
getScreenShot(getClass().getName());
driver.navigate().back();
}
/**
* @return void
* @description: 返回链接跳转正常。
*/
@Test
@Order(4)
void ReturnLinkJumpSucc() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#btn_back")).click();
driver.findElement(By.cssSelector("#body > div > h3"));
Thread.sleep(1000);
getScreenShot(getClass().getName());
}
}
3.5 测试修改用户页
创建 UpdateUserTest 类,该类的测试用例有 5 个(默认登录状态下)。
- 页面内容是否显示正常。
- 管理员权限为超管,修改用户信息时,如果密码和确认密码栏不相同,不能提交。
- 管理员权限为超管,修改用户信息时,如果密码和确认密码栏相同,可以提交。
- 管理员权限为普通用户,可以正常修改信息,但不能提交。
- 返回链接跳转正常,重置按钮功能正常。
/**
* @Description: 测试修改用户页
* @Date 2023/4/15 10:01
* @Author:
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UpdateUserTest extends AutoTestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl() {
driver.get("http://47.108.57.239:8080/update.html?uid=3");
}
/**
* @return void
* @description 检查页面内容显示是否正常
*/
@Test
@Order(1)
void AddPageLoadRight() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#body > div > h3"));
String text = driver.findElement(By.cssSelector("#loginname")).getAttribute("value");
Assertions.assertEquals("111",text);
driver.findElement(By.cssSelector("#btn_sub"));
Thread.sleep(1000);
getScreenShot(getClass().getName());
}
/**
* @return void
* @description :管理员权限为超管,修改用户信息时,如果密码和确认密码栏不相同,不能提交。
*/
@ParameterizedTest
@CsvSource({"1234,111,111666"})
@Order(2)
void SuperTube_PassInequalityFail(String pass,String pass2,String em) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#password")).sendKeys(pass);
driver.findElement(By.cssSelector("#password2")).sendKeys(pass2);
driver.findElement(By.cssSelector("#email")).sendKeys(em);
driver.findElement(By.cssSelector("#btn_sub")).click();
Thread.sleep(1000);
Alert alert = driver.switchTo().alert();
alert.accept();
Thread.sleep(1000);
getScreenShot(getClass().getName());
driver.findElement(By.cssSelector("#btn_reset")).click();
}
/**
* @return void
* @description :管理员权限为超管,修改用户信息时,如果密码和确认密码栏相同,可以提交。
*/
@ParameterizedTest
@CsvSource({"234567,234567,1111111"})
@Order(3)
void SuperTube_PassIdenticalSucc(String pass,String pass2,String em) throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#password")).sendKeys(pass);
driver.findElement(By.cssSelector("#password2")).sendKeys(pass2);
driver.findElement(By.cssSelector("#email")).sendKeys(em);
driver.findElement(By.cssSelector("#btn_sub")).click();
Thread.sleep(1000);
Alert alert = driver.switchTo().alert();
alert.accept();
getScreenShot(getClass().getName());
driver.navigate().back();
}
/**
* @return void
* @description: 返回链接跳转正常。
*/
@Test
@Order(4)
void ReturnLinkJumpSucc() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("#btn_back")).click();
String text = driver.findElement(By.cssSelector("#body > div > h3")).getText();
Assertions.assertEquals("用户信息列表",text);
getScreenShot(getClass().getName());
}
}
3.6 未登录状态
创建 UserNoLoginTest 类,该类的测试用例有 3 个。
- 跳转到用户列表页。
- 跳转到添加用户页。
- 跳转到修改用户页。
/**
* @Description: 测试未登录状态下
* @Date 2023/4/15 10:01
* @Author:
*/
public class UserNoLoginTest extends AutoTestUtils {
public static EdgeDriver driver = createDriver();
/**
* @param :
* @return void
* @description 未登录状态跳转到用户列表页
*/
@Test
void JumpUserListFail() throws InterruptedException, IOException {
driver.get("http://47.108.57.239:8080/list.html");
Thread.sleep(1000);
String text = driver.findElement(By.cssSelector("#body > div > h3")).getText();
Assertions.assertEquals("管理员登录",text);
getScreenShot(getClass().getName());
}
/**
* @return void
* @description 未登录状态跳转到添加用户页
*/
@Test
void JumpAddUserFail() throws InterruptedException, IOException {
driver.get("http://47.108.57.239:8080/add.html");
Thread.sleep(1000);
String text = driver.findElement(By.cssSelector("#body > div > h3")).getText();
Assertions.assertEquals("管理员登录",text);
getScreenShot(getClass().getName());
}
/**
* @return void
* @description 未登录状态跳转到修改用户页
*/
@Test
void JumpUpdateUserFail() throws InterruptedException, IOException {
driver.get("http://47.108.57.239:8080/update.html");
Thread.sleep(1000);
String text = driver.findElement(By.cssSelector("#body > div > h3")).getText();
Assertions.assertEquals("管理员登录",text);
getScreenShot(getClass().getName());
}
}
4. 自动化测试项目总结
4.1 自动化测试项目实现步骤
- 根据自己的项目,用思维导图的方式设计出UI自动化测试用例;
- 结合自己编写的测试用例,使用 Selenium4 自动化测试工具和 Junit5 单元测试框框架,实现 Web 自动化测试;
- 然后就是进行模块划分,主要是按照两个包划分,一个是工具类包,用来创建驱动对象和提供屏幕截图方法,还有一个包是测试用例包,这个包下面的类是按照以页面为单位编写的测试代码,避免了每个方法都要创建驱动对象的麻烦,最后将这些测试类加入到测试套件中。
- 注意不要等项目整个代码写完后再进行测试,最好是写一部分代码测试一下。比如我编写代码时写完一个页面的测试用例后,再进行测试。
4.2 当前项目亮点
- 只创建一次驱动对象,避免每个用例重复创建驱动对象造成时间和资源的浪费
- 使用了 Junit5 中提供的注解:避免生成过多的对象,造成资源和时间的浪费,提高了自动化的执行效率
- 测试套件:降低了测试人员的工作量,通过套件一次执行所有要运行的测试用例
- 使用了等待:隐式等待+强制等待(提高了自动化运行效率,提高了自动化的稳定性)
- 使用屏幕截图:方便问题的追溯以及问题的解决
- 使用参数化:保持用例的简洁,提高代码的可读性