自动化实战 - 博客系统

针对前面的博客系统项目进行自动化实战 - 项目链接 : 博客系统SSM


1. 使用脑图编写 web 自动化测试用例 

2. 准备工作

1. 创建一个 Maven 项目

2. 在 test 包下的 java 下建包管理自己后续要写的代码. 例如 : com.blogWebAutoTest, 再在 blogWebAutoTest 包下建两个包 common, Tests.

3. 导入自动化测试需要的相关依赖.

<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>
<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>

3. 根据脑图设计博客系统自动化测试用例

3.1 准备工具类

在 common 包下创建 AutoTestUtils 类, 该类主要提供的功能有 :

  1. 创建驱动对象, 提供给其他页面使用.
  2. 创建隐式等待, 作用于软件测试的整个生命周期.
  3. 提供屏幕截图的方法, 以及保存截图的格式.
public class AutoTestUtils {
    public static EdgeDriver driver;

    // 创建驱动对象
    public static EdgeDriver getDriver() {
        if(driver == null) {
            driver = new EdgeDriver();
        }
        // 创建隐式等待,作用于driver的整个生命周期 (10秒)
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        return driver;
    }

    // 屏幕截图, 保存文件的格式
    public List<String> getTime() {
        // 文件夹按照天数的维度进行保存
        // 文件格式 2023-4-3 20:07
        SimpleDateFormat sim1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
        SimpleDateFormat sim2 = new SimpleDateFormat("yyyy-MM-dd");
        String filename = sim1.format(System.currentTimeMillis());
        String dirname = sim2.format(System.currentTimeMillis());
        List<String> list = new ArrayList<>();
        list.add(dirname);
        list.add(filename);
        return list;
    }

    // 屏幕截图的方法
    public void getScreenShotAs(String str) throws IOException {
        List<String> list = getTime();
        String filePath = "./src/test/java/com/blogWebAutoTest/"+list.get(0)+"/"+str+"_"+list.get(1)+".png";
        File srcFile = driver.getScreenshotAs(OutputType.FILE);
        FileUtils.copyFile(srcFile, new File(filePath));
    }
}

在 Tests 包下创建 DriverQuitTest 类, 该类主要是用来释放驱动的. (放在测试套件的最后一个)

public class DriverQuitTest extends AutoTestUtils {
    public static EdgeDriver driver;
    @Test
    void driverQuit() {
        driver = getDriver();
        driver.quit();
    }
}

3.2 测试博客注册页

创建 BlogRegTest 类, 该类的测试用例主要有 4 个: 

  1. 注册页面是否能够正常打开.
  2. 输入用户名, 密码, 确认密码, 且用户名第一次注册, 密码和确认密码一致 . 【两次弹窗】
  3. 输入用户名, 密码, 确认密码, 且用户名第一次注册, 密码和确认密码不一致. 【一次弹窗】
  4. 输入用户名, 密码, 确认面, 用户名不是第一次注册. 【弹窗提示用户名已被使用】

其他的用户名为空, 密码为空, 确认密码为空, 就不测了, 下面的登录有类似的.

相关代码 : 

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogRegTest extends AutoTestUtils {
    public static EdgeDriver driver;
    @BeforeAll
    static void baseController() {
        driver = getDriver();
        driver.get("http://43.139.1.94:8080/reg.html");
    }

    /**
     * 检查注册页面是否能够正常打开
     * 检查登录页的导航栏的登录按钮,以及确认密码是否出现
     */
    @Test
    @Order(1)
    void loginPageLoadRight() throws IOException {
        driver.findElement(By.cssSelector("#password2"));
        driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
        getScreenShotAs(getClass().getName());
    }

    /**
     * 检查正常注册的情况
     * 两次弹窗
     */
    @ParameterizedTest
    @CsvSource({"李白,123,123"})
    @Order(2)
    void loginSuccess(String username,String password,String password2) throws IOException, InterruptedException {
        // 注册之前先清空用户名,密码和确认密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#password2")).clear();
        // 注册步骤
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#password2")).sendKeys(password2);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测注册结果 , 第一个弹窗显示注册成功, 第二个弹窗显示是否要去登录页面
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        // 确认弹窗
        Thread.sleep(100);
        alert.dismiss();
        getScreenShotAs(getClass().getName());
    }

    /**
     * 检查注册失败情况 1
     * 账号已存在
     */
    @ParameterizedTest
    @CsvSource({"张三,123,123"})
    @Order(3)
    void loginFail(String username, String password,String password2) throws InterruptedException, IOException {
        // 登录之前先清空用户名和密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#password2")).clear();
        // 登录步骤
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#password2")).sendKeys(password2);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测注册失败的场景
        // 切换到弹窗, 进行弹窗的处理(隐式等待不生效,不得不加入强制等待)
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        getScreenShotAs(getClass().getName());
    }

    /**
     * 检查注册失败情况 2
     * 密码和确认密码不一致
     * @param username
     * @param password
     * @param password2
     * @throws InterruptedException
     * @throws IOException
     */
    @ParameterizedTest
    @CsvSource({"小李,123,1234"})
    @Order(4)
    void loginFail2(String username, String password,String password2) throws InterruptedException, IOException {
        // 登录之前先清空用户名和密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#password2")).clear();
        // 登录步骤
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#password2")).sendKeys(password2);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测注册失败的场景
        // 切换到弹窗, 进行弹窗的处理(隐式等待不生效,不得不加入强制等待)
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        getScreenShotAs(getClass().getName());
    }
}

3.3 测试博客登录页

创建 BlogLoginTest 类, 该类的测试用例主要有 6 个 : 

  1. 登录页面是否能够正常打开.
  2. 用户名密码都不输入, 点击提交. 【弹窗提示请输入用户名】
  3. 不输入用户名, 输入密码, 点击提交. 【弹窗提示请输入用户名】
  4. 不输入密码, 输入用户名, 点击提交. 【弹窗提示请输入密码】
  5. 输入错误的用户名, 密码, 点击提交. 【弹窗提示用户名或密码错误】 
  6. 输入正确的用户名, 密码, 点击提交.

相关代码 : 

// 登录页面
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogLoginTest extends AutoTestUtils {
    public static EdgeDriver driver;
    @BeforeAll
    static void baseController() {
        driver = getDriver();
        driver.get("http://43.139.1.94:8080/login.html");
    }

    /**
     * 检查登录页面是否正常打开
     * 检查登录页的登录,用户名,密码等字样是否出现
     */
    @Test
    @Order(1)
    void loginPageLoadRight() throws IOException {
        driver.findElement(By.cssSelector("body > div.login-container > div > h3"));
        driver.findElement(By.xpath("/html/body/div[2]/div/div[1]/span"));
        driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/span"));
        getScreenShotAs(getClass().getName());
    }

    /**
     * 检查正常登录的情况
     * 页面是否显示用户名, 密码, 提交字样
     */
    @ParameterizedTest
    @CsvSource({"张三,123","李四,123"})
    @Order(6)
    void loginSuccess(String username,String password) throws IOException {
        // 登录之前先清空用户名和密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        // 登录步骤
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测登录结果 (检查我的列表页的查看详情按钮)
        driver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > div.mydetail > a:nth-child(1) > b"));
        getScreenShotAs(getClass().getName());
        // 一个用户测试完了,需要进行回退
        driver.navigate().back();
    }

    /**
     * 检查异常登录的情况 4 种情况 - 账号密码错误
     * 账号密码错误, 会弹窗提示
     */
    @ParameterizedTest
    @CsvSource({"admin,345", "zhangsan,234"})
    @Order(5)
    void loginFail(String username, String password) throws InterruptedException, IOException {
        // 登录之前先清空用户名和密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        // 登录步骤
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测登录失败的场景
        // 切换到弹窗, 进行弹窗的处理(隐式等待不生效,不得不加入强制等待)
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        getScreenShotAs(getClass().getName());
    }

    /**
     * 未输入密码
     * @param username
     * @throws InterruptedException
     */
    @ParameterizedTest
    @ValueSource(strings = {"zhangsan"})
    @Order(4)
    void loginFail2(String username) throws InterruptedException, IOException {
        // 登录之前先清空用户名和密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        // 登录步骤
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测登录失败的场景
        // 切换到弹窗, 进行弹窗的处理(隐式等待不生效,不得不加入强制等待)
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        getScreenShotAs(getClass().getName());
    }

    /**
     * 未输入用户名
     * @param password
     * @throws InterruptedException
     */
    @ParameterizedTest
    @ValueSource(strings = {"123"})
    @Order(3)
    void loginFail3(String password) throws InterruptedException, IOException {
        // 登录之前先清空用户名和密码
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        // 登录步骤
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测登录失败的场景
        // 切换到弹窗, 进行弹窗的处理(隐式等待不生效,不得不加入强制等待)
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        getScreenShotAs(getClass().getName());
    }

    /**
     * 账号,, 密码都没有输入
     * @throws InterruptedException
     */
    @Test
    @Order(2)
    void loginFail4() throws InterruptedException, IOException {
        driver.findElement(By.cssSelector("#submit")).click();
        // 检测登录失败的场景
        // 切换到弹窗, 进行弹窗的处理(隐式等待不生效,不得不加入强制等待)
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        // 警告弹窗
        alert.accept();
        getScreenShotAs(getClass().getName());
    }
}

3.3 测试我的博客列表页

创建 MyBlogListTest 类, 该类的测试用例只有一个 : 

  1. 测试我的博客列表页能否正常显示.

相关代码 : 

public class MyBlogListTest extends AutoTestUtils {
    public static EdgeDriver driver;

    // 创建驱动对象
    @BeforeAll
    static void baseController() {
        driver = getDriver();
        driver.get("http://43.139.1.94:8080/myblog_list.html");
    }

    /**
     * 个人博客列表页可以正常显示
     */
    @Test
    void MyListPageLoadRight() throws IOException {
        // 查看是否有删除按钮
        driver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > div.mydetail > a:nth-child(3) > b"));
        driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
        getScreenShotAs(getClass().getName());
    }
}

3.4 测试大家的博客列表页

创建 EveryBlogListTest 类, 该类的测试用例只有一个 : 

  1. 大家的列表页能否正常显示.

相关代码 : 

public class EveryBlogListTest extends AutoTestUtils {
    public static EdgeDriver driver;

    @BeforeAll
    static void baseController() {
        driver = getDriver();
        driver.get("http://43.139.1.94:8080/blog_list.html");
    }

    /**
     * 大家的列表页能否正常显示
     * @throws IOException
     */
    @Test
    void everyListPageLoadRight() throws IOException {
        driver.findElement(By.cssSelector("body > div.nav > a"));
        driver.findElement(By.cssSelector("#listDiv > div:nth-child(1) > a"));
        getScreenShotAs(getClass().getName());
    }
}

3.5 测试博客编辑页

创建 BlogEditTest 类, 该类的主要测试用例有 : 

  1. 检查编辑页是否可以正常打开.
  2. 测试填写博客标题和正文, 是否能否正常发布博客.

相关代码 : 

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogEditTest extends AutoTestUtils {
    public static EdgeDriver driver;

    @BeforeAll
    static void baseController() {
        driver = getDriver();
        driver.get("http://43.139.1.94:8080/blog_edit.html");
    }
    /**
     * 检查博客编辑页可以正常打开
     */
    @Test
    @Order(1)
    void editPageLoadRight() throws IOException {
        // 检查正文中 "在这里写下一篇博客"
        driver.findElement(By.cssSelector("#editorDiv > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
        // 检查编辑器上的 H5 标题
        driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(17) > a > i"));
        getScreenShotAs(getClass().getName());
    }

    /**
     * 测试填写博客标题, 博客正文, 是否能够正常发布博客
     * @throws InterruptedException
     * @throws IOException
     */
    @Test
    @Order(2)
    void editAndSubmitBlog() throws InterruptedException, IOException {
        String expect = "陈伟霆 vs 彭于晏";
        driver.findElement(By.cssSelector("#title")).sendKeys(expect);
        // 因博客系统使用到的编辑器是第三方插件, 所以不能直接使用 sendKeys 向编辑器发送文本,使用下划线,斜线代替
        driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(21) > a > i")).click();
        driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(6) > a > i")).click();
        driver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button")).click();
        // 获取我的列表页第一条博客的标题文本, 检查是否符合预期
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
        alert.accept();
        getScreenShotAs(getClass().getName());
        String actual = driver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > div.title")).getText();
        Assertions.assertEquals(expect,actual);
    }
}

3.6 测试博客详情页

创建 BlogDetailTest 类, 该类的主要测试用例有 : 

  1. 测试博客详情页显示是否符合预期.

相关代码 : 

public class BlogDetailTest extends AutoTestUtils{
    public static EdgeDriver driver;

    @BeforeAll
    static void baseController() {
        driver = getDriver();
        driver.get("http://43.139.1.94:8080/blog_content.html?id=17");
    }
    /**
     * 测试博客详情页显示是否符合预期
     * @throws IOException
     */
    @Test
    void detailPageLoadRight() throws IOException {
        driver.findElement(By.cssSelector("#title"));
        driver.findElement(By.cssSelector("#date"));
        getScreenShotAs(getClass().getName());
    }
}

3.7 创建测试套件

创建 RunSuite 类, 该类是用来指定哪些测试类需要运行, 以及运行顺序的.

@Suite
@SelectClasses({BlogRegTest.class,BlogLoginTest.class,
MyBlogListTest.class,EveryBlogListTest.class,
BlogEditTest.class,BlogDetailTest.class,DriverQuitTest.class})
public class RunSuite {
}

运行测试套件, 程序执行结果 : 

 屏幕截图结果 : 

4. 常见面试题

【面试题】你的自动化项目是如何实现的, 有什么亮点 ?

如何实现的 >>

1. 大前提, 先让面试官知道我们使用的是什么技术实现的, 实现的是什么测试等等.

   我在写自动化测试的时候, 先是根据自己的项目以脑图的方式设计了一个 UI 自动化测试用例, 然后根据这个测试用例使用了 selenium4 自动化测试工具和 JUnit5 单元测试框架结合实现的 web 自动化测试.  

2. 在实现的时候, 我对自己的代码进行了模块划分, 主要有两个包, 一个包下的类是工具类, 主要是用来创建驱动对象的, 避免在自动化测试的时候, 每次都要创建驱动对象. 另一个 Tests 包主要就是放测试用例的, 一个页面一个测试类, 然后通过测试套件把测试类全部都加进去, 这就是我的自动化项目的实现过程.

项目亮点 >>

1) 使用了 JUnit5 中提供的注解, 避免生成过多的对象, 造成资源和时间的浪费, 提高了自动化的执行效率.

2) 只创建了一次驱动对象, 避免每个用例重复创建驱动对象造成时间和资源的浪费.

3) 用例使用了参数化 : 保持用例的简洁, 提高代码的可读性.

4) 使用了测试套件, 降低了测试人员的工作量, 通过套件既可以指定哪些测试类需要运行, 也可以一次执行所有要运行的测试用例.

5) 使用了等待, 提高了自动化运行效率, 提高了自动化的稳定性, 比如当页面发生跳转的时候, 或者页面还没来得及渲染的时候, 获取页面元素, 就会出现元素找不到的异常, 而这可能不是一个  bug, 所以需要加上隐式等待, 必要的时候可以搭配强制等待.

6) 使用了屏幕截图, 方便问题的追溯以及问题的解决.


本篇文章就到这里了, 谢谢观看!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值