2024.5.1【项目测试报告】模拟微信实现网页聊天室

目录

项目介绍

核心功能

额外拓展

核心技术

 项目页面设计

注册页面

登录页面

找回密码页面

网页聊天室页面

个人中心页面

测试计划

 功能测试

 注册页面

登录页面

找回密码页面

个人中心页面

网页聊天室页面

自动化测试

单例驱动

获取屏幕截图

注册页面自动化测试

登录页面自动化测试

找回密码页面自动化测试

个人中心页面自动化测试

聊天室页面自动化测试

退出驱动

测试套件

自动化测试结果总结

性能测试 

UI 性能测试

录制脚本

运行设置

执行结果与分析

创建测试场景

设置执行策略

场景运行结果


项目介绍

  • 此处实现模拟网页版微信,提供了一个用户之间在线交流平台

核心功能

  • 用户注册、登录、注销、找回密码功能
  • 搜索好友、发送好友申请、添加好友功能
  • 利用 WebSocket 实时查看好友的在线情况
  • 利用 WebSocket 消息推送,选择会话实时聊天
  • 利用 WebSocket 实现会话窗口红色数字提示,实时显示几条消息未读

额外拓展

用户个人中心:支持修改密码、修改昵称
手动实现密码加盐算法,提高用户信息安全性
使用 hutool 工具,实现了登录时图片验证码功能,增加了系统的安全性
增加用户多次登录失败锁定账号功能,并使用 Scheduled 注解实现定时解冻账号
使用 MultipartFile 实现上传头像,使用 WebMvcConfigurer 配置自定义资源映射实现头像展示
使用 HandlerInterceptor 实现统一登录拦截器
使用 ExceptionHandler 注解实现统一异常处理
使用 ResponseBodyAdvice 实现了统一数据格式返回

核心技术

  • Spring Boot / Spring MVC / MyBatis
  • HTML / CSS / JavaScript / Jquery
  • WebSocket
  • MySQL

 项目页面设计

注册页面

  • 客户端输入用户名、密码、密保信息(便于找回密码),通过 ajax 请求发送给后端
  • 后端先检测该用户输入的用户名是否已经被注册,未被注册则针对该密码进行加盐
  • 先利用 UUID 生成一个盐值,随后利用 md5 将(盐值+密码)变为最终的加盐密码
  • 约定存储到数据库的最终密码格式为 (32位盐值+$+32位加盐之后的密码)


登录页面

  • 客户端输入用户名、密码、验证码,通过 ajax 请求发送给后端
  • 后端先检测该用户是否已经注册,再检测该用户是否被锁定、用户输入的验证码是否正确,最后进行密码校验
  • 先从数据库中拿到该用户的最终密码,随后按照之前加密的步骤将该用户输入的明文密码和最终密码中的盐值进行加密,对比新生成的加盐密码与数据库中取出来的加盐密码是否一致
  • 密码一致则登录成功,同时清零该用户密码错误次数,并通过 WebSocket 实时通知其好友,将该用户的状态由离线更改为在线状态
  • 密码不一致则登录失败,该用户密码错误次数 +1,密码错误次数累计到 5 次则直接锁定该用户账号


找回密码页面

  • 客户端输入找回账号、真实姓名、身份证号、密码、确认密码,通过 ajax 请求发送给后端
  • 后端先判断该用户名是否存在,再判断密保信息是否对应正确,最后判断新密码与确认密码是否相同
  • 如果相同则针对新密码进行加盐处理,并更新数据库中该用户对应的最终密码


网页聊天室页面

  • 会话页面

  • 当同意他人的好友申请时,将自动创建会话,并向新添加的好友发送初始消息
  • 当你选择某一会话时,当前会话接收到的消息不会有未读消息红色数字提示
  • 仅有未选择的会话,在收到消息时才会出现未读消息红色数字提示
  • 当你点击该会话,未读消息红色数字提示自动清零消失
  • 最后可通过右侧的聊天框针对选定的会话进行实时聊天

  • 好友页面

  • 展示所有已添加的好友及其状态
  • 当某一好友上线时,将通过 WebSocket 实时通知到当前用户,并实时将该好友的状态改为在线
  • 当某一好友下线时,同样通过 WebSocket 实时通知到当前用户,并实时将该好友的状态改为离线

  • 查询好友页面
  • 用户在输入框中输入关键字进行好友搜索
  • 该搜索为模糊查询,搜索结果将展示所有包含该搜索字符的账号
  • 随后找到想添加的好友,并在其输入框中输入申请理由,最后点击发送申请,那位好友即可收到当前用户发送的好友申请
  • 当然此处不能重复发送申请,如果那位好友拒绝了你,则可以重复发送好友申请

  • 添加好友页面
  • 展示所有当前用户收到的好友申请
  • 当前用户点击拒绝,则将删除该项好友申请
  • 当前用户点击同意,则会自动创建与该好友的会话,并发送初始消息


个人中心页面

  • 当前用户点击上传头像,即可弹出文件资源管理器,便可选择头像上传,前端进行文件格式验证,最后实时更新头像
  • 当前用户在昵称栏中输入想要修改的账号名,点击更改即可实时更新账号名
  • 当前用户输入旧密码,再输入新密码和确认密码,旧密码验证通过即可实时更新账号密码

测试计划

 功能测试

  • 编写测试用例


 注册页面

  • 输入异常的账号、密码、确认密码、真实姓名、身份证号

预期结果

  • 对应的弹窗提示

实际结果

  • 输入正常的账号、密码、确认密码、真实姓名、身份证号

预期结果

  • 弹窗提示是否跳转至登录页面

实际结果


登录页面

  • 输入异常的账号、密码、验证码

预期结果

  • 弹窗对应提示

实际结果

  • 输入正常的账号和密码

预期结果

  • 跳转到聊天室页面

实际结果


找回密码页面

  • 异常填写找回账号名、密保信息、密码、确认密码

预期结果

  • 对应的弹窗提示

实际结果

  • 正常填写找回账号名、密保信息、密码、确认密码

预期结果

  • 弹窗提示密码找回成功,是否跳转到登录页

实际结果


个人中心页面

  • 异常修改头像 —— 上传文件大小大于 5MB 的文件

预期结果

  • 对应弹窗提示

实际结果

  • 异常修改头像 —— 上传非 jpg 和 png 格式的文件

预期结果

  • 对应弹窗提示

实际结果

  • 正常修改头像 —— 上传文件大小大于 5MB 的文件

预期结果

  • 上传成功后,头像实时更新

实际结果


网页聊天室页面

  • 正常查询新的朋友 —— 无匹配到的账号

预期结果

  • 显示查找用户不存在

实际结果

  • 正常查询新的朋友 —— 有一个或多个匹配到的账号

预期结果

  • 自己本身 和 已添加的好友无法被搜索到 并展示在搜索结果页面中
  • 匹配到的用户成列来展示

实际结果

  • 发送好友申请

预期结果

  • 对方能接收到该条好友申请

实际结果

  • 重复发送好友申请 —— 未被对方处理该申请前

预期结果

  • 对应弹窗提示

实际结果

  • 同意好友申请

预期结果

  • 删除该项好友申请
  • 好友列表实时更新并显示新添加的好友
  • 会话列表自动创建会话并向新添加的好友发送初始消息

实际结果

自动化测试

单例驱动

  • 自动化测试程序过程中, 会很频繁的使用驱动
  • 如果频繁的创建和销毁驱动,其开销还是比较大的,因此我们可以使用懒汉模式来加载驱动
  • 这样既能保证驱动不会频繁创建(程序运行过程中保持单例),又能减轻程序刚开始启动时的系统开销(只有用到驱动时才去加载它)
  • 当然如果其他类需要用到驱动的话,直接继承该类即可

获取屏幕截图

  • 当我们测试用例出错时,我们需要查看当时网页出现的情况,那么就需要使用屏幕截图来排查问题
  • 我们可以使用 getScreenshotAs 方法来保存屏幕截图,在每个测试用例执行完后进行一次屏幕截图
  • 屏幕截图统一保存到一个路径下,文件名以当时时间进行组织(防止保存屏幕截图出现覆盖情况)
  • 综上我们便可以在 AutoTestUtils 类下加上保存截图的方法,方便其他类调用
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

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 AutoTestUtils {
    private static ChromeDriver driver;

    public static ChromeDriver getDriver() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");

        if(driver == null) {
            driver = new ChromeDriver(options);
            // 创建隐式等待,设置最长等待时间为 10s
            driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        }

        return driver;
    }
    
    /*
    * 获取屏幕截图,将用例执行结果保存下来
    * */
    public void getScreenShot(String str) throws IOException {
        List<String> list = getTime();
        String fileName = "./src/test/java/com/blogWebAutoTest/" + list.get(0) + "/" + str + "_" + list.get(1) + ".png";
        File srcFile = driver.getScreenshotAs(OutputType.FILE);
//        把生成的截图放入指定路径
        FileUtils.copyFile(srcFile,new File(fileName));
    }
    
//    获取当前时间(记录每个屏幕截图是什么时候拍摄的)
    private List<String> getTime() {
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyyMMdd");
//        保存的文件夹名 当天的年月日 + 具体时间
        String fileName = sdf1.format(System.currentTimeMillis());
//        保存的图片名  当天的年月日
        String dirName = sdf2.format(System.currentTimeMillis());
        List<String> list = new ArrayList<>();
        list.add(dirName);
        list.add(fileName);
        return list;
    }
}

注册页面自动化测试

  • 此处我们创建一个 RegTest 类继承 AutoTestUtils 类得到驱动
  • 先测试注册页面是否正常打开,再找几个典型的用例使用参数化注解进行测试注册功能
  • 最后进行相应弹窗处理,进行屏幕截图
package UserTest;

import common.AutoTestUtils;
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;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RegTest extends AutoTestUtils {

//    获取到驱动
    private static WebDriver driver = AutoTestUtils.getDriver();

    /*
    * 打开网页
    * 注册页面一般是从登录页面进入
    * */
    @BeforeAll
    public static void openWeb() {
//      先打开登录页面
        driver.get("http://116.196.82.203:8080/login.html");
//      再点击登录页面中的 注册链接按钮
        driver.findElement(By.cssSelector("body > div.login-container > div > div > div.row2 > a:nth-child(3)")).click();
        driver.manage().window().maximize();
    }

    /*
    * 验证网页正常打开
    * */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
//        用户名输入框
        driver.findElement(By.cssSelector("#username"));
//        密码输入框
        driver.findElement(By.cssSelector("#password"));
//        确认密码输入框
        driver.findElement(By.cssSelector("#confirm_password"));
//        真实姓名输入框
        driver.findElement(By.cssSelector("#realname"));
//        身份证号输入框
        driver.findElement(By.cssSelector("#idcard"));
//        提交按钮
        driver.findElement(By.cssSelector("#submit"));
        getScreenShot(getClass().getName());
    }

    /*
    * 异常测试注册功能
    * 参数一: 尝试注册已注册用户
    * 参数二: 密码与确认密码不一致
    * 参数三: 身份证号格式有误
    * */
    @ParameterizedTest
    @CsvSource({"mastermao,123,123,王小林,430202123405080222","testMan,123,1234,王小林,430202123405080222","zhansan,123,123,王小林,4302021234"})
    @Order(2)
    public void regFunTest(String username,String password,String confirmPassword,String realName,String idCard) throws InterruptedException, IOException {
//        拿到元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
        WebElement inputConfirmPassword = driver.findElement(By.cssSelector("#confirm_password"));
        WebElement inputRealName = driver.findElement(By.cssSelector("#realname"));
        WebElement inputIdCard = driver.findElement(By.cssSelector("#idcard"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//        再清除输入框
        inputUserName.clear();
        inputPassword.clear();
        inputConfirmPassword.clear();
        inputRealName.clear();
        inputIdCard.clear();
//        输入用例
        inputUserName.sendKeys(username);
        inputPassword.sendKeys(password);
        inputConfirmPassword.sendKeys(confirmPassword);
        inputRealName.sendKeys(realName);
        inputIdCard.sendKeys(idCard);
//        提交
        button.click();
//        强制等待弹窗
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
//        期望结果
        String expect = "注册名已注册/密码与确认密码不一致/身份证号格式有误";
        String actual = "注册名已注册/密码与确认密码不一致/身份证号格式有误";
        if(alert != null) {
            alert.accept();
        }else {
            actual = "当前用例执行失败";
        }
        Assertions.assertEquals(expect,actual);
//        屏幕截图
        getScreenShot(getClass().getName());
    }
}

登录页面自动化测试

  • 此处我们创建一个 LoginTest 类继承 AutoTestUtils 类得到驱动
  • 先测试登录页面是否正常打开,再找几个典型的用例对异常、正常登录分别进行测试
  • 最后进行相应弹窗处理,进行屏幕截图
package UserTest;

import common.AutoTestUtils;
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.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.IOException;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginTest extends AutoTestUtils {

//  获取到驱动
    private static ChromeDriver driver = AutoTestUtils.getDriver();

    /*
    * 打开网页
    * @Before 标识在所有测试用例执行之前执行依次
    * */
    @BeforeAll
    public static void openWeb() {
        driver.get("http://116.196.82.203:8080/login.html");
        driver.manage().window().maximize();
    }

    /*
    * 检测登录页面是否能正常打开
    * */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
//        用户名输入框
        driver.findElement(By.cssSelector("#username"));
//        密码输入框
        driver.findElement(By.cssSelector("#password"));
//        验证码输入框
        driver.findElement(By.cssSelector("#captcha"));
//        找回密码链接
        driver.findElement(By.cssSelector("body > div.login-container > div > div > div.row2 > a:nth-child(1)"));
//        注册账号链接
        driver.findElement(By.cssSelector("body > div.login-container > div > div > div.row2 > a:nth-child(3)"));
//        登录按钮
        driver.findElement(By.cssSelector("#submit"));
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 异常测试注册功能
    * 参数一: 尝试登录未注册用户
    * 参数二: 密码输入错误 登录账号
    * 参数三: 验证码输入错误 登陆账号
    * 0000 为万能验证码
    */
    @ParameterizedTest
    @CsvSource({"adm*,123,0000","mastermao,123456,0000","mastermao,123,warn"})
    @Order(2)
    public void loginAbnormalTest (String username,String password, String captcha) throws IOException, InterruptedException {
//        拿到元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
        WebElement inputCaptcha = driver.findElement(By.cssSelector("#captcha"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//      清除用户名、密码框、验证码
        inputUserName.clear();
        inputPassword.clear();
        inputCaptcha.clear();
//        输入账号、密码、验证码
        inputUserName.sendKeys(username);
        inputPassword.sendKeys(password);
        inputCaptcha.sendKeys(captcha);
//        点击登录
        button.click();
//        强制等待弹窗
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
//        期望结果
        String expect = "用户名/密码/验证码错误";
        String actual = "用户名/密码/验证码错误";
        if(alert != null && !alert.getText().equals("该用户未注册!是否要跳转到登录页面?")) {
            alert.accept();
        }else if(alert != null && alert.getText().equals("该用户未注册!是否要跳转到登录页面?")){
            alert.dismiss();
        }else {
            actual = "当前用例执行失败";
        }
        Assertions.assertEquals(expect,actual);
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 正常登录测试
    * */
    @ParameterizedTest
    @CsvSource("mastermao,123,0000")
    @Order(3)
    public void loginNormalTest(String username,String password,String captcha) throws InterruptedException, IOException {
//        拿到元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
        WebElement inputCaptcha = driver.findElement(By.cssSelector("#captcha"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//        清除用户名、密码框、验证码
        inputUserName.clear();
        inputPassword.clear();
        inputCaptcha.clear();
//        输入账号、密码、验证码
        inputUserName.sendKeys(username);
        inputPassword.sendKeys(password);
        inputCaptcha.sendKeys(captcha);
//        点击登录
        button.click();
//        强制等待 加载页面
        Thread.sleep(100);
//        屏幕截图
        getScreenShot(getClass().getName());
//        登陆成功后需返回登录界面
//        点击注销
        driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
//        强制等待弹窗
        Thread.sleep(100);
//        处理弹窗
        driver.switchTo().alert().accept();
//        等待处理完成
        Thread.sleep(100);
//        屏幕截图
        getScreenShot(getClass().getName());
    }
}

找回密码页面自动化测试

  • 此处我们创建一个 FundPasswordTest 类继承 AutoTestUtils 类得到驱动
  • 先测试找回密码页面是否正常打开,再找几个典型的用例使用参数化注解对其进行测试
  • 最后进行相应弹窗处理,进行屏幕截图
package UserTest;

import common.AutoTestUtils;
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.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.IOException;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class FundPasswordTest extends AutoTestUtils {

//    获取驱动
    private static ChromeDriver driver = AutoTestUtils.getDriver();

    /*
    * 打开页面
    * 找回密码页面一般是从登录页面进入
    * */
    @BeforeAll
    public static void openWeb() {
//      先打开登录页面
        driver.get("http://116.196.82.203:8080/login.html");
//      再点击登录页面中的 找回密码链接按钮
        driver.findElement(By.cssSelector("body > div.login-container > div > div > div.row2 > a:nth-child(1)")).click();
        driver.manage().window().maximize();
    }

    /*
    * 验证页面正常打开
    * */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
//        找回账号名输入框
        driver.findElement(By.cssSelector("#username"));
//        真实姓名输入框
        driver.findElement(By.cssSelector("#realname"));
//        身份证号输入框
        driver.findElement(By.cssSelector("#idcard"));
//        新密码输入框
        driver.findElement(By.cssSelector("#password"));
//        确认密码输入框
        driver.findElement(By.cssSelector("#confirm_password"));
//        提交按钮
        driver.findElement(By.cssSelector("#submit"));
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 异常找回密码测试
    * 参数一: 验证找回不存在账号
    * 参数二: 验证密保信息输入错误
    * 参数三: 身份证号正确性校验
    * 参数四: 新密码与确认密码不一致
    * */
    @ParameterizedTest
    @CsvSource({"adm*,王小林,432204422225080732,123,123","xiaowang,王小林,432204422225080739,123,123",
            "xiaowang,王小林,43220,123,123","xiaowang,王小林,432204422225080732,123,1234"})
    @Order(2)
    public void fundPasswordFunExceptionTest(String userName,String realName,String IdCard,
                                             String newPassword,String confirmPassword) throws InterruptedException, IOException {
//      拿到元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputRealName = driver.findElement(By.cssSelector("#realname"));
        WebElement inputIdCard = driver.findElement(By.cssSelector("#idcard"));
        WebElement inputNewPassword = driver.findElement(By.cssSelector("#password"));
        WebElement inputConfirmPassword = driver.findElement(By.cssSelector("#confirm_password"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//      清除找回账号、真实姓名、身份证号、新密码、确认密码
        inputUserName.clear();
        inputRealName.clear();
        inputIdCard.clear();
        inputNewPassword.clear();
        inputConfirmPassword.clear();
//      输入找回账号、真实姓名、身份证号、新密码、确认密码
        inputUserName.sendKeys(userName);
        inputRealName.sendKeys(realName);
        inputIdCard.sendKeys(IdCard);
        inputNewPassword.sendKeys(newPassword);
        inputConfirmPassword.sendKeys(confirmPassword);
//        点击提交
        button.click();
//        强制等待弹窗
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
//        期望结果
        String expect = "找回账号不存在/密保信息错误/身份证号格式有误/密码与确认密码不一致";
        String actual = "找回账号不存在/密保信息错误/身份证号格式有误/密码与确认密码不一致";
        if(alert != null) {
            alert.accept();
        }else {
            actual = "当前用例执行失败";
        }
        Assertions.assertEquals(expect,actual);
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 正常找回密码测试
    * */
    @ParameterizedTest
    @CsvSource("xiaowang,王小林,432204422225080732,123,123")
    @Order(3)
    public void fundPasswordFuNormalTest(String userName,String realName,String IdCard,
                                         String newPassword,String confirmPassword) throws InterruptedException, IOException {
//      拿到元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputRealName = driver.findElement(By.cssSelector("#realname"));
        WebElement inputIdCard = driver.findElement(By.cssSelector("#idcard"));
        WebElement inputNewPassword = driver.findElement(By.cssSelector("#password"));
        WebElement inputConfirmPassword = driver.findElement(By.cssSelector("#confirm_password"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//      清除找回账号、真实姓名、身份证号、新密码、确认密码
        inputUserName.clear();
        inputRealName.clear();
        inputIdCard.clear();
        inputNewPassword.clear();
        inputConfirmPassword.clear();
//      输入找回账号、真实姓名、身份证号、新密码、确认密码
        inputUserName.sendKeys(userName);
        inputRealName.sendKeys(realName);
        inputIdCard.sendKeys(IdCard);
        inputNewPassword.sendKeys(newPassword);
        inputConfirmPassword.sendKeys(confirmPassword);
//      点击提交
        button.click();
//      处理弹窗
        Thread.sleep(100);
        driver.switchTo().alert().accept();
//      屏幕截图
        getScreenShot(getClass().getName());
    }
}

个人中心页面自动化测试

  • 此处我们创建一个 PersonalCenterTest 类继承 AutoTestUtils 类得到驱动
  • 先测试找回密码页面是否正常打开,对修改账号名、修改密码使用典型用例进行测试
  • 最后进行相应弹窗处理,进行屏幕截图
package UserTest;

import common.AutoTestUtils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.IOException;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PersonalCenterTest extends AutoTestUtils {

    private static ChromeDriver driver = AutoTestUtils.getDriver();

    @BeforeAll
    public static void openWeb() throws InterruptedException {
//        1) 单元测试 先登录,再进入个人中心页面
//        拿到元素
        driver.get("http://116.196.82.203:8080/login.html");
        driver.manage().window().maximize();
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
        WebElement inputCaptcha = driver.findElement(By.cssSelector("#captcha"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//        清除用户名、密码框、验证码
        inputUserName.clear();
        inputPassword.clear();
        inputCaptcha.clear();
//        输入账号、密码、验证码
        inputUserName.sendKeys("mastermao");
        inputPassword.sendKeys("123");
        inputCaptcha.sendKeys("0000");
//        点击登录
        button.click();
//        强制等待页面加载
        Thread.sleep(100);
        driver.findElement(By.cssSelector("body > div.nav > a:nth-child(3)")).click();

//        2) 整体测试 此时已经存在 session 会话,直接进入即可
//        driver.get("http://116.196.82.203:8080/personal_center.html");
    }

    /*
    * 验证页面正常打开
    * */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
//        账号名展示
        driver.findElement(By.cssSelector("#username"));
//        头像展示
        driver.findElement(By.cssSelector("#avatarImg"));
//        上传头像按钮
        driver.findElement(By.cssSelector("#changeBtn"));
//        更换昵称输入框
        driver.findElement(By.cssSelector("#newname"));
//        更改昵称按钮
        driver.findElement(By.cssSelector("#smallsubmit"));
//        修改密码 —— 旧密码输入框
        driver.findElement(By.cssSelector("#password2"));
//        修改密码 —— 新密码输入框
        driver.findElement(By.cssSelector("#password3"));
//        修改密码 —— 确认密码输入框
        driver.findElement(By.cssSelector("#password4"));
//        修改密码 —— 提交按钮
        driver.findElement(By.cssSelector("#submit"));
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 异常修改账号名(昵称)测试
    * 参数一: 尝试修改成已存在的账号名
    * */
    @ParameterizedTest
    @ValueSource(strings = {"123","xiaowang"})
    @Order(2)
    public void basicInfoAbnormalTest(String newName) throws InterruptedException, IOException {
//        清除输入框
        WebElement inputNewName  = driver.findElement(By.cssSelector("#newname"));
        inputNewName.clear();
//        填入想要更换的账号名
        inputNewName.sendKeys(newName);
//        点击更改按钮
        driver.findElement(By.cssSelector("#smallsubmit")).click();
//        处理弹窗
        Thread.sleep(100);
//        预期结果
        String text = "新用户名已经被注册,请重新填写!";
//        实际结果
        String alertText = driver.switchTo().alert().getText();
//        断言
        Assertions.assertEquals(text,alertText);
        driver.switchTo().alert().accept();
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 正常修改账号名(昵称)测试
    * 此处修改账号名我需要形成闭环,账号名被修改影响之后的测试
    * */
    @ParameterizedTest
    @ValueSource(strings = {"1234","xiaozhang","&*^%$","小张","mastermao"})
    @Order(3)
    public void basicInfoTest(String newName) throws InterruptedException, IOException {
//        清除输入框
        WebElement inputNewName  = driver.findElement(By.cssSelector("#newname"));
        inputNewName.clear();
//        填入想要更换的账号名
        inputNewName.sendKeys(newName);
//        点击更改按钮
        driver.findElement(By.cssSelector("#smallsubmit")).click();
//        处理弹窗
        Thread.sleep(100);
        driver.switchTo().alert().accept();
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
     * 异常修改密码测试
     * 参数一: 旧密码输入错误(1)
     * 参数二: 旧密码输入错误(2)
     * 参数三: 新密码与确认密码不一致(1)
     * 参数四: 新密码与确认密码不一致(2)
     **/
    @ParameterizedTest
    @CsvSource({"12345678,123456,123456","*#&2*,123456,123456",
            "123,adfa,1551sf8","123,489,123"})
    @Order(4)
    public void updatePasswordAbnormalTest(String oldPassword,String newPassword,String confirmPassword) throws InterruptedException, IOException {
//        修改密码 —— 旧密码输入框
        WebElement inputOldPassword = driver.findElement(By.cssSelector("#password2"));
//        修改密码 —— 新密码输入框
        WebElement inputNewPassword = driver.findElement(By.cssSelector("#password3"));
//        修改密码 —— 确认密码输入框
        WebElement inputConfirmPassword = driver.findElement(By.cssSelector("#password4"));
//        修改密码 —— 提交按钮
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//        清除输入框
        inputOldPassword.clear();
        inputNewPassword.clear();
        inputConfirmPassword.clear();
//        输入参数
        inputOldPassword.sendKeys(oldPassword);
        inputNewPassword.sendKeys(newPassword);
        inputConfirmPassword.sendKeys(confirmPassword);
//        点击提交
        button.click();
//        处理弹窗
        Thread.sleep(100);
        Alert alert = driver.switchTo().alert();
//        期望结果
        String expect = "旧密码错误/密码与确认密码不一致";
        String actual = "旧密码错误/密码与确认密码不一致";
        if(alert != null) {
            alert.accept();
        }else {
            actual = "当前用例执行失败";
        }
        Assertions.assertEquals(expect,actual);
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
    * 正常修改密码测试
    * */
    @ParameterizedTest
    @CsvSource({"123,123456,123456", "123456,123,123"})
    @Order(5)
    public void updatePasswordTest(String oldPassword,String newPassword,String confirmPassword) throws InterruptedException, IOException {
//        修改密码 —— 旧密码输入框
        WebElement inputOldPassword = driver.findElement(By.cssSelector("#password2"));
//        修改密码 —— 新密码输入框
        WebElement inputNewPassword = driver.findElement(By.cssSelector("#password3"));
//        修改密码 —— 确认密码输入框
        WebElement inputConfirmPassword = driver.findElement(By.cssSelector("#password4"));
//        修改密码 —— 提交按钮
        WebElement button = driver.findElement(By.cssSelector("#submit"));
//        清除输入框
        inputOldPassword.clear();
        inputNewPassword.clear();
        inputConfirmPassword.clear();
//        输入参数
        inputOldPassword.sendKeys(oldPassword);
        inputNewPassword.sendKeys(newPassword);
        inputConfirmPassword.sendKeys(confirmPassword);
//        点击提交
        button.click();
//        处理弹窗
        Thread.sleep(100);
//        预期结果
        String text = "恭喜!修改密码成功!";
//        实际结果
        String alertText = driver.switchTo().alert().getText();
//        断言
        Assertions.assertEquals(text,alertText);
        driver.switchTo().alert().accept();
//        屏幕截图
        getScreenShot(getClass().getName());
    }
}

聊天室页面自动化测试

  • 此处我们创建一个 ClientTest 类继承 AutoTestUtils 类得到驱动
  • 先对在线显示以及聊天功能进行测试,最后进行相应的弹窗处理,最后进行屏幕截图
package UserTest;

import common.AutoTestUtils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.IOException;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ClientTest extends AutoTestUtils {
    //    获取驱动
    public static ChromeDriver driver = AutoTestUtils.getDriver();

    @BeforeAll
    public static void openWeb() throws InterruptedException {
        1) 单元测试 先登录,再进入个人中心页面
        拿到元素
//        driver.get("http://116.196.82.203:8080/login.html");
//        driver.manage().window().maximize();
//        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
//        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
//        WebElement inputCaptcha = driver.findElement(By.cssSelector("#captcha"));
//        WebElement button = driver.findElement(By.cssSelector("#submit"));
        清除用户名、密码框、验证码
//        inputUserName.clear();
//        inputPassword.clear();
//        inputCaptcha.clear();
        输入账号、密码、验证码
//        inputUserName.sendKeys("mastermao");
//        inputPassword.sendKeys("123");
//        inputCaptcha.sendKeys("0000");
        点击登录
//        button.click();
//        2)  整体测试 此时已经存在 session 会话,直接进入即可
        driver.get("http://116.196.82.203:8080/client.html");
    }

    /*
     * 测试页面正常打开
     * */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
//        主页个人头像
        driver.findElement(By.cssSelector("#avatarImg"));
//        主页个人账号名
        driver.findElement(By.cssSelector("#username"));
//        搜索输入框
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.search > input[type=text]"));
//        搜索按钮
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.search > button"));
//        会话标签图
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-session"));
//        好友标签图
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-friend"));
//        添加好友标签图
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-add-friend"));
//        聊天输入框
        driver.findElement(By.cssSelector("#message-list > textarea"));
//        发送按钮
        driver.findElement(By.cssSelector("#message-list > div.ctrl > button"));
//        屏幕截图
        getScreenShot(getClass().getName());
    }

    /*
     * 测试聊天功能
     * */
    @ParameterizedTest
    @ValueSource(strings = {"我在测试中很高兴认识你!","我在测试中啦啦啦!","一二三四五六七八九十"})
    @Order(2)
    public void TalkTest(String msg) throws InterruptedException, IOException {
//        点击头像开始聊天
        driver.findElement(By.xpath("//*[@id=\"session-list\"]/li[1]")).click();
//        选择输入框开始聊天
        driver.findElement(By.cssSelector("#message-list > textarea")).sendKeys(msg);
//        点击发送
        driver.findElement(By.cssSelector("#message-list > div.ctrl > button")).click();
        Thread.sleep(100);
//        屏幕截图
        getScreenShot(getClass().getName());
    }
}

退出驱动

  • 所有自动化测试用例执行完后,需要退出浏览器
  • 此处我们创建一个退出驱动类,放到测试套件类的最后一个测试类
package common;

import org.junit.jupiter.api.Test;
import org.openqa.selenium.chrome.ChromeDriver;

public class DriverQuitTest extends AutoTestUtils{

    private static ChromeDriver driver  = AutoTestUtils.getDriver();

    /*
    * 退出驱动
    * */
    @Test
    public void quitWeb() {
        driver.quit();
    }
}

测试套件

  • 此处我们创建一个 RunSuite 类,通过 @Suite 注解标识该类为测试套件类
  • 然后使用 @SelectClasses 注解来声明我们要指定的类
  • 最后通过 RunSuite 类来运行测试用例

优点:

  • 相比于使用函数调用来对测试用例进行测试,当前方式大大减少了开销和时间
  • 可以直接指定类的测试顺序,即在注解 @SelectClasses 参数中的测试顺序为从左向右
package common;

import UserTest.*;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({RegTest.class, FundPasswordTest.class, LoginTest.class, PersonalCenterTest.class, ClientTest.class})
public class RunSuite {
}

自动化测试结果总结

​​​​​​​

  • 使用 Junit5 中提供的注解,避免生成过多对象从而造成资源和时间上的浪费,提高自动化执行的效率
  • 使用单例模式创建驱动,避免每个用例重复创建驱动造成时间和资源的浪费
  • 使用隐藏式等待,提高了自动化运行效率,提高了自动化的稳定性
  • 使用参数化,保持用例的整洁,提高代码的可读性
  • 使用屏幕截图,方便问题的追溯以及解决

性能测试 

  • 此处我将使用 Load Runner 三件套对该项目进行性能测试

UI 性能测试

  • 访问登录页面 ——> 执行登录操作 ——> 进入聊天室页面
录制脚本
  • 此处为了进行性能测试的数据收集,我自主添加了事务、集合点、检查点、参数化
  • 事务:衡量性能的重要指标,通过观察每秒事务通过数来衡量性能
  • 集合点:让所有的虚拟用户执行到集合点时断在集合,满足条件后一起执行下一个步骤
  • 检查点:可以用来检测当前页面的元素是否存在以及存在个数
  • 参数化:通过提供的数据源可以实现多个参数逐个执行
Action()
{
	web_add_cookie("JSESSIONID=8EA4077CAC83B39B832BF2F68CA04C27; DOMAIN=116.196.82.203");

//	开启事务1
	lr_start_transaction("index_trans");
	
//	登录页面的检查点
	web_reg_find("Fail=NotFound",
		"Search=All",
		"SaveCount=",
		"Text=login",
		LAST);
	
//	1、访问登录页面
	web_url("login.html", 
		"URL=http://116.196.82.203:8080/login.html", 
		"Resource=0", 
		"RecContentType=text/html", 
		"Referer=http://116.196.82.203:8080/login.html", 
		"Snapshot=t1.inf", 
		"Mode=HTML", 
		EXTRARES, 
		"Url=/user/captcha?1714547166056", ENDITEM, 
		LAST);
	
//	登录的集合点
	lr_rendezvous("login_rendezvous");
	
//	开启事务2
	lr_start_transaction("login_trans");
	
//	输入登录账号和密码
	web_submit_data("login", 
		"Action=http://116.196.82.203:8080/user/login", 
		"Method=POST", 
		"RecContentType=application/json", 
		"Referer=http://116.196.82.203:8080/login.html", 
		"Snapshot=t2.inf", 
		"Mode=HTML", 
		ITEMDATA, 
		"Name=username", "Value={username}", ENDITEM, 
		"Name=password", "Value=123", ENDITEM, 
		"Name=captcha", "Value=0000", ENDITEM, 
		EXTRARES, 
		"Url=userinfo", "Referer=http://116.196.82.203:8080/client.html", ENDITEM, 
		"Url=../session/session-list", "Referer=http://116.196.82.203:8080/client.html", ENDITEM, 
		"Url=../friend/friend-list", "Referer=http://116.196.82.203:8080/client.html", ENDITEM, 
		"Url=../friend/get-friend-request", "Referer=http://116.196.82.203:8080/client.html", ENDITEM, 
		LAST);
	
//	结束事务2
	lr_end_transaction("login_trans", LR_AUTO);
	
//	结束事务1
	lr_end_transaction("index_trans", LR_AUTO);
	return 0;
}
运行设置
  • 此处我针对 username 进行了参数化,这些用户名对应的密码均为 "123"

  • 因为我参数化设置了三个账号名,所以此处将 Action 脚本执行次数修改为 3

  • 与此同时,我还想让参数值能够直接打印到日志信息中

执行结果与分析

创建测试场景
  • 此处我针对上述脚本创建测试场景,设置 3 个虚拟用户

设置执行策略
  • 此处我们分别设置 虚拟用户初始化、启动、运行时间、退出,这四个方面的执行策略

场景运行结果
  • 此处我直接查看 Analysis 所生成的测试报告
  • 关于事务总结模块,一般不太注重最大值和最小值,除非差距太大,就需考虑其稳定性
  • 主要关注 平均值 和 标准偏差,标准偏差值越大,则越不稳定

  • 虚拟用户运行图表展示
  • 作用 ——> 通过显示的虚拟用户数来判断出哪个时间段服务器负载最大

  • 每秒点击数图表展示
  • 作用 ——> 通过点击率可以判断出某时间段内服务器的负载

  • 吞吐量图表展示
  • 注意对比吞吐量与每秒点击数图标
  • 虽然二者图表相似,但仔细观察吞吐量均基本滞后于每秒点击数
  • 这也主要因为吞吐量表示的是响应返回的资源数量,肯定是先有请求再有返回

  • 事务图表展示

  • 平均事务响应时间图表展示
  • 作用 ——> 可以观察到虚拟用户在性能测试中,每秒在服务器上的命中次数,可以根据命中次数评估虚拟用户生成的负载量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588995.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GPG的使用

这里写自定义目录标题 安装加密程序生成加密密钥怎么备份自己的密钥就可以使用公钥加密邮件信息了 安装加密程序 下载gpg4win&#xff1a; https://www.gpg4win.org/index.html 免费的&#xff0c;如果使用的是苹果电脑&#xff0c;使用https://gpgtools.org/。 如果是linux&a…

【精选文献】JAG|基于时序Sentinel-1 SAR影像小农耕作区烟草空间分布制图

目录 文章简介 01 文章摘要 02 研究背景、目标及创新点 03 研究区域与数据集 04 研究方法 05 研究结果 06 研究讨论 07 研究结论 08 文章引用 文章简介 论文名称&#xff1a;Mapping tobacco planting areas in smallholder farmlands using Phenological-Spatial-Te…

golang判断通道chan是否关闭的2种方式

chan通道在go语言的办法编程中使用频繁&#xff0c;我们可以通过以下2种方式来判断channel通道是否已经关闭&#xff0c;1是使用 for range循环&#xff0c;另外是通过 for循环中if 简短语句的 逗号 ok 模式来判断。 示例代码如下&#xff1a; //方式1 通过for range形式判断…

LNMP部署wordpress

1.环境准备 总体架构介绍 序号类型名称外网地址内网地址软件02负载均衡服务器lb0110.0.0.5192.168.88.5nginx keepalived03负载均衡服务器lb0210.0.0.6192.168.88.6nginx keepalived04web服务器web0110.0.0.7192.168.88.7nginx05web服务器web0210.0.0.8192.168.88.8nginx06we…

Nodejs -- 流程控制库

流程控制库 尾触发和next 尾触发最多的应用是在Connect的中间件 var app connect() app.use(connect.staticCache()) app.use(connect.static(__dirname /public)) app.use(conect.cokkieParser()) app.use(connect.session()) app.use(connect.query()) app.use(connect.…

跨平台终端软件——quardCRT

作为一个技术栈比较复杂的程序&#xff0c;工作常常会在windows/linux/macos等不同的平台切换开发&#xff0c;开发过程中最常用的就是终端工具了&#xff0c;一个趁手的终端可以成倍的提高工作效率&#xff0c;因此我一直希望能找个一个跨平台体验一致无缝切换的终端软件&…

Unity Audio Filter 入门

概述&#xff1a; 如果你在你项目中需要一些特殊的声音效果&#xff0c;那这部分声音过滤器的部分一定不要错过喔&#xff0c;让我们来学习这部分的内容吧&#xff01; 这部分理论性比较强&#xff0c;认真看我的注解哈&#xff0c;我尽量解释的易懂一点。 Audio Chorus Filter…

街道征迁项目档案管理系统

街道征迁项目档案管理系统是一个用于管理街道征迁项目档案的软件系统。该系统的主要功能包括档案录入、档案存储、档案检索、档案共享等。 系统的用户可以通过该系统录入征迁项目相关的档案信息&#xff0c;包括项目名称、征迁范围、土地面积、征迁补偿费用等。同时&#xff0c…

vue本地调试devtools

一、谷歌浏览器加载扩展程序 二、把解压的压缩包添加即可&#xff0c;重启浏览器 三、启动前端本地项目&#xff0c;即可看到Vue小图标

AD | Altium Designer(原理图设计、电路仿真、PCB绘图)汉化版

Altium Designer(原理图设计、电路仿真、PCB绘图) 通知公告 Altium Designer(AD)是一种功能强大的电子设计自动化(EDA)软件。它主要用于设计和开发电子产品,如电路板(PCB)、集成电路(IC)和嵌入式系统。AD提供了完整的设计工具套件,包括原理图设计、PCB布局、仿真、设…

ICode国际青少年编程竞赛- Python-1级训练场-识别循环规律1

ICode国际青少年编程竞赛- Python-1级训练场-识别循环规律1 1、 for i in range(4):Dev.step(6)Dev.turnLeft()2、 for i in range(3):Dev.turnLeft()Dev.step(2)Dev.turnRight()Dev.step(2)3、 for i in range(3):Spaceship.step(5)Spaceship.turnLeft()Spaceship.step(…

互联网轻量级框架整合之MyBatis底层运转逻辑

MyBatis运转过程中主要步骤有两个&#xff0c;其一读取配置文件缓存到Configuration对象&#xff0c;用于构建SqlSessionFactory&#xff1b;其二是SqlSession的执行过程&#xff0c;这其中SqlSessionFactory的构建过程相对很好理解&#xff0c;而SqlSession的执行过程就相对复…

LT6911GX HDMI2.1 至四端口 MIPI/LVDS,带音频 龙迅方案

1. 描述LT6911GX 是一款面向 VR / 显示应用的高性能 HDMI2.1 至 MIPI 或 LVDS 芯片。HDCP RX作为HDCP中继器的上游&#xff0c;可以与其他芯片的HDCP TX配合使用&#xff0c;实现中继器功能。对于 HDMI2.1 输入&#xff0c;LT6911GX 可配置为 3/4 通道。自适应均衡功能使其适合…

vue3+vite+js 实现移动端,PC端响应式布局

目前使用的是vue3vite&#xff0c;没有使用ts 纯移动端|PC端 这种适用于只适用一个端的情况 方法&#xff1a;amfe-flexible postcss-pxtorem相结合 ① 执行以下两个命令 npm i -S amfe-flexible npm install postcss-pxtorem --save-dev② main.js文件引用 import amfe-f…

FreeRTOS信号量

信号量简介 def 1&#xff1a; 信号量是一种解决问题的机制&#xff0c;可以实现共享资源的访问 信号量浅显理解例子&#xff1a; 空车位&#xff1a; 信号量资源&#xff08;计数值&#xff09; 让出占用车位&#xff1a; 释放信号量&#xff08;计数值&#xff09; 占用车…

LT6911UXB HDMI2.0 至四端口 MIPI DSI/CSI,带音频 龙迅方案

1. 描述LT6911UXB 是一款高性能 HDMI2.0 至 MIPI DSI/CSI 转换器&#xff0c;适用于 VR、智能手机和显示应用。HDMI2.0 输入支持高达 6Gbps 的数据速率&#xff0c;可为4k60Hz视频提供足够的带宽。此外&#xff0c;数据解密还支持 HDCP2.2。对于 MIPI DSI / CSI 输出&#xff0…

jvm 马士兵 01

01.JVM是什么 JVM是一个跨平台的标准 JVM只识别class文件&#xff0c;符合JVM规范的class文件都可以被识别

知乎广告开户流程,知乎广告的优势是什么?

社交媒体平台不仅是用户获取知识、分享见解的场所&#xff0c;更是品牌展示、产品推广的重要舞台。知乎作为国内知名的知识分享社区&#xff0c;以其高质量的内容生态和庞大的用户基础&#xff0c;成为了众多企业进行广告投放的优选之地。云衔科技通过其专业服务&#xff0c;助…

数字身份管理:Facebook如何利用区块链技术?

随着数字化进程的加速&#xff0c;个人身份管理已成为一个关键议题。在这方面&#xff0c;区块链技术正在逐渐展现其巨大潜力。作为全球最大的社交媒体平台&#xff0c;Facebook也在积极探索和应用区块链技术来改进其数字身份管理系统。本文将深入探讨Facebook如何利用区块链技…

<Linux> 权限

目录 权限人员相对于文件来说的分类更改权限文件的拥有者与所属组 权限 权限是操作系统用来限制对资源访问的机制&#xff0c;权限一般分为读、写、执行。系统中的每个文件都拥有特定的权限、所属用户及所属组&#xff0c;通过这样的机制来限制哪些用户、哪些组可以对特定文件…