[Project Test Report] Blog System + Online Chat Room


1. Project introduction

This project is a small Web project, including two main modules of blog system and online chat room. Among them, the blog system is mainly used for publishing, managing and browsing blog articles, etc.; while the online chat room provides real-time two-way communication function, so that authors who follow each other can communicate in real time.

1.1 Core Technology

Spring Boot、Spring MVC、MyBatis、Java 8、MySQL、Lombok、WebSocket、Redis、Git、HTML、CSS、Javascript、JQuery 等

1.2 Core Functions

Blog system module:

  1. User registration, login, logout.
  2. Logged-in users can add, save drafts, publish regularly, modify, delete their own blogs, modify their personal information, and also view blogs published by other authors, search blogs by title, blog homepages of other authors, and in specific blogs View below, post comments and delete your own comments, follow other authors, view the authors and fans you follow, and initiate online chat with authors who follow each other.
  3. Users who are not logged in can register, log in, view blogs published by all authors, and view comments under specific blogs.

Live chat module (for logged in):

  1. View a list of conversations that have established conversations (follow each other) in the chat room.
  2. View historical messages with specific users.
  3. Communicate with online users in real time.

1.3 Technical highlights

  1. A custom salt encryption algorithm is implemented for the user's password, which ensures the security of user information to a certain extent.
  2. Using the Hutool tool, the graphic verification code verification when logging in is realized, which increases the security of the system.
  3. After logging in, use Redis to implement distributed storage of HttpSession, which improves the performance of the program to a certain extent.
  4. View, post comments and delete your own comments under specific blogs.
  5. Realized saving drafts of articles, timing publishing (thread pool optimization), etc.
  6. Realized the function of users paying attention to each other, viewing their own follow list and fan list.
  7. User Personal Center: Use MultipartFile to upload avatars, set nicknames, Gitee addresses, etc.
  8. Use unified exception handling.
  9. Use ResponseBodyAdvice to realize unified data format return.
  10. Use HandlerInterceptor to implement the interceptor for unified login.
  11. If the wrong password is entered more than three times, the user will be frozen for a period of time (thread pool optimization).

1.4 Front-end page design

The front-end page of the whole system is divided into pages:

  • Login page (any user)
  • Registration page (any user)
  • Blog Plaza Page (Any User)
  • Search results page (any user)
  • Personal blog page (currently logged in user)
  • Personal center page (currently logged in user)
  • My Follow/Fan page (currently logged in user)
  • Chat room page (currently logged in user)
  • Blog page (currently logged in user)
  • My Drafts Page (currently logged in user)
  • Modify blog page (currently logged in user)
  • Blog details page (any user)
  • Others' blog home page (any user)

log in page

  • This page is used for user login, and login requires input of user name, password, and verification code.
  • When any one of them is not input, a window will pop up to remind the input;
  • If the login fails, the corresponding failure reason will be prompted;
  • If the password is wrong more than three times, the user will be frozen for a period of time;
  • When logging in, it will be compared according to the user information in the database, and the entered password will be decrypted by the decryption algorithm in the encryption and decryption algorithm, and then compared;
  • If it is correct, the login is successful, and the Session will be persistently stored in Redis;
  • After successful login, you will be redirected to the personal blog page.

registration page

  • This page is used for registration, including username, password and confirmation password;
  • When registering, the user name is required to be no less than four characters, and the password is no less than six characters, and both are numbers or letters. If the requirements are not met, a pop-up window will prompt;
  • When registering, if the user name already exists in the database, it will prompt "Registration failed! The user already exists".
  • If the password and confirmation password are different, a pop-up window will remind;
  • The password submitted for registration will be encrypted with salt;
  • After successful registration, it will be adjusted to the login page.

Blog Square Page

not logged in:

  • You can view blogs posted by all users when you are not logged in;
  • All blog lists are displayed in pages, divided into home page, previous page, next page and last page, with the current page number displayed in the middle;
  • When entering this page, the home page is displayed by default;
  • If you are already on the home page, click on the home page and the previous page, and a pop-up window will prompt "Currently on the home page!";
  • If it is already on the last page, click on the next page and the last page, and a pop-up window will prompt "It is already the last page!";
  • Each blog will display its title, release time, summary;
  • You can click "View Full Text" to view the content of a specific blog;
  • Fuzzy search can be performed according to the title of the article;
  • You can choose to log in and register according to the navigation bar above.

After login:

  • After logging in, in addition to the functions when not logged in, the navigation bar adds functions that can only be used after logging in;
  • Such as personal blog, personal center, my followers/fans, chat room, my draft, blogging, logout;
  • Clicking any one of them will adjust to the corresponding page.

search results page

The search result page will also display different navigation bar statuses due to login and non-login, but the displayed content is the same. Take login as an example:
Search results:

No results:

  • When searching, it will fuzzily match the titles of articles in the database according to the input content;
  • If it exists, it will be displayed in order from top to bottom according to the release time;
  • Display content includes title, release time, article summary;
  • You can choose the article you want to view, and click "View Full Text" to view the specific article content;
  • If there is no search result, "No search result" will be displayed on the page;
  • You can continue to search for the content you want to search in the search box.

personal blog page

  • The navigation bar of the personal blog page displays the homepage function, click to jump to the corresponding page;
  • The left side of the page displays basic personal information, such as avatar, nickname, my follow/fan button, gitee address, total number of articles, total visits, and number of comments obtained;
  • All personal blogs are displayed on the right side, displayed from top to bottom according to the release time;
  • Each blog shows the title, release time, abstract, view full text, delete, modify buttons;

Personal center page

  • The personal center page contains the navigation bar and the display of personal information;
  • The user's avatar is displayed, click on the avatar to modify it;
  • The username, nickname and Gitee address are displayed;
  • The nickname and Gitee address can be modified.

My Follow/Fan Page

This page is mainly divided into two parts: My Followers and Fans.
My Followers:

My Fans:

  • The right side of the two pages shows your personal information;
  • My follow page shows a list of authors I follow;
  • The fan page shows a list of fans who follow you;
  • Each message in the list shows follower/follower avatar, username, follow button and more;
  • Click the button on the right to unfollow and follow users;
  • Click More, you can start an online chat with authors who follow each other.

chat room page

  • The left side of the page shows your avatar, nickname and a list of existing sessions;
  • Click one in the list to see historical messages and initiate a session;
  • If the other party is online, you can receive the sent message in real time;
  • And you can also receive messages sent by others in real time.

blogging page

  • This page is used to edit the blog, and only includes the title part, the content part, three buttons for timing publishing, posting articles, and saving drafts;
  • When submitting blog content to the backend, neither the title nor the body part can be empty, otherwise a pop-up window will prompt;
  • Regularly published blogs will be automatically published at the specified time;
  • Published articles will be displayed in the blog square and your own blog list;
  • Save the draft and it will be displayed on the draft page.

my draft

With draft:

Without draft:

  • This page shows a list of all drafts;
  • Each record in the list has title, save time, continue writing and delete buttons;
  • Click to continue writing to jump to the blog editing page;
  • Clicking the delete button will delete the corresponding draft article record from the database;
  • When there is no draft in the database, "No draft!" will be displayed on the page.

modify blog page

  • This page is similar to the new blog page;
  • Also the title and body cannot be empty;
  • Drafts can be saved, and articles can also be published.

Blog details page

not logged in:

Has logged:

  • This page displays the specific blog content, with author information on the left and article content on the right;
  • The content of the article shows the title, author, reading volume, release time, and article text;
  • If the content of the article is too long, it will be folded, and after more expansion, you can also click to close it;
  • Below the content of the article, all users' comments on the article are displayed, which can be viewed by both logged-in and non-logged-in users;
  • The difference between not logged in and logged in is that the display of the navigation bar is different, and logged in users can follow the author;
  • Logged in can also post comments and delete their own comments.

Others' blog homepage

Clicking the author's avatar or user name on the blog details page will jump to the author's blog home page. There is also a difference between unlogged and logged in. The difference is basically the same as that of the blog details page. Here is an example of logged in:

  • In addition to the navigation bar, the author's basic information is displayed on the left;
  • Displayed on the right is a list of all articles of the author, arranged from top to bottom according to the time of publication;
  • Each record contains the title of the article, release time, abstract, and the button to view the full text;

2. Functional test

2.1 Login test case

Enter the correct username, password, verification code:


The username/password/verification code is empty:



Chinese or other symbols appear in the username/password:

The username/password is entered incorrectly:


Verification code input error:

wrong password three times in a row:

2.2 Register test case


registration success:

Username/Password/ConfirmPassword are empty:


Username or password contains Chinese or other characters:


The length of the username is less than 4, and the length of the password is less than 6:

Username already exists in database:

password and confirm password are different:

2.3 Blog Plaza test case


Logged in and not logged in:


When on the home page, click Home and Previous:

Click Last and Next when on the last page:


Click the previous or next page when it is not the first or last page


2.4 Add blog test case

Enter title or body is empty:


Scheduled release:




Save Draft:


Publish Article:


2.5 Personal blog page test case


Page information display:

view full text button:


modify button:

Delete button:



2.6 Other user blog home page test cases

Not logged in:

logged in, and the author is me:

Logged in, the author is someone else:

Follow the button related to the function - the author is me:

Follow the function-related buttons - authored by someone else:

2.7 Draft list page test case


Page display:

continue to write:

delete:

2.8 Search function test cases


Able to search for results:

Can't find results:

2.9 My Follow/Fan Page Test Case


Visit My Follow and My Fan page Result Display:

Follow Function Related Button Function Test:


Click the user name to jump to the corresponding user's blog homepage Functional test:

send private message button test:

2.10 Chat room page test case

The page displayed after entering the chat room:

click on any session The page displayed:

Send function test:

2.11 Personal center page test cases


Display the content of the personal center page:

modify the profile picture - upload non-picture files:


Modify avatar - upload image file:

modify nickname - content is empty:

modify nickname - content is not empty:

2.12 Blog detail page test case


Displayed on the login details page:

Not logged in details page display:

2.13 Review Module Test Cases


Content displayed in the comment area without logging in:

Logged in to the comment area display content:

Leave a comment Functional Test:

Delete comment function test:

3. Automated testing

3.1 Singleton driver tool class

Since the browser driver class is used very frequently in the automation program, if the driver object is frequently created and destroyed in each test class, it will bring a lot of resource consumption to the system, so we can define a lazy mode Singleton-driven class, thus avoiding unnecessary system overhead. The main functions of this tool class include obtaining singleton driver objects, obtaining screenshots of web pages, closing the browser, etc. Subsequent test classes can obtain these functions by inheriting this class.

Obtain singleton driver: In this class, a static uninitialized WebDriverobject is defined, as well as a method to obtain this object getWebDriver. If the singleton object has not been created, it will be created when this method is called for the first time. Also, this singleton uses 双重 if 判断 + synchronized + volatilethe thread-safety issue.

Get a screenshot: After the page is loaded, getScreenshotAsget a screenshot of the current page, and save the package name and file name with a time + class name as a picture. In other test classes, only by calling this method at an appropriate time and passing in its class name can the screenshot operation be performed.

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 单例浏览器驱动类
 */
public class WebDriverSingleton {
    
    
    private static volatile WebDriver webDriver;

    private static final Object locker = new Object();

    /**
     * 获取 单例的 WebDriver
     * @return WebDriver
     */
    public static WebDriver getWebDriver() {
    
    
        // 处理多线程并发问题
        if (webDriver == null) {
    
    
            synchronized (locker) {
    
    
                if (webDriver == null) {
    
    
                    // 配置Chrome WebDriver 选项
                    ChromeOptions options = new ChromeOptions();
                    // options.setHeadless(true); // 无头模式

                    // 获取当前屏幕分辨率
                    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
                    int screenWidth = (int) screenSize.getWidth();
                    int screenHeight = (int) screenSize.getHeight();

                    // 设置浏览器窗口大小为屏幕大小
                    options.addArguments("--window-size=" + screenWidth + "," + screenHeight);

                    // 创建 Chrome WebDriver 实例
                    webDriver = new ChromeDriver(options);
                    // 设置等待时间
                    webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
                }
            }
        }
        return webDriver;
    }

    public void getScreenShot(String src) throws IOException {
    
    

        // 获取时间
        List<String> times = getTime();
        // 构建文件路径 + 文件名
        String fileName = "./src/out/" + times.get(0) + "/" + src + "/" + times.get(1) + ".png";

        // 获取屏幕截图
        File srcFile = ((TakesScreenshot) getWebDriver()).getScreenshotAs(OutputType.FILE);
        // 保存图片
        FileUtils.copyFile(srcFile, new File(fileName));
    }

    /**
     * 获取当前时间
     * @return 日期 + 时间
     */
    private List<String> getTime(){
    
    
        // 保存目录名格式:年月日
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd");
        // 保存文件名格式:年月日+具体时间
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");

        // 目录名
        String dirName = sdf1.format(System.currentTimeMillis());
        // 文件名
        String filename = sdf2.format(System.currentTimeMillis());

        List<String> list = new ArrayList<>();
        list.add(dirName);
        list.add(filename);
        return list;
    }

    /**
     * 关闭浏览器
     */
    public static void shutWebDriver(){
    
    
        if(webDriver != null){
    
    
            webDriver.quit();
            webDriver = null;
        }
    }
}

3.2 Registration page automated testing

RegTestThe role of the class is to complete the automated testing of the registration page. This class first inherits WebDriverSingletonthe class and obtains the singleton driver object. In addition, through @TestMethodOrderannotations, the test methods in it are set @Orderto be executed in the order specified by the annotations.

@BeforeAllAnd @AfterAllAnnotation: The @BeforeAllannotated openWebpagemethod is used to open the webpage, which will be executed before all the test methods in this class are executed; the @AfterAllannotated method is used to close the page opened by the current test class, and will be executed after all the test methods are executed executed between.

webpageCompMethod: This method is used to take screenshots between opening pages, and you can judge whether the pages are opened normally through the captured pictures.

regTestMethod: In this method, the user tests the registration function, @ParameterizedTestrealizes parameterization through annotations, and @CsvSourcesets multiple sets of parameters through annotations for testing.

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.IOException;

/**
 * 注册页面自动化测试
 */

// 设置执行顺序按 @Order 注解设置的顺序执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RegTest extends WebDriverSingleton {
    
    

    // 获取单例驱动
    private static final WebDriver webDriver = getWebDriver();

    /**
     * 打开网页
     */
    @BeforeAll
    public static void openWebpage(){
    
    
        webDriver.get("http://8.130.52.26:8081/reg.html");
    }

    /**
     * 验证网页是否正常打开
     */
    @Test
    @Order(1)
    public void WebpageComp() throws IOException {
    
    
        webDriver.findElement(By.cssSelector("#username"));
        webDriver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(3) > span"));
        webDriver.findElement(By.xpath("//*[@id=\"submit\"]"));

        // 获取屏幕截图,参数为当前类名
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 验证注册功能
     */
    @ParameterizedTest
    @CsvSource({
    
    "zhangsan, 123456, 123457", "李四, 123456, 123456", "test, 123456, 123456"})
    @Order(2)
    public void regTest(String username, String password, String confirmPassword) throws IOException, InterruptedException {
    
    
        // 获取元素
        WebElement inputUsername = webDriver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = webDriver.findElement(By.cssSelector("#password1"));
        WebElement inputConfirmPassword = webDriver.findElement(By.cssSelector("#password2"));
        WebElement submit = webDriver.findElement(By.cssSelector("#submit"));

        // 清楚输入框
        inputUsername.clear();
        inputPassword.clear();
        inputConfirmPassword.clear();

        // 输入测试用例
        inputUsername.sendKeys(username);
        inputPassword.sendKeys(password);
        inputConfirmPassword.sendKeys(confirmPassword);

        // 提交
        submit.click();


        // 处理弹窗 alter
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 截图
        getScreenShot(getClass().getSimpleName());
    }
}

3.3 Login page automated testing

LoginTest The class realizes the automated test of the login function, and three test methods are set in the test class, which are to webpageCompverify whether the page is opened normally, abnormalLoginTestabnormal login test, and normalLoginTestnormal login test.

In the abnormal login test, @ParameterizedTestannotations are used to achieve parameterization, and @CsvSourcemultiple sets of parameters are set through annotations. The test objects have verification code errors, usernames appear in Chinese, and passwords are empty.

adminIn the normal login test, in order to facilitate the test, a special user is added to the back-end program , and its verification code codeis also set to adminfacilitate the test.

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 登录页面自动化测试
 */

// 设置执行顺序按 @Order 注解设置的顺序执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginTest extends WebDriverSingleton {
    
    
    // 获取单例驱动
    private static final WebDriver webDriver = getWebDriver();

    /**
     * 打开登录页面
     */
    @BeforeAll
    public static void openWebPage(){
    
    
        webDriver.get("http://8.130.52.26:8081/login.html");
        // 设置等待时间
        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    /**
     * 验证登录页面是否打开成功
     */
    @Test
    @Order(1)
    public void webpageComp() throws IOException, InterruptedException {
    
    
        // 等待验证码加载完毕
        Thread.sleep(1000);
        // 验证页面是否能找到这些元素
        webDriver.findElement(By.cssSelector("#username"));
        webDriver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(3) > span"));
        webDriver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(4) > span"));
        webDriver.findElement(By.xpath("//*[@id=\"submit\"]"));

        // 获取当前页面截图
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 异常登录测试
     * @param username 用户名
     * @param password 密码
     * @param confirmCode 验证码
     */
    @Order(2)
    @ParameterizedTest
    @CsvSource({
    
    "zhangsan, 123457, abcde", "李四, 123456, abcde", "wangwu, '', 123456"})
    public void abnormalLoginTest(String username, String password, String confirmCode) throws InterruptedException, IOException {
    
    
        // 获取元素
        WebElement inputUsername = webDriver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = webDriver.findElement(By.cssSelector("#password"));
        WebElement inputConfirmCode = webDriver.findElement(By.cssSelector("#code"));
        WebElement submit = webDriver.findElement(By.cssSelector("#submit"));
        // 清除用户名、密码、验证码
        inputUsername.clear();
        inputPassword.clear();
        inputConfirmCode.clear();
        // 输入用户名、密码、验证码
        inputUsername.sendKeys(username);
        inputPassword.sendKeys(password);
        inputConfirmCode.sendKeys(confirmCode);
        // 提交
        submit.click();

        // 处理弹窗
        // 等待弹窗加载完毕
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 截图
        getScreenShot(getClass().getSimpleName());
    }


    /**
     * 正常登录成功测试
     * @param username 用户名
     * @param password 密码
     * @param confirmCode 验证码
     * @throws IOException
     * @throws InterruptedException
     */
    @Order(3)
    @ParameterizedTest
    @CsvSource("admin, 123456, admin")
    public void normalLoginTest(String username, String password, String confirmCode) throws IOException, InterruptedException {
    
    
        // 获取元素
        WebElement inputUsername = webDriver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = webDriver.findElement(By.cssSelector("#password"));
        WebElement inputConfirmCode = webDriver.findElement(By.cssSelector("#code"));
        WebElement submit = webDriver.findElement(By.cssSelector("#submit"));
        // 清除用户名、密码、验证码
        inputUsername.clear();
        inputPassword.clear();
        inputConfirmCode.clear();
        // 输入用户名、密码、验证码
        inputUsername.sendKeys(username);
        inputPassword.sendKeys(password);
        inputConfirmCode.sendKeys(confirmCode);

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());

        // 提交
        submit.click();

        // 设置等待时间
        Thread.sleep(1000);

        // 获取截图
        getScreenShot(getClass().getSimpleName());
    }
}

3.4 Automated testing of blog management

CtrlBlogTestThe test class basically realizes the overall process of article operation. The test method process set in this test class is:

新增文章 =》修改文章 =》保存草稿 =》继续编写 =》发布文章 =》删除文章

None of the processes represents a test method, which is executed sequentially to complete the test requirements.

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 博客管理自动化测试
 * 测试内容:
 * 新增文章 =》修改文章 =》保存草稿 =》继续编写 =》发布文章 =》删除文章
 */

// 设置方法执行顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CtrlBlogTest extends WebDriverSingleton {
    
    
    // 获取单例驱动
    private static final WebDriver webDriver = getWebDriver();

    /**
     * 打开个人博客页面
     * 由于登录测试运行过,已经存在 Session,不必再登录
     */
    @BeforeAll
    public static void openWebpage(){
    
    
        webDriver.get("http://8.130.52.26:8081/myblog_list.html");
    }

    /**
     * 检查当前页面是否打开成功
     */
    @Test
    @Order(1)
    public void webpageComp() throws IOException, InterruptedException {
    
    
        // 等待页面加载完成
        Thread.sleep(1000);
        // 验证在当前页面是否能够找到下面的元素
        webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
        webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
        webDriver.findElement(By.xpath("//*[@id=\"author2\"]"));
        webDriver.findElement(By.xpath("/html/body/div[2]/div[1]/div/button[1]"));

        // 截图
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 新增文章
     */
    @Test
    @Order(2)
    public void addBlogTest() throws IOException, InterruptedException {
    
    
        // 找到写博客连接,并点击
        webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(10)")).click();
        // 等待页面加载完毕
        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        // 获取当前页面截图
        getScreenShot(getClass().getSimpleName());

        // 获取标题输入框并输入内容
        webDriver.findElement(By.cssSelector("#title")).sendKeys("Test");

        Thread.sleep(1000);
        // 截取当前页面
        getScreenShot(getClass().getSimpleName());

        // 获取发布按钮,并发布
        webDriver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button:nth-child(4)")).click();

        // 处理弹窗
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.dismiss();

        Thread.sleep(1000);
        // 获取当前页面截图
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 修改文章
     */
    @Test
    @Order(3)
    public void alterBlogTest() throws InterruptedException, IOException {
    
    
        // 获取修改文章按钮并点击
        webDriver.findElement(By.cssSelector("#artlist > div:nth-child(1) > a:nth-child(5)")).click();
        // 等待页面加载
        Thread.sleep(1000);
        // 获取截图
        getScreenShot(getClass().getSimpleName());

        // 获取标题按钮并修改
        webDriver.findElement(By.cssSelector("#title")).clear();
        webDriver.findElement(By.cssSelector("#title")).sendKeys("Test2");

        Thread.sleep(1000);
        // 获取当前页面截图
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 保存草稿
     */
    @Test
    @Order(4)
    public void saveDraftTest() throws InterruptedException, IOException {
    
    
        // 获取保存草稿按钮并点击
        webDriver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button:nth-child(2)")).click();
        // 处理弹窗
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 获取当前页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }


    /**
     * 继续编写
     */
    @Test
    @Order(5)
    public void continueEditBlogTest() throws InterruptedException, IOException {
    
    
        // 找到继续编写按钮并点击
        webDriver.findElement(By.cssSelector("#artlist > div > a:nth-child(4)")).click();
        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());

        // 获取标题按钮并修改
        webDriver.findElement(By.cssSelector("#title")).clear();
        webDriver.findElement(By.cssSelector("#title")).sendKeys("Test3");

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }


    /**
     * 发布文章
     */
    @Test
    @Order(6)
    public void postBlogTest() throws InterruptedException, IOException {
    
    
        // 找到发布按钮并点击
        webDriver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button:nth-child(3)")).click();
        // 处理弹窗
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 删除文章
     */
    @Test
    @Order(7)
    public void delBlogTest() throws InterruptedException, IOException {
    
    
        // 找到删除按钮并点击
        webDriver.findElement(By.cssSelector("#artlist > div:nth-child(1) > a:nth-child(6)")).click();

        // 处理弹窗
        // 确认删除弹窗
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 删除成功弹窗
        Thread.sleep(1000);
        alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());

    }
}

3.5 Automated testing of search function

SearchTest The function of the test class is to test the search function. Three test methods are also set in this test class: webpageCompverify whether the page is opened normally, searchEmptyTestsearch test when the content of the input box is empty, and searchTestsearch test for content in the input box.

In searchTestthe same way, annotations are used @ParameterizedTestto achieve parameterization, and @CsvSourcemultiple sets of parameters are set through annotations, including search keywords that can be found and cannot be found.

One thing worth noting in the search function is that when the search button is clicked, a new tab is created to display the search results. Therefore, in this test method, first obtain the handle of the current tab page through the method WebDriverin , and then obtain all tab page handles through the method of , and loop through the judgment. If the value of the traversed handle is not equal , it is a newly opened tab page , and then switch to this new tab page through , after taking a screenshot of the search results page, close this page, and then switch back to the original tab page.getWindowHandlecurrentHandlegetWindowHandlescurrentHandlewebDriver.switchTo().window(handle)currentHandle

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 搜索功能自动化测试
 */

// 设置测试方法的运行顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SearchTest extends WebDriverSingleton {
    
    
    // 获取单例驱动
    private static final WebDriver webDriver = getWebDriver();

    /**
     * 打开博客页面
     */
    @BeforeAll
    public static void openWebpage() {
    
    
        webDriver.get("http://8.130.52.26:8081/blog_list.html");
        // 等待页面加载完毕
        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    /**
     * 判断页面是否正常打开
     */
    @Test
    @Order(1)
    public void webpageComp() throws IOException {
    
    
        // 如果在当前页面找到以下元素,则测试通过
        webDriver.findElement(By.cssSelector("#pageDiv > button:nth-child(1)"));
        webDriver.findElement(By.cssSelector("#pageDiv > button:nth-child(5)"));
        // 使用断言判断当前页面的标题是否是 “博客列表”
        boolean flag = webDriver.getTitle().equals("博客列表");
        Assertions.assertTrue(flag);

        // 截取当前页面
        getScreenShot(getClass().getSimpleName());
    }


    /**
     * 搜索框中无内容
     */
    @Test
    @Order(2)
    public void searchEmptyTest() throws InterruptedException, IOException {
    
    
        // 获取搜索输入框
        WebElement searchInput = webDriver.findElement(By.cssSelector("#search-input"));
        // 获取搜索按钮
        WebElement button = webDriver.findElement(By.cssSelector("body > div.nav > div > button"));

        // 清除输入框内容
        searchInput.clear();
        // 输入内容
        searchInput.sendKeys("");
        // 点击搜索按钮
        button.click();

        // 处理弹窗
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

    }

    /**
     * 搜索框中输入内容。
     * 实现多参数,每次搜索都会打开新页面,先获得当前页面以及所有页面的句柄,先切换至新页面,截图后关闭该页面,然后再切换回原页面
     * @param searchKey 搜索关键字
     */

    @ParameterizedTest
    @CsvSource({
    
    "Java", "123", "Spring"})
    @Order(3)
    public void searchTest(String searchKey) throws InterruptedException, IOException {
    
    
        // 获取搜索输入框
        WebElement searchInput = webDriver.findElement(By.cssSelector("#search-input"));
        // 获取搜索按钮
        WebElement button = webDriver.findElement(By.cssSelector("body > div.nav > div > button"));

        // 清除输入框内容
        searchInput.clear();
        // 输入内容
        searchInput.sendKeys(searchKey);
        // 点击搜索按钮
        button.click();

        // 获取当前窗口句柄
        String currentHandle = webDriver.getWindowHandle();


        // 获取所有窗口句柄
        Set<String> allHandles = webDriver.getWindowHandles();

        // 遍历窗口句柄,找到新标签页的句柄
        for (String handle : allHandles) {
    
    
            if (!handle.equals(currentHandle)) {
    
    
                webDriver.switchTo().window(handle);
                // 搜索结果截图
                Thread.sleep(1000);
                getScreenShot(getClass().getSimpleName());

                // 关闭这个新窗口
                webDriver.close();

                // 切换会原窗口
                webDriver.switchTo().window(currentHandle);
            }
        }
    }
}

3.6 Automated testing of my follow/fan pages

FollowRelationTestThe function of this test class is to realize the test of my follow/fan page. It mainly implements four test methods: webpageComptest whether the page is opened normally, switchToFollowsTestswitch to the fan page test, switchBackToFollowingTestswitch to the follow page test, and gotoOtherPageTestgo to other user blog homepage tests.

In the function of going to the corresponding user's blog homepage by clicking the user name, a new tab page will also be opened, and the processing method here is the same as that of the above-mentioned search function test.

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

import java.io.IOException;
import java.util.Set;

/**
 * 我的关注和粉丝页面自动化测试
 */
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class FollowRelationTest extends WebDriverSingleton {
    
    

    // 获取单例驱动对象
    private static final WebDriver webDriver = getWebDriver();

    /**
     * 打开页面
     */
    @BeforeAll
    public static void openWebpage() {
    
    
        webDriver.get("http://8.130.52.26:8081/my_relation_following.html");
    }


    /**
     * 验证页面是否正常打开
     */
    @Test
    @Order(1)
    public void webpageComp() throws InterruptedException, IOException {
    
    
        // 如果获取到下面的元素,则说明页面正常打开了
        webDriver.findElement(By.cssSelector("#relationship > div.relation-tab > div.following-tab"));
        webDriver.findElement(By.cssSelector("#relationship > div.relation-tab > div.follows-tab.active"));
        // 判断网页标题
        boolean flag = webDriver.getTitle().equals("我的关注");
        Assertions.assertTrue(flag);

        // 获取截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 切换至粉丝页面测试
     */
    @Test
    @Order(2)
    public void switchToFollowsTest() throws InterruptedException, IOException {
    
    
        // 点击我的粉丝按钮
        webDriver.findElement(By.cssSelector("#relationship > div.relation-tab > div.follows-tab.active")).click();

        // 判断页面标题
        boolean flag = webDriver.getTitle().equals("我的粉丝");
        Assertions.assertTrue(flag);

        // 获取当前页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 切换会我的关注页面测试
     */
    @Test
    @Order(3)
    public void switchBackToFollowingTest() throws InterruptedException, IOException {
    
    
        // 点击我的关注按钮
        webDriver.findElement(By.cssSelector("#relationship > div.relation-tab > div.following-tab.active")).click();
        // 判断页面标题
        boolean flag = webDriver.getTitle().equals("我的关注");
        Assertions.assertTrue(flag);

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 点击一个关注用户的用户名,跳转至其主页测试
     */
    @Test
    @Order(4)
    public void gotoOtherPageTest() throws IOException, InterruptedException {
    
    
        // 点击关注列表中的第一个用户的用户名或昵称
        webDriver.findElement(By.cssSelector("#following-list > div:nth-child(1) > h3")).click();

        // 获取当前页面句柄
        String currentHandle = webDriver.getWindowHandle();
        // 获取所有页面句柄
        Set<String> handles = webDriver.getWindowHandles();

        // 切换至新页面截图,然后关闭该页面并跳转回原页面
        for (String handle : handles) {
    
    
            if (!handle.equals(currentHandle)) {
    
    
                webDriver.switchTo().window(handle);

                // 获取当前页面截图
                Thread.sleep(1000);
                getScreenShot(getClass().getSimpleName());

                // 关闭当前页面
                webDriver.close();
                // 切换会原页面
                webDriver.switchTo().window(currentHandle);
            }
        }
    }
}

3.7 Chat room automated testing

ChatOnlineTestThe role of the test class is to complete the automated testing of the chat room. In this test class, three test methods are implemented: webpageComptest whether the page is opened normally, clickSessionTesttest the switching function of the session list, and postMessageTesttest the sending function of the message.

In postMessageTestthe test method, annotations are used @ParameterizedTestto achieve parameterization, and @CsvSourceannotations are used to send a set of messages.

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.IOException;

/**
 * 在线聊天室自动化测试
 */

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ChatOnlineTest extends WebDriverSingleton {
    
    
    // 获取单例驱动对象
    private static final WebDriver webDriver = getWebDriver();

    /**
     * 打开网页
     */
    @BeforeAll
    public static void openWebpage(){
    
    
        webDriver.get("http://8.130.52.26:8081/private_message.html");
    }

    /**
     * 验证页面是否正常打开
     */
    @Test
    @Order(1)
    public void webpageComp() throws InterruptedException, IOException {
    
    
        // 如果找到下面元素则说明打开页面成功
        String text = webDriver.findElement(By.cssSelector("body > div.chat-container > div > div.right > div.title")).getText();
        Assertions.assertEquals("在线聊天室", text);
        webDriver.findElement(By.cssSelector("#author"));

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 点击会话列表测试
     */
    @Test
    @Order(2)
    public void clickSessionTest() throws InterruptedException, IOException {
    
    
        // 点击会话列表中的第二个会话
        webDriver.findElement(By.cssSelector("#session-list > li:nth-child(2)")).click();
        // 获取并判断会话列表中的用户名与会话标题是否相等
        String sessionUsername = webDriver.findElement(By.cssSelector("#session-list > li:nth-child(2) > h3")).getText();
        String sessionTitle = webDriver.findElement(By.cssSelector("body > div.chat-container > div > div.right > div.title")).getText();
        Assertions.assertEquals(sessionTitle, sessionUsername);

        // 获取截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());

        // 点击会话列表中的第一个会话
        webDriver.findElement(By.cssSelector("#session-list > li:nth-child(1)")).click();

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 发送消息测试
     */
    @ParameterizedTest
    @CsvSource({
    
    "在的!", "怎么了?", "还不睡觉。"})
    @Order(3)
    public void postMessageTest(String message) throws InterruptedException, IOException {
    
    
        // 获取聊天输入框
        WebElement textareaInput = webDriver.findElement(By.cssSelector("body > div.chat-container > div > div.right > textarea"));
        // 输入消息内容
        textareaInput.sendKeys(message);
        // 获取发送按钮并发送
        Thread.sleep(1000);
        webDriver.findElement(By.cssSelector("body > div.chat-container > div > div.right > div.ctrl > button")).click();

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }
}

3.8 Automated testing of blog detail page + comment function

BlogContentAndCommentTestThe function of the test class is to automate the testing of related functions of the blog details page and comment area. Four test methods are implemented in this class: testing whether the page is opened normally, webpageComptesting checkFirstBlogTestthe first blog content in the blog square and the comments of the blog Display area, postCommentTesttest the function of posting comments, and deleteCommentTesttest the function of deleting comments posted by the current user.

In checkFirstBlogTestthe test method, when obtaining a screenshot of the comment area, you first need to use Javascript to slide the details on the right side of the page to the bottom, and then intercept the page content; in the test method, postCommentTestuse @ParameterizedTestannotations to achieve parameterization, and @CsvSourcepass multiple parameters through annotations.

import com.myblog.utils.WebDriverSingleton;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.*;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 博客详情页 + 评论功能自动化测试
 */

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogContentAndCommentTest extends WebDriverSingleton {
    
    
    // 获取单例驱动对象
    private static final WebDriver webDriver = getWebDriver();

    // 打开博客列表页
    @BeforeAll
    public static void openWebpage() {
    
    
        webDriver.get("http://8.130.52.26:8081/blog_list.html");
        // 等待页面加载完毕
        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    /**
     * 测试页面是否打开成功
     */
    @Test
    @Order(1)
    public void webpageComp() throws InterruptedException, IOException {
    
    
        // 如果找到了以下元素,则说明打开页面成功
        webDriver.findElement(By.cssSelector("#userLoginElement > a:nth-child(1)"));
        webDriver.findElement(By.cssSelector("#pageDiv > button:nth-child(1)"));
        // 验证页面标题
        boolean flag = webDriver.getTitle().equals("博客广场");
        Assertions.assertTrue(flag);

        // 获取当前页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 点击查看第一篇博客
     */
    @Test
    @Order(2)
    public void checkFirstBlogTest() throws InterruptedException, IOException {
    
    
        // 点击查看全文
        webDriver.findElement(By.cssSelector("#artlist > div:nth-child(1) > a")).click();

        // 等待页面加载
        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);


        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());

        // 找到右侧内容区域的元素
        WebElement rightContentElement = webDriver.findElement(By.cssSelector(".container-right"));

        // 创建一个 JavascriptExecutor 对象
        JavascriptExecutor jsExecutor = (JavascriptExecutor) webDriver;

        // 使用 JavaScript 将右侧内容滚动到最底部
        jsExecutor.executeScript("arguments[0].scrollTo(0, arguments[0].scrollHeight);", rightContentElement);

        // 获取评论区截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }

    /**
     * 评论区发表评论测试
     */
    @ParameterizedTest
    @CsvSource({
    
    "666", "泰裤辣!"})
    @Order(3)
    public void postCommentTest(String comment) throws InterruptedException, IOException {
    
    
        // 获取评论输入框
        webDriver.findElement(By.cssSelector("#inputText")).sendKeys(comment);

        // 获取发表评论按钮并点击
        webDriver.findElement(By.cssSelector("#userLoginElement2 > div > div > button")).click();

        // 获取截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }


    /**
     * 删除评论自动化测试
     */
    @Test
    @Order(4)
    public void deleteCommentTest() throws InterruptedException, IOException {
    
    
        // 获取删除按钮
        webDriver.findElement(By.cssSelector("#commentList > div:nth-child(4) > div.delete > button")).click();

        // 处理弹窗
        Thread.sleep(1000);
        Alert alert = webDriver.switchTo().alert();
        System.out.println("弹窗内容:" + alert.getText());
        alert.accept();

        // 获取页面截图
        Thread.sleep(1000);
        getScreenShot(getClass().getSimpleName());
    }
}

3.9 Junit Test Suite

RunSuiteis a Junit test suite that groups the above test classes together. When performing automated testing, you only need to run this test suite to run all the automated test code. This class is executed in the order of the test classes by annotating and specifying the order of the test code. This method helps organize and manage multiple test classes, and they can be run at one time to ensure that your application works properly in different aspects @Suite. @SelectClasses.

import com.myblog.test.*;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

/**
 * 测试套件
 */
@Suite
@SelectClasses({
    
    
        RegTest.class, LoginTest.class, CtrlBlogTest.class, SearchTest.class,
        FollowRelationTest.class, ChatOnlineTest.class, BlogContentAndCommentTest.class,
        QuitDriveTest.class})
public class RunSuite {
    
    }

3.10 Summary of automated testing

Test Results:



By running RunSuitethe suite, it was finally found that all the above test methods passed the test, and the screenshots of the corresponding pages were obtained, which is convenient for comparison of the test results.

During the above tests:

  1. By creating a singleton driver object, the browser driver object is only created once, which keeps the code simple and reduces unnecessary system overhead to a certain extent.
  2. Using Junit5 to write and manage test cases has more annotation functions, simpler parameterization methods, more flexible assertions, etc. than Junit4.
  3. In automated testing, some tests use parameterization to make the code cleaner and more readable.
  4. Using implicit waiting, this intelligent waiting method improves the efficiency and stability of automated operations.
  5. Screenshots are used to facilitate comparison with the expected results after the test and to facilitate traceability.

4. Performance test

4.1 LoadRunner tool description

LoadRunner is a performance testing tool for testing the performance and scalability of applications, websites, and services. It consists of three main components, each with its specific function and purpose:

  1. Virtual User Generator (VUGen)

    • VUGen is the first component in LoadRunner for creating virtual user scripts. Virtual user scripts define the actions a user performs on an application, including browsing web pages, filling out forms, performing searches, and more. VUGen provides a recording function that can record user actions on the application and turn them into scripts. Scripts can also be manually written to simulate different user behaviors.
  2. Controller

    • The Controller is the second component of LoadRunner and is used to manage and execute performance tests. You can create test scenarios in Controller that combine multiple dummy user scripts and simulate multiple users accessing the application at the same time. The Controller also allows configuring the number of virtual users, setting the test duration, adjusting the load pattern, and monitoring the performance metrics of the test. Once the test scenario is configured, the test can be started and performance data collected.
  3. Analysis

    • Analysis, the third component of LoadRunner, is used to analyze and visualize performance test results. Once the performance testing is complete, Analysis allows the import and analysis of the collected performance data in order to evaluate the performance of the application. Graphs, reports and trend analysis can be generated to help identify performance bottlenecks and problems. Analysis provides a rich set of tools and options for gaining insight into how your application behaves under different loads.

Together, these three components form the core functionality of LoadRunner, which enables the creation, execution, and analysis of performance tests to evaluate an application's performance and scalability under varying loads. LoadRunner also provides many other features such as monitoring, automation, report generation, etc. to support more complex performance testing needs.

4.2 Record the login test script

  1. Create a test project

Since what needs to be tested now is a web project, when creating a test script, the protocol selected is Web-HTTP/HTML:

  1. After creating the test project, click Record to configure the recording related rules
  2. Click to start recording to record the script.
    In fact, the so-called recording script is to monitor the network traffic on the computer and record the interaction with the application, so that this scene can be simulated for performance testing later. These records include HTTP requests, responses, page views, and other user interactions with the application. Once the recordings are complete, these recordings can be used to generate performance test scripts, which can then be run under different load conditions to assess the performance and stability of the application.

Here, what I record is to log in to this scene. After cutting out unnecessary codes, the recording result is as follows:

Action()
{
    
    
	web_url("login.html", 
		"URL=http://8.130.52.26:8081/login.html", 
		"Resource=0", 
		"Referer=", 
		"Snapshot=t53.inf", 
		"Mode=HTML", 
		EXTRARES, 
		"Url=/login.html", ENDITEM, 
		"Url=/image/d6bfc596-1b3c-41aa-ac57-0c59e8f5fbbe.png", ENDITEM, 
		LAST);


	web_custom_request("getcode", 
		"URL=http://8.130.52.26:8081/user/getcode", 
		"Method=POST", 
		"Resource=0", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/login.html", 
		"Snapshot=t56.inf", 
		"Mode=HTML", 
		"EncType=", 
		EXTRARES, 
		"Url=http://t.wg.360-api.cn/api/helpgame?app=hotrank", "Referer=", ENDITEM, 
		"Url=http://t.wg.360-api.cn/ap/tips/browse?cver=&mid=9202afc53bcc84de173ca76f798bad1c", "Referer=", ENDITEM, 
		"Url=http://t.wg.360-api.cn/ap/tips/guess?sence=browse_ai&mid=9202afc53bcc84de173ca76f798bad1c&cver=9.1.2.1018&gver=", "Referer=", ENDITEM, 
		LAST);


	web_submit_data("login", 
		"Action=http://8.130.52.26:8081/user/login", 
		"Method=POST", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/login.html", 
		"Snapshot=t57.inf", 
		"Mode=HTML", 
		ITEMDATA, 
		"Name=username", "Value=admin", ENDITEM, 
		"Name=password", "Value=123456", ENDITEM, 
		"Name=code", "Value=q43py", ENDITEM, 
		LAST);


	web_url("myblog_list.html", 
		"URL=http://8.130.52.26:8081/myblog_list.html", 
		"Resource=0", 
		"Referer=http://8.130.52.26:8081/login.html", 
		"Snapshot=t61.inf", 
		"Mode=HTML", 
		LAST);


	web_custom_request("getuserinfo", 
		"URL=http://8.130.52.26:8081/user/getuserinfo", 
		"Method=POST", 
		"Resource=0", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/myblog_list.html", 
		"Snapshot=t63.inf", 
		"Mode=HTML", 
		"EncType=", 
		LAST);

	web_custom_request("mylist", 
		"URL=http://8.130.52.26:8081/art/mylist", 
		"Method=POST", 
		"Resource=0", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/myblog_list.html", 
		"Snapshot=t64.inf", 
		"Mode=HTML", 
		"EncType=", 
		LAST);


	web_submit_data("get_total_rcount_and_comment", 
		"Action=http://8.130.52.26:8081/user/get_total_rcount_and_comment", 
		"Method=POST", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/myblog_list.html", 
		"Snapshot=t67.inf", 
		"Mode=HTML", 
		ITEMDATA, 
		"Name=uid", "Value=3", ENDITEM, 
		LAST);

	return 0;
}

4.3 Add transactions, rendezvous points, and parameterization in test scripts

In order to better collect relevant data during performance testing, we add some extra things to the recorded steps:

  • Transactions: The response time of transactions and the number of transactions per second are important indicators for measuring server performance;

  • Assembly point: let all the virtual users run to the assembly point, and then run together. The rendezvous point is inserted to measure the performance of the server under heavy load.

  • Parameterization: Providing parameterization can pass different user data, and can also make the script move more times.

The modified script code below:

Action()
{
    
    

	// 开启关于整个登录的事务	
	lr_start_transaction("login_transaction");

	web_url("login.html", 
		"URL=http://8.130.52.26:8081/login.html", 
		"Resource=0", 
		"Referer=", 
		"Snapshot=t53.inf", 
		"Mode=HTML", 
		EXTRARES, 
		"Url=/login.html", ENDITEM, 
		"Url=/image/d6bfc596-1b3c-41aa-ac57-0c59e8f5fbbe.png", ENDITEM, 
		LAST);


	web_custom_request("getcode", 
		"URL=http://8.130.52.26:8081/user/getcode", 
		"Method=POST", 
		"Resource=0", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/login.html", 
		"Snapshot=t56.inf", 
		"Mode=HTML", 
		"EncType=", 
		EXTRARES, 
		"Url=http://t.wg.360-api.cn/api/helpgame?app=hotrank", "Referer=", ENDITEM, 
		"Url=http://t.wg.360-api.cn/ap/tips/browse?cver=&mid=9202afc53bcc84de173ca76f798bad1c", "Referer=", ENDITEM, 
		"Url=http://t.wg.360-api.cn/ap/tips/guess?sence=browse_ai&mid=9202afc53bcc84de173ca76f798bad1c&cver=9.1.2.1018&gver=", "Referer=", ENDITEM, 
		LAST);

	// 设置登录集合点
	lr_rendezvous("login_rendezvous");

	
	// 开启登录操作事务
	lr_start_transaction("input_transaction");

	web_submit_data("login", 
		"Action=http://8.130.52.26:8081/user/login", 
		"Method=POST", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/login.html", 
		"Snapshot=t57.inf", 
		"Mode=HTML", 
		ITEMDATA, 
		// 用户名和密码参数化
		"Name=username", "Value={username}", ENDITEM, 
		"Name=password", "Value={password}", ENDITEM, 
		"Name=code", "Value=q43py", ENDITEM, 
		LAST);
	
	// 结束登录操作事务
	lr_end_transaction("input_transaction", LR_AUTO);

	// 结束登录事务
	lr_end_transaction("login_transaction", LR_AUTO);


	web_url("myblog_list.html", 
		"URL=http://8.130.52.26:8081/myblog_list.html", 
		"Resource=0", 
		"Referer=http://8.130.52.26:8081/login.html", 
		"Snapshot=t61.inf", 
		"Mode=HTML", 
		LAST);


	web_custom_request("getuserinfo", 
		"URL=http://8.130.52.26:8081/user/getuserinfo", 
		"Method=POST", 
		"Resource=0", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/myblog_list.html", 
		"Snapshot=t63.inf", 
		"Mode=HTML", 
		"EncType=", 
		LAST);

	web_custom_request("mylist", 
		"URL=http://8.130.52.26:8081/art/mylist", 
		"Method=POST", 
		"Resource=0", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/myblog_list.html", 
		"Snapshot=t64.inf", 
		"Mode=HTML", 
		"EncType=", 
		LAST);


	web_submit_data("get_total_rcount_and_comment", 
		"Action=http://8.130.52.26:8081/user/get_total_rcount_and_comment", 
		"Method=POST", 
		"RecContentType=application/json", 
		"Referer=http://8.130.52.26:8081/myblog_list.html", 
		"Snapshot=t67.inf", 
		"Mode=HTML", 
		ITEMDATA, 
		"Name=uid", "Value=3", ENDITEM, 
		LAST);

	return 0;
}

You can find out by running:

4.4 Create a performance test scenario

The tool used to create performance test scenarios is Controller, and the setup steps are as follows:

1. Set the number of posts

2. Initialize the virtual user

3. Set up a virtual user to start running


4. Set the running time

5. Set the end strategy

After setting all the policies, you can find that the interactive progress chart on the right is as shown in the figure below: the

ascending trapezoidal code in the front part of the virtual users starts to run one after another, while the descending trapezoid in the back represents the virtual users exiting one after another. Each interval is about It is 3s, and the smooth line in the middle represents the running time of each virtual user, which is about 5 minutes.

Running scenario:

When all policy settings are complete, switch to Run, then click Start ScenarioRun, and wait for the end:

Scenario running results:

4.5 Generate and analyze test reports

But after this kind of scene is over, the performance test report can be automatically generated through the Analysis tool.

1. General report

2. The number of running virtual users

You can judge which time period the server load is the highest (00:25 ~ 05:20 in the above picture has the highest load) through the displayed number of virtual users.

3. Hits per second line chart


Hits per second represents the number of HTTP requests submitted by users to the web server per second. The higher the click-through rate, the greater the pressure on the server. The click here is not a click of the mouse, and one click may have multiple HTTP requests.

4. Throughput Discount Chart


The trend of the throughput curve is roughly the same as that of the number of clicks, because the request and response will be generated after the click, and the throughput of the server can be judged.

5. Overall transaction diagram

6. The average transaction per second

TPS refers to the number of transactions that the system can handle per second. It is an important indicator to measure the processing power of the system. When the pressure increases, if the TPS curve changes slowly or has a flat trend, it is likely that the server has started to become a bottleneck. If there is no major change in the environment, there will be a maximum transaction processing capacity for the same system, which does not change with the increase or decrease of concurrent users
.

7. Average transaction response time

The average transaction response time reflects the ability of the system to process transactions, and is also an important indicator for measuring system performance.

8. HTTP Responses per Second


The number of HTTP responses per second shows the ability of the system to process tasks and return data, and is an important indicator of system performance.

Guess you like

Origin blog.csdn.net/qq_61635026/article/details/132515990