目录

自动化测试

自动化测试

1.项目背景

  • 项目名称:博客系统
  • 核心功能: 登录 / 注册 / 发布博客 / 查看博客 / 删除博客 / 退出登录 / 
  • 开发环境: JDK1.8、Windows11
  • 技术栈: java、MySQL、Spring Cloud、MyBatis-Plus、RedisRabbitMQ
  • 整体架构:前端使用 HTML+CSS+JS 构建页面整体布局,后端采用分层结构,分为 Servlet 层,Service 层,Dao层的设计,以达到在设计上的高内聚低耦合
  • 项目描述:登陆时服务器通过login()方法来判断用户名和密码是否正确,匹配成功即可登录并将用户信息写入session 中;在前端页面中可以写博客、查看博客、修改博客以及退出登录等操作。

2.项目功能介绍

登录界面

https://i-blog.csdnimg.cn/direct/4cb5b5bcd5c2492bab522feff15ec4b9.png

注册界面

https://i-blog.csdnimg.cn/direct/2ade7cc7cb3c46c28565eb6f425e3c31.png

博客界面

https://i-blog.csdnimg.cn/direct/0815c4a18a5d4a73bd1feb379d6ea526.png

发布博客界面

https://i-blog.csdnimg.cn/direct/f7fad9835e7f4994a746c50086ac1870.png

博客详情界面

https://i-blog.csdnimg.cn/direct/390a55e759c845aba8a86be39a9c9079.png

3.测试功能

脑图

https://i-blog.csdnimg.cn/direct/3d2689934f5943a0bc84b0f3753a81e1.png

代码编写(自动化测试)

添加相关依赖pom.xml

<dependencies>
        <!--        添加selenium依赖-->
        <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>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>5.5.3</version>
        </dependency>
    </dependencies>

新建包并在包下创建测试类以及公共类

1)公共类AutoTestUtils

public class Utils {
    public static WebDriver driver = null;
    public static String DetailUrl = "http://127.0.0.1:80/blog_detail.html?blogId=6";

    public WebDriverWait wait = null;

    public Utils(String url)
    {
        //调用driver对象
        driver = createDriver();
        wait = new WebDriverWait(driver,Duration.ofSeconds(2));
        driver.get(url);
    }

    /**
     * 获取驱动对象
     * @return
     */
    public static WebDriver createDriver()
    {
        if(driver == null)
        {
            //下载驱动
            WebDriverManager.chromedriver().setup();
            ChromeOptions options = new ChromeOptions();
            //允许访问所有的链接
            options.addArguments("--remote-allow-origins=*");

            driver = new ChromeDriver(options);

            //隐式等待
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
        }
        return driver;
    }

    /**
     * 屏幕截图
     */
    public void ScreenShot(String str) throws IOException {
        //屏幕截图文件目录
        //   ./src/test/java/images/
        //                         /2025-03-13/
        //                                    /test01_19372010.png
        SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sim2 = new SimpleDateFormat("HHmmssSS");

        String dirTime = sim1.format(System.currentTimeMillis());
        String fileTime = sim2.format(System.currentTimeMillis());

        //./src/test/java/images/2025-03-13/test01_19372010.png
        String filename = "./src/test/java/images/"+ dirTime + "/" + str + "_" + fileTime + ".png";

        File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
        FileUtils.copyFile(srcFile,new File(filename));
    }
}

2)登录页面测试Login

public class LoginPage extends Utils {
    public static String url = "http://127.0.0.1:80";

    public LoginPage() {
        super(url);
    }

    /**
     * 检查页面是否可以正常打开
     */
    public void checkPageRight() {
        //检查菜单是否加载成功
        driver.findElement(By.cssSelector("body > div.container-login > div > div:nth-child(5) > span > a"));

        //检查登录框是否加载成功
        driver.findElement(By.cssSelector("#username"));
        driver.findElement(By.cssSelector("#password"));
        driver.findElement(By.cssSelector("#submit"));
        System.out.println("登录框加载成功");
    }

    /**
     * 异常登录
     */
    public void LoginFail() {
        driver.findElement(By.cssSelector("#username")).sendKeys(Constant.username);
        driver.findElement(By.cssSelector("#password")).sendKeys(Constant.Expected_password);
        driver.findElement(By.cssSelector("#submit")).click();

        //处理错误的弹窗——警告弹窗
        wait.until(ExpectedConditions.alertIsPresent());

        Alert alert = driver.switchTo().alert();
        String actualMsg = alert.getText();
        alert.accept();

        String expectMsg = "密码错误";
        assert actualMsg.equals(expectMsg);
        System.out.println("异常登录测试通过,弹窗信息验证正确");
    }

    /**
     * 正常登录
     */
    public void LoginSuc() {
        //先清空
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();

        driver.findElement(By.cssSelector("#username")).sendKeys(Constant.username);
        driver.findElement(By.cssSelector("#password")).sendKeys(Constant.password);
        driver.findElement(By.cssSelector("#submit")).click();

        //登录成功会进入到列表页--注销菜单
        driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
        System.out.println("正常登录测试通过,成功进入列表页");
    }

}

3)注册页面测试RegisterPage

public class RegisterPage extends Utils {
    public static String url = "http://127.0.0.1:80/blog_register.html";

    public RegisterPage() {
        super(url);
    }

    /**
     * 检查页面是否可以正常打开
     */
    public void checkPageRight() {
        //检查菜单是否加载成功
        driver.findElement(By.cssSelector("body > div.container-register > div > div:nth-child(7) > span > a"));

        //检查登录框是否加载成功
        driver.findElement(By.cssSelector("#username"));
        driver.findElement(By.cssSelector("#password"));
        driver.findElement(By.cssSelector("#githubUrl"));
        driver.findElement(By.cssSelector("#email"));
        driver.findElement(By.cssSelector("#submit"));
        System.out.println("注册框加载成功");
    }

    /**
     * 异常登录
     */
    public void RegisterPageFail() {
        driver.findElement(By.cssSelector("#username")).sendKeys(Constant.username);
        driver.findElement(By.cssSelector("#password")).sendKeys(Constant.password);
        driver.findElement(By.cssSelector("#githubUrl")).sendKeys(Constant.githubUrl);
        driver.findElement(By.cssSelector("#email")).sendKeys(Constant.email);
        driver.findElement(By.cssSelector("#submit")).click();

        //处理错误的弹窗——警告弹窗
        wait.until(ExpectedConditions.alertIsPresent());

        Alert alert = driver.switchTo().alert();
        String actualMsg = alert.getText();
        alert.accept();

        String expectMsg = "用户已存在";
        assert actualMsg.equals(expectMsg);
    }

    /**
     * 正常登录
     */
    public void RegisterPageSuc() {
        //先清空
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#githubUrl")).clear();
        driver.findElement(By.cssSelector("#email")).clear();

        driver.findElement(By.cssSelector("#username")).sendKeys(Constant.Success_username);
        driver.findElement(By.cssSelector("#password")).sendKeys(Constant.password);
        driver.findElement(By.cssSelector("#githubUrl")).sendKeys(Constant.githubUrl);
        driver.findElement(By.cssSelector("#email")).sendKeys(Constant.email);
        driver.findElement(By.cssSelector("#submit")).click();

        //注册成功会进入注册成功页
        driver.findElement(By.cssSelector("body > div.container-register > div > a"));
        driver.findElement(By.cssSelector("body > div.container-register > div > a")).click();

        // 等待一段时间,让日志有时间记录
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 检查日志中是否有邮件发送成功的记录
        boolean isEmailSent = checkEmailSentInLog(Constant.email);
        assert isEmailSent;
        System.out.println("邮件发送成功验证通过");
    }

    private boolean checkEmailSentInLog(String email) {
        String logFilePath = "D:\\programming\\Gitwarehouse\\back-end\\spring-cloud-blog\\blog.log"; // 替换为实际的日志文件路径
        try (BufferedReader br = new BufferedReader(new FileReader(logFilePath))) {
            String line;
            while ((line = br.readLine())!= null) {
                if (line.contains("邮件发送成功,收件人: " + email)) {
                    return true;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
}

4)详情页面测试DetailPage

public class DetailPage extends Utils {
    //注意:这里的url必须是有效的博客ID,若博客ID失效了将会导致当前类下面的所有用例执行失败
    public static String url = DetailUrl;

    public DetailPage() {
        super(url);
    }

    public void checkBlogDetail() {
        System.out.println("开始检查博客详情");
        //标题
        String titleText = driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.title")).getText();
        //时间
        String timeText = driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.date")).getText();

        String contentText = driver.findElement(By.xpath("//*[@id=\"detail\"]")).getText();
        //编辑
        driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.operating > button:nth-child(1)"));
        //删除按钮
        driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.operating > button:nth-child(2)"));

        assert !titleText.isEmpty();
        assert !timeText.isEmpty();
        assert !contentText.isEmpty();
        System.out.println("博客详情页编辑功能测试通过,成功回到博客列表页");
    }

    /**
     * 博客详情页——编辑功能
     */
    public void checkDeailEdit() {
        System.out.println("开始测试博客详情页删除取消功能");
        //通过编辑按钮进入到编辑页面
        driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.operating > button:nth-child(1)")).click();

        //检查跳转是否正确url 更新文章
        String updateUrl = driver.getCurrentUrl();
        String updateText = driver.findElement(By.cssSelector("#submit")).getAttribute("value");

        assert updateUrl.contains("update");
        assert updateText.equals("更新文章");

        //检查点击更新按钮继续回到博客详情页
        driver.findElement(By.cssSelector("#submit")).click();

        // 等待弹窗出现并处理
        wait.until(ExpectedConditions.alertIsPresent());
        Alert alert = driver.switchTo().alert();
        alert.accept();

        wait.until(ExpectedConditions.urlContains("blog_list"));
        System.out.println("博客详情页删除取消功能测试通过,页面未跳转");
    }

    /**
     * 博客详情页——删除功能
     */
    public void checkDeailDel_dismiss() {
        System.out.println("开始测试博客详情页删除确认功能");
        //编辑之后回到的是博客列表页,要手动将页面跳转到博客详情页
        driver.get(url);
        driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.operating > button:nth-child(2)")).click();

        //出现弹窗--等待
        wait.until(ExpectedConditions.alertIsPresent());
        //处理弹窗
        Alert alert = driver.switchTo().alert();
        //取消
        alert.dismiss();

        assert (driver.getCurrentUrl()).equals(url);
    }

    public void checkDeailDel_accept() {
        //编辑之后回到的是博客列表页,要手动将页面跳转到博客详情页
        driver.get(url);
        driver.findElement(By.cssSelector("body > div.my-container > div.right > div > div.operating > button:nth-child(2)")).click();

        //出现弹窗--等待
        wait.until(ExpectedConditions.alertIsPresent());
        //处理弹窗
        Alert alert = driver.switchTo().alert();
        //确定是否删除
        alert.accept();
        //再次等待出现弹窗
        wait.until(ExpectedConditions.alertIsPresent());
        //确定--跳转到博客列表页
        alert.accept();
        wait.until(ExpectedConditions.urlContains("blog_list"));
        System.out.println("博客详情页删除确认功能测试通过,成功跳转到博客列表页");
    }

}

5)编辑页面测试EditPage

public class EditPage extends Utils {
    public static String url = "http://127.0.0.1:80/blog_edit.html";

    public EditPage() {
        super(url);
    }

    /**
     * 不写标题
     * 不写内容
     * 都不写
     */
    //不写标题
    public void EditPageFail_noTitle() {
        driver.findElement(By.cssSelector("#submit")).click();
        //处理弹窗
        wait.until(ExpectedConditions.alertIsPresent());
        //切换弹窗
        Alert alert = driver.switchTo().alert();
        //点击确定——停留在当前页面
        alert.accept();
    }

    public void EditPageFail_noContent() throws InterruptedException {
        System.out.println("开始不写标题的编辑测试");
        //写标题
        driver.findElement(By.cssSelector("#title")).sendKeys("自动化测试演示标题fail");

        //清空内容
        WebElement ele = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span"));

        Actions action = new Actions(driver);

        //方案一:清除内容——保留了##
//        action.doubleClick(ele)
//                .sendKeys(Keys.DELETE)
//                .sendKeys(Keys.DELETE)
//                .sendKeys(Keys.DELETE)
//                .perform();

        //方案二:清空内容——全部删除
        Thread.sleep(2000);
        action.keyDown(Keys.ALT)
                .sendKeys(ele, Keys.ARROW_RIGHT) //把鼠标放到最右边
                .keyUp(Keys.ALT)  //通过ctrl+shift+↑选中全部文本
                .keyDown(Keys.SHIFT)
                .sendKeys(Keys.ARROW_UP)
                .sendKeys(Keys.DELETE)//删除选中的本操作
                .perform();

        //还没有执行完删除就提交了---必须要加上强制等待
        Thread.sleep(1000);
        driver.findElement(By.cssSelector("#submit")).click();
        //处理弹窗
        wait.until(ExpectedConditions.alertIsPresent());
        //切换弹窗
        Alert alert = driver.switchTo().alert();
        //点击确定——停留在当前页面
        alert.accept();
        driver.findElement(By.cssSelector("#title")).clear();
        System.out.println("不写内容的编辑测试通过,成功处理弹窗");
    }

    /**
     * 成功提交博客
     */
    public void EditPageSuc() throws InterruptedException {
        System.out.println("开始成功提交博客测试");
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
        driver.navigate().refresh();
        //写标题+随机数
        String title = "自动化测试演示标题suc";

        driver.findElement(By.cssSelector("#title")).sendKeys(title);
        //本身就是有内容
        driver.findElement(By.cssSelector("#submit")).click();

        //处理弹窗
        wait.until(ExpectedConditions.alertIsPresent());
        //切换弹窗
        Alert alert = driver.switchTo().alert();
        alert.accept();

        //回到列表页
        //先跳转最后一页
        driver.findElement(By.cssSelector("#pageContainer > li:nth-child(9) > a")).click();
        //获取所有的文章,根据最后一篇文章来校验标题是否为我们刚创建的文章
        List<WebElement> eles = driver.findElements(By.cssSelector("body > div.my-container > div.right > div"));
        //手动构建最新博客对应的选择器元素
        String focus = "body > div.my-container > div.right > div:nth-child(" + eles.size() + ") > div.title";
        String actualTitle = driver.findElement(By.cssSelector(focus)).getText();

        assert actualTitle.equals(title);

        System.out.println("成功提交博客测试通过,博客标题验证正确");
        //driver.quit();
    }
}

测试结果

https://i-blog.csdnimg.cn/direct/9a0447a9ffa44c4a8d47d4045c7dda7f.png

4.小结

1)一定要关注测试用例的执行顺序问题

2)对于页面的检查一定要到位,如检查元素是否存在确保页面的正确性

3)注意多参数测试的页面导航问题

4)驱动关闭的位置要注意,只有最后一个用例结束之后才进行关闭。

5)注意屏幕截图保存的方式:动态时间戳并进行时间格式化,然后期望按照某种维度(天、周)以文件夹的方式进行保存。

6)获取元素的时候建议获取固定的元素,如时间、标题等;内容不建议获取,因为是动态的。然后元素的路径是不可变的,所以可以使用xpath来进行元素的定位。

7)可以适当关注用例执行时间,如果时间过长就需要考虑是我们自己写的测试用例的问题还是程序真的有性能问题呢

8)测试用例并不是越多越好

9)可以使用无头模式来创建驱动