[Relatório de teste do projeto] Sistema de blog + sala de bate-papo on-line

Diretório de artigos


1. Introdução do projeto

Este projeto é um pequeno projeto Web, incluindo dois módulos principais de sistema de blog e sala de chat online. Entre eles, o sistema de blog é usado principalmente para publicar, gerenciar e navegar em artigos de blog, etc.; enquanto a sala de chat online oferece função de comunicação bidirecional em tempo real, para que os autores que se seguem possam se comunicar em tempo real.

1.1 Tecnologia Central

Spring Boot, Spring MVC, MyBatis, Java 8, MySQL, Lombok, WebSocket, Redis, Git, HTML, CSS, Javascript, JQuery.

1.2 Funções Principais

Módulo do sistema de blog:

  1. Cadastro de usuário, login, logout.
  2. Os usuários logados podem adicionar, salvar rascunhos, publicar regularmente, modificar, excluir seus próprios blogs, modificar suas informações pessoais e também visualizar blogs publicados por outros autores, pesquisar blogs por título, páginas iniciais de blogs de outros autores e em blogs específicos Ver abaixo, poste comentários e exclua seus próprios comentários, siga outros autores, visualize os autores e fãs que você segue e inicie bate-papo on-line com autores que seguem uns aos outros.
  3. Os usuários que não estão logados podem se registrar, fazer login, visualizar blogs publicados por todos os autores e visualizar comentários em blogs específicos.

Módulo de chat ao vivo (para logado):

  1. Veja uma lista de conversas que estabeleceram conversas (se seguem) na sala de chat.
  2. Veja o histórico de mensagens com usuários específicos.
  3. Comunique-se com usuários online em tempo real.

1.3 Destaques técnicos

  1. Um algoritmo de criptografia salt personalizado é implementado para a senha do usuário, o que garante a segurança das informações do usuário até certo ponto.
  2. Utilizando a ferramenta Hutool, é realizada a verificação do código de verificação gráfica no momento do login, o que aumenta a segurança do sistema.
  3. Após o login, use o Redis para implementar o armazenamento distribuído de HttpSession, o que melhora até certo ponto o desempenho do programa.
  4. Visualize, poste comentários e exclua seus próprios comentários em blogs específicos.
  5. Realizei salvamento de rascunhos de artigos, publicação de tempo (otimização do pool de threads), etc.
  6. Percebi a função de os usuários prestarem atenção uns nos outros, visualizando sua própria lista de seguidores e lista de fãs.
  7. Centro Pessoal do Usuário: Use MultipartFile para fazer upload de avatares, definir apelidos, endereços Gitee, etc.
  8. Use tratamento unificado de exceções.
  9. Use ResponseBodyAdvice para obter retorno de formato de dados unificado.
  10. Use HandlerInterceptor para implementar o interceptor para login unificado.
  11. Se a senha errada for digitada mais de três vezes, o usuário ficará congelado por um período de tempo (otimização do pool de threads).

1.4 Design da página inicial

A página inicial de todo o sistema é dividida em páginas:

  • Página de login (qualquer usuário)
  • Página de registro (qualquer usuário)
  • Página do Blog Plaza (qualquer usuário)
  • Página de resultados de pesquisa (qualquer usuário)
  • Página de blog pessoal (usuário conectado no momento)
  • Página central pessoal (usuário conectado no momento)
  • Minha página de seguidores/fãs (usuário conectado no momento)
  • Página da sala de bate-papo (usuário conectado no momento)
  • Página do blog (usuário conectado no momento)
  • Minha página de rascunhos (usuário conectado no momento)
  • Modificar página do blog (usuário conectado no momento)
  • Página de detalhes do blog (qualquer usuário)
  • Página inicial do blog de terceiros (qualquer usuário)

página de login

  • Esta página é usada para login do usuário e o login requer a inserção de nome de usuário, senha e código de verificação.
  • Quando algum deles não for inserido, uma janela aparecerá para lembrar a entrada;
  • Se o login falhar, o motivo da falha correspondente será solicitado;
  • Se a senha estiver errada mais de três vezes, o usuário ficará congelado por um período de tempo;
  • Ao efetuar login, ela será comparada de acordo com as informações do usuário no banco de dados, e a senha inserida será descriptografada pelo algoritmo de descriptografia no algoritmo de criptografia e descriptografia e depois comparada;
  • Se estiver correto, o login foi bem-sucedido e a Sessão será armazenada persistentemente no Redis;
  • Após o login bem-sucedido, você será redirecionado para a página do blog pessoal.

página de registro

  • Esta página é utilizada para cadastro, incluindo nome de usuário, senha e senha de confirmação;
  • Ao se registrar, o nome de usuário deve ter no mínimo quatro caracteres e a senha não deve ter menos que seis caracteres, sendo ambos números ou letras.Se os requisitos não forem atendidos, uma janela pop-up será exibida;
  • Ao se cadastrar, caso o nome do usuário já exista no banco de dados, será exibido "Falha no cadastro! O usuário já existe".
  • Se a senha e a senha de confirmação forem diferentes, uma janela pop-up irá lembrá-la;
  • A senha enviada para cadastro será criptografada com salt;
  • Após o registro bem-sucedido, ele será ajustado para a página de login.

Página quadrada do blog

não logado:

  • Você pode visualizar blogs postados por todos os usuários quando não estiver logado;
  • Todas as listas de blogs são exibidas em páginas, divididas em página inicial, página anterior, próxima página e última página, com o número da página atual exibido no meio;
  • Ao entrar nesta página, a página inicial é exibida por padrão;
  • Se você já estiver na página inicial, clique na página inicial e na página anterior e uma janela pop-up solicitará "Atualmente na página inicial!";
  • Se já estiver na última página, clique na próxima página e na última página, e uma janela pop-up solicitará “Já é a última página!”;
  • Cada blog exibirá seu título, horário de lançamento, resumo;
  • Você pode clicar em “Ver Texto Completo” para visualizar o conteúdo de um blog específico;
  • A busca difusa pode ser realizada de acordo com o título do artigo;
  • Você pode optar por fazer login e registrar-se de acordo com a barra de navegação acima.

Após o login:

  • Após o login, além das funções quando não estiver logado, a barra de navegação adiciona funções que só podem ser utilizadas após o login;
  • Como blog pessoal, centro pessoal, meus seguidores/fãs, sala de bate-papo, meu rascunho, blog, logout;
  • Clicar em qualquer um deles irá ajustar a página correspondente.

página de resultados de pesquisa

A página de resultados da pesquisa também exibirá diferentes status da barra de navegação devido ao login e ao não login, mas o conteúdo exibido é o mesmo. Tome o login como exemplo: Resultados da pesquisa:
Sem

resultados:

  • Ao pesquisar, fará uma correspondência imprecisa dos títulos dos artigos na base de dados de acordo com o conteúdo de entrada;
  • Se existir, será exibido de cima para baixo de acordo com o tempo de lançamento;
  • O conteúdo de exibição inclui título, horário de lançamento, resumo do artigo;
  • Você pode escolher o artigo que deseja visualizar e clicar em “Visualizar texto completo” para visualizar o conteúdo específico do artigo;
  • Se não houver resultado de pesquisa, “Nenhum resultado de pesquisa” será exibido na página;
  • Você pode continuar pesquisando o conteúdo que deseja na caixa de pesquisa.

página do blog pessoal

  • A barra de navegação da página do blog pessoal exibe a função da página inicial, clique para ir para a página correspondente;
  • O lado esquerdo da página exibe informações pessoais básicas, como avatar, apelido, botão seguir/fã, endereço do gitee, número total de artigos, total de visitas e número de comentários obtidos;
  • Todos os blogs pessoais são exibidos no lado direito, exibidos de cima para baixo de acordo com o horário de lançamento;
  • Cada blog mostra o título, horário de lançamento, resumo, visualizar texto completo, excluir, modificar botões;

Página central pessoal

  • A página central pessoal contém a barra de navegação e a exibição de informações pessoais;
  • O avatar do usuário é exibido, clique no avatar para modificá-lo;
  • O nome de usuário, apelido e endereço do Gitee são exibidos;
  • O apelido e o endereço do Gitee podem ser modificados.

Minha página de seguidores/fãs

Esta página está dividida principalmente em duas partes: Meus Seguidores e Fãs.
Meus Seguidores:

Meus Fãs:

  • O lado direito das duas páginas mostra suas informações pessoais;
  • Minha página de acompanhamento mostra uma lista de autores que sigo;
  • A fan page mostra uma lista de fãs que te seguem;
  • Cada mensagem na lista mostra o avatar do seguidor/seguidor, nome de usuário, botão seguir e muito mais;
  • Clique no botão à direita para deixar de seguir e seguir usuários;
  • Clique em Mais, você pode iniciar um bate-papo online com autores que se seguem.

página da sala de bate-papo

  • O lado esquerdo da página mostra seu avatar, apelido e uma lista de sessões existentes;
  • Clique em um na lista para ver o histórico de mensagens e iniciar uma sessão;
  • Se a outra parte estiver online, você poderá receber a mensagem enviada em tempo real;
  • E você também pode receber mensagens enviadas por outras pessoas em tempo real.

página de blog

  • Esta página é usada para editar o blog e inclui apenas a parte do título, a parte do conteúdo e três botões para cronometrar a publicação, postar artigos e salvar rascunhos;
  • Ao enviar o conteúdo do blog para o backend, nem o título nem a parte do corpo podem ficar vazios, caso contrário, uma janela pop-up será exibida;
  • Blogs publicados regularmente serão publicados automaticamente no horário especificado;
  • Os artigos publicados serão exibidos na praça do blog e na sua própria lista de blogs;
  • Salve o rascunho e ele será exibido na página do rascunho.

meu rascunho

Com rascunho:

Sem rascunho:

  • Esta página mostra uma lista de todos os rascunhos;
  • Cada registro da lista possui botões de título, economizar tempo, continuar escrevendo e excluir;
  • Clique para continuar escrevendo para ir para a página de edição do blog;
  • Clicar no botão excluir excluirá o registro do rascunho do artigo correspondente do banco de dados;
  • Quando não houver nenhum rascunho no banco de dados, “No draft!” será exibido na página.

modificar página do blog

  • Esta página é semelhante à nova página do blog;
  • Além disso, o título e o corpo não podem ficar vazios;
  • Rascunhos podem ser salvos e artigos também podem ser publicados.

Página de detalhes do blog

não logado:

Registrado:

  • Esta página exibe o conteúdo específico do blog, com informações do autor à esquerda e o conteúdo do artigo à direita;
  • O conteúdo do artigo mostra título, autor, volume de leitura, horário de publicação e texto do artigo;
  • Se o conteúdo do artigo for muito longo, ele será dobrado, e após mais expansão, você também poderá clicar para fechá-lo;
  • Abaixo do conteúdo do artigo são exibidos todos os comentários dos usuários sobre o artigo, que podem ser visualizados tanto por usuários logados quanto por não logados;
  • A diferença entre não logado e logado é que a exibição da barra de navegação é diferente e os usuários logados podem seguir o autor;
  • O login também pode postar comentários e excluir seus próprios comentários.

Página inicial do blog de outras pessoas

Clicar no avatar ou nome de usuário do autor na página de detalhes do blog irá para a página inicial do blog do autor. Há também uma diferença entre não conectado e conectado. A diferença é basicamente a mesma da página de detalhes do blog. Aqui está um exemplo de logado:

  • Além da barra de navegação, à esquerda são exibidas informações básicas do autor;
  • À direita está exibida uma lista de todos os artigos do autor, organizados de cima para baixo de acordo com a época de publicação;
  • Cada registro contém o título do artigo, horário de publicação, resumo e o botão para visualização do texto completo;

2. Teste funcional

2.1 Caso de teste de login

Digite o nome de usuário, senha e código de verificação corretos:


O nome de usuário/senha/código de verificação está vazio:



Símbolos chineses ou outros aparecem no nome de usuário/senha:

O nome de usuário/senha foi digitado incorretamente:


Erro de entrada do código de verificação:

senha errada três vezes seguidas:

2.2 Registrar caso de teste


Registro bem sucedido:

Nome de usuário/senha/ConfirmPassword estão vazios:


O nome de usuário ou senha contém caracteres chineses ou outros:


O comprimento do nome de usuário é menor que 4 e o comprimento da senha é menor que 6:

O nome de usuário já existe no banco de dados:

a senha e a senha de confirmação são diferentes:

2.3 Caso de teste do Blog Plaza


Logado e não logado:


Quando estiver na página inicial, clique em Home e Anterior:

Clique em Último e Próximo quando estiver na última página:


Clique na página anterior ou na próxima quando não for a primeira ou a última página


2.4 Adicionar caso de teste de blog

Insira o título ou o corpo está vazio:


Lançamento agendado:




Salvar rascunho:


Publicar artigo:


2.5 Caso de teste de página de blog pessoal


Exibição de informações da página:

botão visualizar texto completo:


botão modificar:

Botão Excluir:



2.6 Outros casos de teste da página inicial do blog do usuário

Não logado:

logado e o autor sou eu:

Logado, o autor é outra pessoa:

Siga o botão referente à função - o autor sou eu:

Siga os botões relacionados à função - de autoria de outra pessoa:

2.7 Caso de teste de página de lista de rascunho


Exibição da página:

continue a escrever:

excluir:

2.8 Casos de teste da função de pesquisa


Capaz de pesquisar resultados:

Não consigo encontrar resultados:

2.9 Caso de teste de minha página de seguidores/fãs


Visite My Follow e My Fan page Exibição de resultados:

Teste de função do botão relacionado à função Follow:


Clique no nome do usuário para ir para a página inicial do blog do usuário correspondente. Teste funcional:

teste do botão enviar mensagem privada:

2.10 Caso de teste da página da sala de bate-papo

A página exibida após entrar na sala de chat:

clique em qualquer sessão A página exibida:

Enviar teste de função:

2.11 Casos de teste da página central pessoal


Exiba o conteúdo da página central pessoal:

modifique a imagem do perfil - carregue arquivos que não sejam de imagem:


Modificar avatar - fazer upload do arquivo de imagem:

modificar apelido - o conteúdo está vazio:

modificar apelido - o conteúdo não está vazio:

2.12 Caso de teste da página de detalhes do blog


Exibido na página de detalhes de login:

Exibição da página de detalhes sem login:

2.13 Revise os casos de teste do módulo


Conteúdo exibido na área de comentários sem fazer login:

Efetuado login na área de comentários exibe o conteúdo:

Deixe um comentário Teste Funcional:

Teste de função de exclusão de comentário:

3. Testes automatizados

3.1 Classe de ferramenta de driver Singleton

Como a classe de driver do navegador é usada com muita frequência no programa de automação, se o objeto driver for criado e destruído com frequência em cada classe de teste, isso trará muito consumo de recursos para o sistema, então podemos definir um modo lento orientado por Singleton classe, evitando assim sobrecarga desnecessária do sistema. As principais funções desta classe de ferramenta incluem obter objetos de driver singleton, obter capturas de tela de páginas da web, fechar o navegador, etc. As classes de teste subsequentes podem obter essas funções herdando esta classe.

Obter driver singleton: Nesta classe WebDriveré definido um objeto estático não inicializado, bem como um método para obter este objeto.Se getWebDrivero objeto singleton não tiver sido criado, ele será criado quando este método for chamado pela primeira vez. Além disso, este singleton usa 双重 if 判断 + synchronized + volatileo problema de segurança de thread.

Obtenha uma captura de tela: depois que a página for carregada, getScreenshotAsobtenha uma captura de tela da página atual e salve o nome do pacote e o nome do arquivo com uma hora + nome da classe como uma imagem. Em outras classes de teste, somente chamando esse método no momento apropriado e passando o nome de sua classe a operação de captura de tela pode ser realizada.

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 Teste automatizado da página de registro

RegTestO papel da classe é completar o teste automatizado da página de registro.Esta classe primeiro herda WebDriverSingletona classe e obtém o objeto driver singleton. Além disso, por meio @TestMethodOrderde anotações, os métodos de teste nele contidos são configurados @Orderpara serem executados na ordem especificada pelas anotações.

@BeforeAllE @AfterAllAnotação: O método @BeforeAllanotado openWebpageé usado para abrir a página da web, que será executada antes de todos os métodos de teste nesta classe serem executados; o @AfterAllmétodo anotado é usado para fechar a página aberta pela classe de teste atual e será executado depois de tudo os métodos de teste são executados entre eles.

webpageCompMétodo: Este método é usado para fazer capturas de tela entre as páginas abertas, e você pode avaliar se as páginas são abertas normalmente através das imagens capturadas.

regTestMétodo: Neste método, o usuário testa a função de registro, @ParameterizedTestrealiza a parametrização por meio de anotações e @CsvSourcedefine vários conjuntos de parâmetros por meio de anotações para teste.

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 Teste automatizado da página de login

LoginTest A classe realiza o teste automatizado da função de login, e três métodos de teste são definidos na classe de teste, que verificam webpageCompse a página é aberta normalmente, abnormalLoginTestteste de login anormal e normalLoginTestteste de login normal.

No teste de login anormal, @ParameterizedTestanotações são usadas para obter parametrização e @CsvSourcevários conjuntos de parâmetros são definidos por meio de anotações.Os objetos de teste apresentam erros de código de verificação, os nomes de usuário aparecem em chinês e as senhas estão vazias.

adminNo teste de login normal, para facilitar o teste, um usuário especial é adicionado ao programa back-end , e seu código de verificação codetambém é definido para adminfacilitar o teste.

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 Teste automatizado de gerenciamento de blog

CtrlBlogTestA classe de teste basicamente realiza o processo geral de operação do artigo. O processo do método de teste definido nesta classe de teste é:

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

Nenhum dos processos representa um método de teste, que é executado sequencialmente para completar os requisitos de teste.

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 Teste automatizado da função de pesquisa

SearchTest A função da classe de teste é testar a função de pesquisa. Três métodos de teste também são definidos nesta classe de teste: webpageCompverificar se a página está aberta normalmente, searchEmptyTesttestar a pesquisa quando o conteúdo da caixa de entrada está vazio e searchTesttestar o conteúdo em a caixa de entrada.

Da searchTestmesma forma, anotações são usadas @ParameterizedTestpara obter parametrização e @CsvSourcevários conjuntos de parâmetros são definidos por meio de anotações, incluindo palavras-chave de pesquisa que podem ser encontradas e não podem ser encontradas.

Uma coisa que vale a pena notar na função de pesquisa é que quando o botão de pesquisa é clicado, uma nova guia é criada para exibir os resultados da pesquisa. Portanto, neste método de teste, primeiro obtenha o identificador da página da guia atual por meio do método WebDriverem e , em seguida, obtenha todos os identificadores da página da guia por meio do método de e faça um loop no julgamento. Se o valor do identificador percorrido não for igual , é uma página de guia recém-aberta e, em seguida , mude para esta nova página de guia por meio de , depois de fazer uma captura de tela da página de resultados da pesquisa, feche esta página e volte para a página da guia original.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 Teste automatizado de minhas páginas de seguidores/fãs

FollowRelationTestA função desta classe de teste é realizar o teste da minha página de seguidores/fãs. Ela implementa principalmente quatro métodos de teste: webpageComptestar se a página está aberta normalmente, switchToFollowsTestmudar para o teste da página de fãs, switchBackToFollowingTestmudar para o teste da página seguinte e gotoOtherPageTestir para testes da página inicial do blog de outros usuários.

Na função de acessar a página inicial do blog do usuário correspondente clicando no nome do usuário, uma nova aba também será aberta, e o método de processamento aqui é o mesmo do teste da função de busca mencionado acima.

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 Testes automatizados em salas de bate-papo

ChatOnlineTestA função da classe de teste é concluir o teste automatizado da sala de chat. Nesta classe de teste, três métodos de teste são implementados: webpageComptestar se a página é aberta normalmente, clickSessionTesttestar a função de comutação da lista de sessões e postMessageTesttestar a função de envio da mensagem.

No postMessageTestmétodo de teste, as anotações são usadas @ParameterizedTestpara obter parametrização e @CsvSourceas anotações são usadas para enviar um conjunto de mensagens.

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 Teste automatizado da página de detalhes do blog + função de comentários

BlogContentAndCommentTestA função da classe de teste é automatizar o teste de funções relacionadas à página de detalhes do blog e à área de comentários. Quatro métodos de teste são implementados nesta classe: testar se a página é aberta normalmente, testar o primeiro conteúdo do blog na praça do webpageCompblog checkFirstBlogTeste os comentários da área de exibição do blog, postCommentTesttestar a função de postar comentários e deleteCommentTesttestar a função de excluir comentários postados pelo usuário atual.

No checkFirstBlogTestmétodo de teste, ao obter uma captura de tela da área de comentários, primeiro você precisa usar Javascript para deslizar os detalhes do lado direito da página para a parte inferior e, em seguida, interceptar o conteúdo da página; no método de teste, use postCommentTestanotações @ParameterizedTestpara obter parametrização e @CsvSourcepassar vários parâmetros por meio de anotações.

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 Conjunto de testes junho

RunSuiteé um conjunto de testes Junit que agrupa as classes de teste acima. Ao realizar testes automatizados, você só precisa executar este conjunto de testes para executar todo o código de teste automatizado. Esta classe é executada na ordem das classes de teste, anotando e especificando a ordem do código de teste. Este método ajuda a organizar e gerenciar várias classes de teste, e elas podem ser executadas ao mesmo tempo para garantir que seu aplicativo funcione corretamente em diferentes aspectos. @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 Resumo dos testes automatizados

Resultado dos testes:



Ao executar RunSuiteo pacote, descobriu-se finalmente que todos os métodos de teste acima passaram no teste e foram obtidas capturas de tela das páginas correspondentes, o que é conveniente para comparação dos resultados do teste.

Durante os testes acima:

  1. Ao criar um objeto de driver singleton, o objeto de driver do navegador é criado apenas uma vez, o que mantém o código simples e reduz a sobrecarga desnecessária do sistema até certo ponto.
  2. Usar o Junit5 para escrever e gerenciar casos de teste tem mais funções de anotação, métodos de parametrização mais simples, asserções mais flexíveis, etc. do que o Junit4.
  3. Nos testes automatizados, alguns testes utilizam parametrização para tornar o código mais limpo e legível.
  4. Usando a espera implícita, esse método de espera inteligente melhora a eficiência e a estabilidade das operações automatizadas.
  5. Capturas de tela são usadas para facilitar a comparação com os resultados esperados após o teste e para facilitar a rastreabilidade.

4. Teste de desempenho

4.1 Descrição da ferramenta LoadRunner

LoadRunner é uma ferramenta de teste de desempenho para testar o desempenho e a escalabilidade de aplicativos, sites e serviços. É composto por três componentes principais, cada um com sua função e finalidade específicas:

  1. Gerador de usuário virtual (VUGen) :

    • VUGen é o primeiro componente do LoadRunner para criação de scripts de usuário virtual. Os scripts de usuário virtual definem as ações que um usuário executa em um aplicativo, incluindo navegar em páginas da web, preencher formulários, realizar pesquisas e muito mais. VUGen fornece uma função de gravação que pode registrar as ações do usuário no aplicativo e transformá-las em scripts. Os scripts também podem ser escritos manualmente para simular diferentes comportamentos do usuário.
  2. Controlador :

    • O Controlador é o segundo componente do LoadRunner e é usado para gerenciar e executar testes de desempenho. Você pode criar cenários de teste no Controller que combinam vários scripts de usuário fictícios e simulam vários usuários acessando o aplicativo ao mesmo tempo. O Controlador também permite configurar o número de usuários virtuais, definir a duração do teste, ajustar o padrão de carga e monitorar as métricas de desempenho do teste. Depois que o cenário de teste estiver configurado, o teste poderá ser iniciado e os dados de desempenho coletados.
  3. Análise :

    • Análise, o terceiro componente do LoadRunner, é usado para analisar e visualizar resultados de testes de desempenho. Uma vez concluído o teste de desempenho, o Analysis permite a importação e análise dos dados de desempenho coletados para avaliar o desempenho da aplicação. Gráficos, relatórios e análises de tendências podem ser gerados para ajudar a identificar gargalos e problemas de desempenho. A análise fornece um rico conjunto de ferramentas e opções para obter insights sobre como seu aplicativo se comporta sob diferentes cargas.

Juntos, esses três componentes formam a funcionalidade central do LoadRunner, que permite a criação, execução e análise de testes de desempenho para avaliar o desempenho e a escalabilidade de um aplicativo sob cargas variadas. LoadRunner também oferece muitos outros recursos, como monitoramento, automação, geração de relatórios, etc. para oferecer suporte a necessidades de testes de desempenho mais complexas.

4.2 Grave o script de teste de login

  1. Crie um projeto de teste

Como o que precisa ser testado agora é um projeto web, ao criar um script de teste, o protocolo selecionado é Web-HTTP/HTML:

  1. Após criar o projeto de teste, clique em Gravar para configurar as regras relacionadas à gravação
  2. Clique para iniciar a gravação para gravar o script
    , na verdade o chamado script de gravação serve para monitorar o tráfego da rede no computador e registrar a interação com a aplicação, para que essa cena possa ser simulada para posterior teste de desempenho. Esses registros incluem solicitações HTTP, respostas, visualizações de páginas e outras interações do usuário com o aplicativo. Assim que as gravações forem concluídas, elas poderão ser usadas para gerar scripts de teste de desempenho, que poderão então ser executados sob diferentes condições de carga para avaliar o desempenho e a estabilidade do aplicativo.

Aqui o que eu gravo é logar nessa cena, depois de recortar os códigos desnecessários, o resultado da gravação é o seguinte:

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 Adicionar transações, pontos de encontro e parametrização em scripts de teste

Para melhor coletar dados relevantes durante os testes de desempenho, adicionamos alguns itens extras às etapas registradas:

  • Transações: O tempo de resposta das transações e o número de transações por segundo são indicadores importantes para medir o desempenho do servidor;

  • Ponto de montagem: deixe todos os usuários virtuais correrem para o ponto de montagem e depois correrem juntos. O ponto de encontro é inserido para medir o desempenho do servidor sob carga pesada.

  • Parametrização: Fornecer parametrização pode transmitir diferentes dados do usuário e também fazer o script se mover mais vezes.

O código do script modificado abaixo:

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

Você pode descobrir executando:

4.4 Crie um cenário de teste de desempenho

A ferramenta usada para criar cenários de teste de desempenho é o Controller, e as etapas de configuração são as seguintes:

1. Defina o número de postagens

2. Inicialize o usuário virtual

3. Configure um usuário virtual para começar a executar


4. Defina o tempo de execução

5. Defina a estratégia final

Depois de definir todas as políticas, você verá que o gráfico de progresso interativo à direita é mostrado na figura abaixo: o

código trapezoidal ascendente na parte frontal dos usuários virtuais começa a ser executado um após o outro, enquanto o trapézio descendente no back representa os usuários virtuais saindo um após o outro. Cada intervalo é de cerca de 3s, e a linha suave no meio representa o tempo de execução de cada usuário virtual, que é de cerca de 5 minutos.

Cenário de execução:

Quando todas as configurações de política forem concluídas, mude para Run, clique em Start ScenarioExecutar e aguarde o fim:

Resultados de execução do cenário:

4.5 Gerar e analisar relatórios de teste

Porém, após o término desse tipo de cena, o relatório do teste de desempenho pode ser gerado automaticamente através da ferramenta Análise.

1. Relatório geral

2. O número de usuários virtuais em execução

Você pode avaliar qual período de tempo a carga do servidor é mais alta (00:25 ~ 05:20 na imagem acima tem a carga mais alta) através do número exibido de usuários virtuais.

3. Gráfico de linhas de acessos por segundo


Os acessos por segundo representam o número de solicitações HTTP enviadas pelos usuários ao servidor web por segundo. Quanto maior a taxa de cliques, maior será a pressão sobre o servidor. O clique aqui não é um clique do mouse e um clique pode ter várias solicitações HTTP.

4. Gráfico de desconto no rendimento


A tendência da curva de rendimento é aproximadamente a mesma do número de cliques, porque a solicitação e a resposta serão geradas após o clique, e o rendimento do servidor pode ser avaliado.

5. Diagrama geral da transação

6. A média de transações por segundo

TPS refere-se ao número de transações que o sistema pode processar por segundo. É um indicador importante para medir o poder de processamento do sistema. Quando a pressão aumenta, se a curva TPS muda lentamente ou apresenta uma tendência plana, é provável que o servidor tenha começado a se tornar um gargalo. Caso não haja grandes mudanças no ambiente, haverá uma capacidade máxima de processamento de transações para um mesmo sistema, que não muda com o aumento ou diminuição de usuários simultâneos
.

7. Tempo médio de resposta das transações

O tempo médio de resposta das transações reflete a capacidade do sistema de processar transações e também é um indicador importante para medir o desempenho do sistema.

8. Respostas HTTP por segundo


O número de respostas HTTP por segundo mostra a capacidade do sistema de processar tarefas e retornar dados e é um indicador importante do desempenho do sistema.

Acho que você gosta

Origin blog.csdn.net/qq_61635026/article/details/132515990
Recomendado
Clasificación