• 项目实战——Web自动化测试


    目录

    一、前言及测试用例设计

    二、 首页测试(未登录)

    三、注册测试

    四、对局列表测试

    五、排行榜测试

    六、对战测试

    七、Bot测试

    八、测试套件Suite


    一、前言及测试用例设计

    整个项目已经部署完成,我们历经九九八十一难,但再好的软件,bug也是再所难免,因此从这篇文章开始(也可能只有这一篇文章),我们就对整个项目进行测试,也会包含web自动化测试,因此如果没有学习过测试的同学可以看我之前的博客,废话不多说我们直接开始介绍我们的总体测试:

    二、 首页测试(未登录)

     首先在我们的test目录下新建 FrontPageTest 类,由于我们每次都要用到 Chrome 的驱动,因此我们可以把它封装成一个工具类:

    1. package com.kob.backend.tests.common;
    2. import org.openqa.selenium.chrome.ChromeDriver;
    3. public class CommonDriver {
    4. // 创建驱动对象并返回
    5. private static ChromeDriver driver;
    6. public ChromeDriver getDriver() {
    7. if (driver == null) driver = new ChromeDriver();
    8. return driver;
    9. }
    10. }

    为了防止有时出现页面没加载出来的情况,这里添加隐式等待:

    driver.manage().timeouts().implicitlyWait(Duration.ofMillis(3000));

    FrontPageTest:

    1. package com.chaoyang.tests;
    2. import com.chaoyang.autotest.common.CommonDriver;
    3. import org.junit.jupiter.api.Assertions;
    4. import org.junit.jupiter.api.BeforeAll;
    5. import org.junit.jupiter.api.Test;
    6. import org.openqa.selenium.By;
    7. import org.openqa.selenium.chrome.ChromeDriver;
    8. public class FrontPageTest {
    9. private static ChromeDriver driver = CommonDriver.getDriver();
    10. /*
    11. 跳转到对应url
    12. */
    13. @BeforeAll
    14. static void getUrl() {
    15. driver.get("https://app2703.acapp.acwing.com.cn/");
    16. }
    17. /*
    18. 校验首页(未登录)是否正确展示元素
    19. */
    20. @Test
    21. void checkFunIsExist() {
    22. String registerText = driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li:nth-child(2) > a")).getText();
    23. String loginText = driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li:nth-child(1) > a")).getText();
    24. // 检验文本是否符合预期
    25. Assertions.assertEquals(registerText,"注册");
    26. Assertions.assertEquals(loginText,"登录");
    27. driver.quit();
    28. }
    29. }

    三、注册测试

     首先我们要保证我们的状态是未登录的,然后我们去校验“确认密码” 这个元素

    1. @Test
    2. void checkFunISExist() {
    3. String registerText = driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > div:nth-child(3) > label")).getText();
    4. // 获取注册页面是否包含对应文本,校验文本是否符合预期
    5. Assertions.assertEquals(registerText,"确认密码");
    6. }

    如何确定我们从注册页面跳转到登录页面呢?我们去校验页面包含“密码”而不含“确认密码”即可, 

    1. void addUser() {
    2. // 用户名
    3. driver.findElement(By.cssSelector("#username")).sendKeys("杨过7");
    4. // 密码
    5. driver.findElement(By.cssSelector("#password")).sendKeys("123");
    6. // 确认密码
    7. driver.findElement(By.cssSelector("#confirmedPassword")).sendKeys("123");
    8. // 提交
    9. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    10. // 是否跳转到登录界面
    11. // 包含输入密码 但 不包含 确认密码 即可确定我们已经到达登录界面
    12. String password = driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > div:nth-child(2) > label")).getText();
    13. Assertions.assertEquals(password,"密码");
    14. }

    但这样一次添加一个用户测试太慢,这时就要使用到我们的参数化,使用大量数据测试!

    不了解参数化的朋友可以看我之前软件测试章节补补功课

    添加@ParameterizedTest 注解

    添加数据来源注解

    这里可以构造一个static 同名方法即可

    参数来源 common.ParamsUtil:

    1. package com.chaoyang.tests.common;
    2. public class ParamsUtil {
    3. private String[] users = new String[] {"姜子牙1","瑞文1","鲁班八号","百里守约1","芈月1"};
    4. private String[] password = new String[] {"123","321","456","qwer123","哈哈哈"};
    5. public String getUserName() {
    6. // 随机返回一个用户名
    7. int index = (int)(Math.random()*users.length);
    8. return users[index];
    9. }
    10. /*
    11. 密码和确认密码
    12. */
    13. public String getPassword() {
    14. int index = (int)(Math.random()*password.length);
    15. return password[index];
    16. }
    17. }

    RegisterTest:

    1. package com.chaoyang.tests;
    2. import com.chaoyang.autotest.common.CommonDriver;
    3. import com.chaoyang.tests.common.ParamsUtil;
    4. import org.junit.jupiter.api.*;
    5. import org.junit.jupiter.params.ParameterizedTest;
    6. import org.junit.jupiter.params.provider.Arguments;
    7. import org.junit.jupiter.params.provider.MethodSource;
    8. import org.openqa.selenium.By;
    9. import org.openqa.selenium.chrome.ChromeDriver;
    10. import java.util.stream.Stream;
    11. @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    12. public class RegisterTest {
    13. private static ChromeDriver driver = CommonDriver.getDriver();
    14. /*
    15. 跳转url
    16. */
    17. @BeforeAll
    18. static void getUrl() {
    19. driver.get("https://app2703.acapp.acwing.com.cn/user/account/register/");
    20. }
    21. @Test
    22. @Order(1)
    23. void checkFunISExist() {
    24. String registerText = driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > div:nth-child(3) > label")).getText();
    25. // 获取注册页面是否包含对应文本,校验文本是否符合预期
    26. Assertions.assertEquals(registerText,"确认密码");
    27. }
    28. /*
    29. 正确注册用户之后跳转到登录页面
    30. */
    31. @Order(2)
    32. @ParameterizedTest()
    33. @MethodSource()
    34. void addUser(String name,String password,String confirmedPassword) {
    35. // 用户名
    36. driver.findElement(By.cssSelector("#username")).sendKeys(name);
    37. // 密码
    38. driver.findElement(By.cssSelector("#password")).sendKeys(password);
    39. // 确认密码
    40. driver.findElement(By.cssSelector("#confirmedPassword")).sendKeys(confirmedPassword);
    41. // 提交
    42. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    43. // 是否跳转到登录界面
    44. // 包含输入密码 但 不包含 确认密码 即可确定我们已经到达登录界面
    45. String password1 = driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > div:nth-child(2) > label")).getText();
    46. Assertions.assertEquals(password1,"密码");
    47. }
    48. static Stream addUser() {
    49. // 给 addUSer 提供数据来源
    50. ParamsUtil paramsUtil = new ParamsUtil();
    51. String userName = paramsUtil.getUserName();
    52. String password = paramsUtil.getPassword();
    53. return Stream.of(Arguments.arguments(userName,password,password));
    54. }
    55. @AfterAll
    56. static void driverQuit() {
    57. // driver.quit();
    58. }
    59. }

     测试结果:

     

    成功跳转! 完美实现!

    四、对局列表测试

    当我们登录之后,测试我们是否可以正常获取所有的对局列表,以及是否可以正常查看回放,以及切页功能是否正常。

     

    创建 RecordTest:

    1. package com.chaoyang.tests;
    2. import org.junit.jupiter.api.AfterAll;
    3. import org.junit.jupiter.api.Assertions;
    4. import org.junit.jupiter.api.BeforeAll;
    5. import org.junit.jupiter.api.Test;
    6. import org.openqa.selenium.By;
    7. import org.openqa.selenium.chrome.ChromeDriver;
    8. public class RecordTest {
    9. private static ChromeDriver driver = new ChromeDriver();
    10. /*
    11. 跳转到对局回顾 url
    12. */
    13. @BeforeAll
    14. static void getUrl() throws InterruptedException {
    15. driver.get("https://app2703.acapp.acwing.com.cn/user/account/login/");
    16. driver.findElement(By.cssSelector("#username")).sendKeys("杨过1");
    17. driver.findElement(By.cssSelector("#password")).sendKeys("123");
    18. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    19. Thread.sleep(1000);
    20. driver.findElement(By.cssSelector("#navbarText > ul.navbar-nav.me-auto.mb-2.mb-lg-0 > li:nth-child(2) > a")).click();
    21. }
    22. /*
    23. 测试是否正确展示元素
    24. */
    25. @Test
    26. void checkFunIsExist() throws InterruptedException {
    27. String A = driver.findElement(By.cssSelector("#app > div > div > div > table > thead > tr > th:nth-child(1)")).getText();
    28. String B = driver.findElement(By.cssSelector("#app > div > div > div > table > thead > tr > th:nth-child(2)")).getText();
    29. String recordTime = driver.findElement(By.cssSelector("#app > div > div > div > table > thead > tr > th:nth-child(4)")).getText();
    30. Assertions.assertEquals(A,"A");
    31. Assertions.assertEquals(B,"B");
    32. Assertions.assertEquals(recordTime,"对战时间");
    33. driver.findElement(By.cssSelector("#app > div > div > div > table > tbody > tr:nth-child(2) > td:nth-child(5) > button")).click();
    34. // 点击之后等待四秒,保证我们的对局回放结束
    35. Thread.sleep(4000);
    36. }
    37. @AfterAll
    38. static void driverQuit() {
    39. driver.quit();
    40. }
    41. }

    同时在每一次断言前截图,方便我们留存

    CommonDriver中修改:

    1. package com.chaoyang.tests.common;
    2. import org.apache.commons.io.FileUtils;
    3. import org.openqa.selenium.OutputType;
    4. import org.openqa.selenium.chrome.ChromeDriver;
    5. import java.io.File;
    6. import java.io.IOException;
    7. import java.text.SimpleDateFormat;
    8. import java.time.Duration;
    9. import java.util.ArrayList;
    10. import java.util.List;
    11. import java.util.stream.Stream;
    12. public class CommonDriver {
    13. // 创建驱动对象并返回
    14. private static ChromeDriver driver;
    15. public static ChromeDriver getDriver() {
    16. if (driver == null) {
    17. driver = new ChromeDriver();
    18. // 添加隐式等待
    19. // selenium 是无法等待弹窗的 如果遇到弹窗 可以强制等待
    20. driver.manage().timeouts().implicitlyWait(Duration.ofMillis(3000));
    21. }
    22. return driver;
    23. }
    24. public static List getTime() {
    25. // 文件名格式 20221201-141030+毫秒
    26. SimpleDateFormat sim1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
    27. // 文件夹名称格式 2022-12-01
    28. SimpleDateFormat sim2 = new SimpleDateFormat("yyyy-MM-dd");
    29. String filename = sim1.format(System.currentTimeMillis());
    30. String dirname = sim2.format(System.currentTimeMillis());
    31. List list = new ArrayList<>();
    32. list.add(dirname);
    33. list.add(filename);
    34. return list;
    35. }
    36. /*
    37. 获取屏幕截图
    38. str:类名下的用例
    39. */
    40. public static void getScreenshot(String str) throws IOException {
    41. List times = getTime();
    42. String filename = "./src/test/autotest-" + times.get(0) + "/" + str + "-" + times.get(1) + ".png";
    43. File srcFile = driver.getScreenshotAs(OutputType.FILE);
    44. // 把截图放到指定路径下
    45. FileUtils.copyFile(srcFile,new File(filename));
    46. }
    47. }

     

    测试上下切页功能是否正常 

    1. /*
    2. 测试切换页面功能
    3. */
    4. @Test
    5. void checkChange() throws InterruptedException {
    6. for (int i = 0; i < 3; i ++) {
    7. driver.findElement(By.cssSelector("#app > div > div > div > nav > ul > li:nth-child(5) > a")).click();
    8. Thread.sleep(1000);
    9. }
    10. }

     

    五、排行榜测试

    进入排行榜页面之后,我们可以得到所有用户的 rating 分和 用户名 ,在次基础上我们开始测试排行榜是否能正确展示以及上下切换功能是否正常

    RankListTest:

    1. package com.chaoyang.tests;
    2. import org.junit.jupiter.api.Assertions;
    3. import org.junit.jupiter.api.BeforeAll;
    4. import org.junit.jupiter.api.Test;
    5. import org.openqa.selenium.By;
    6. import org.openqa.selenium.chrome.ChromeDriver;
    7. public class RankListTest {
    8. private static ChromeDriver driver = new ChromeDriver();
    9. /*
    10. 跳转到排行榜 url
    11. */
    12. @BeforeAll
    13. static void getUrl() throws InterruptedException {
    14. driver.get("https://app2703.acapp.acwing.com.cn/user/account/login/");
    15. driver.findElement(By.cssSelector("#username")).sendKeys("杨过1");
    16. driver.findElement(By.cssSelector("#password")).sendKeys("123");
    17. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    18. Thread.sleep(1000);
    19. driver.findElement(By.cssSelector("#navbarText > ul.navbar-nav.me-auto.mb-2.mb-lg-0 > li:nth-child(3) > a")).click();
    20. }
    21. /*
    22. 测试是否正确展示元素
    23. */
    24. @Test
    25. void checkFunIsExist() throws InterruptedException {
    26. String player = driver.findElement(By.cssSelector("#app > div > div > div > table > thead > tr > th:nth-child(1)")).getText();
    27. String rating = driver.findElement(By.cssSelector("#app > div > div > div > table > thead > tr > th:nth-child(2)")).getText();
    28. Assertions.assertEquals(player,"玩家");
    29. Assertions.assertEquals(rating,"天梯分");
    30. }
    31. /*
    32. 测试切换页面功能
    33. */
    34. @Test
    35. void checkChange() throws InterruptedException {
    36. for (int i = 0; i < 3; i ++) {
    37. driver.findElement(By.cssSelector("#app > div > div > div > nav > ul > li:nth-child(5) > a")).click();
    38. Thread.sleep(1000);
    39. }
    40. }
    41. }

    六、Bot测试

    当我们的用户处于登录状态时,我们可以通过点击自己的用户名进而创建 Bot 或删除 Bot 

     接下来我们就对于Bot的创建删除等功能进行测试:

    切换页面功能:

    创建Bot功能: 

    1. package com.chaoyang.tests;
    2. import com.chaoyang.tests.common.CommonDriver;
    3. import org.junit.jupiter.api.*;
    4. import org.openqa.selenium.By;
    5. import org.openqa.selenium.chrome.ChromeDriver;
    6. @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    7. public class BotTest {
    8. private static ChromeDriver driver = CommonDriver.getDriver();
    9. /*
    10. 跳转到排行榜 url
    11. */
    12. @BeforeAll
    13. static void getUrl() throws InterruptedException {
    14. driver.get("https://app2703.acapp.acwing.com.cn/user/account/login/");
    15. driver.findElement(By.cssSelector("#username")).sendKeys("杨过1");
    16. driver.findElement(By.cssSelector("#password")).sendKeys("123");
    17. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    18. Thread.sleep(1000);
    19. driver.findElement(By.cssSelector("#navbarDropdown")).click();
    20. }
    21. /*
    22. 测试是否正确展示元素
    23. */
    24. @Test
    25. @Order(1)
    26. void checkFunIsExist() throws InterruptedException {
    27. String bot = driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li > ul > li:nth-child(1) > a")).getText();
    28. String exit = driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li > ul > li:nth-child(3) > a")).getText();
    29. Assertions.assertEquals(bot,"我的Bot");
    30. Assertions.assertEquals(exit,"退出");
    31. }
    32. /*
    33. 测试切换页面功能 - 到Bot页面
    34. */
    35. @Test
    36. @Order(2)
    37. void checkChange() throws InterruptedException {
    38. driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li > ul > li:nth-child(1) > a")).click();
    39. String myBot = driver.findElement(By.cssSelector("#app > div > div > div.col-9 > div > div.card-header > span")).getText();
    40. Assertions.assertEquals(myBot,"我的Bot");
    41. }
    42. /*
    43. 创建Bot 功能
    44. */
    45. @Test
    46. @Order(3)
    47. void checkCreateBot() {
    48. driver.findElement(By.cssSelector("#app > div > div > div.col-9 > div > div.card-header > button")).click();
    49. // 输入名称,简介,代码等。。。。
    50. driver.findElement(By.cssSelector("#add-bot-title")).sendKeys("名称1");
    51. driver.findElement(By.cssSelector("#add-bot-description")).sendKeys("简介1");
    52. driver.findElement(By.cssSelector("#add-bot-btn > div > div > div.modal-footer > button.btn.btn-primary")).click();
    53. }
    54. }

    修改Bot功能:

     

    删除Bot功能:

     

    成功删除~ 

    1. package com.chaoyang.tests;
    2. import com.chaoyang.tests.common.CommonDriver;
    3. import org.junit.jupiter.api.*;
    4. import org.openqa.selenium.Alert;
    5. import org.openqa.selenium.By;
    6. import org.openqa.selenium.chrome.ChromeDriver;
    7. @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    8. public class BotTest {
    9. private static ChromeDriver driver = CommonDriver.getDriver();
    10. /*
    11. 跳转到排行榜 url
    12. */
    13. @BeforeAll
    14. static void getUrl() throws InterruptedException {
    15. driver.get("https://app2703.acapp.acwing.com.cn/user/account/login/");
    16. driver.findElement(By.cssSelector("#username")).sendKeys("杨过1");
    17. driver.findElement(By.cssSelector("#password")).sendKeys("123");
    18. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    19. Thread.sleep(1000);
    20. driver.findElement(By.cssSelector("#navbarDropdown")).click();
    21. }
    22. /*
    23. 测试是否正确展示元素
    24. */
    25. @Test
    26. @Order(1)
    27. void checkFunIsExist() throws InterruptedException {
    28. String bot = driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li > ul > li:nth-child(1) > a")).getText();
    29. String exit = driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li > ul > li:nth-child(3) > a")).getText();
    30. Assertions.assertEquals(bot,"我的Bot");
    31. Assertions.assertEquals(exit,"退出");
    32. }
    33. /*
    34. 测试切换页面功能 - 到Bot页面
    35. */
    36. @Test
    37. @Order(2)
    38. void checkChange() throws InterruptedException {
    39. driver.findElement(By.cssSelector("#navbarText > ul:nth-child(2) > li > ul > li:nth-child(1) > a")).click();
    40. String myBot = driver.findElement(By.cssSelector("#app > div > div > div.col-9 > div > div.card-header > span")).getText();
    41. Assertions.assertEquals(myBot,"我的Bot");
    42. }
    43. /*
    44. 创建Bot 功能
    45. */
    46. @Test
    47. @Order(3)
    48. void checkCreateBot() {
    49. driver.findElement(By.cssSelector("#app > div > div > div.col-9 > div > div.card-header > button")).click();
    50. // 输入名称,简介,代码等。。。。
    51. driver.findElement(By.cssSelector("#add-bot-title")).sendKeys("名称1");
    52. driver.findElement(By.cssSelector("#add-bot-description")).sendKeys("简介1");
    53. driver.findElement(By.cssSelector("#add-bot-btn > div > div > div.modal-footer > button.btn.btn-primary")).click();
    54. driver.findElement(By.cssSelector("#add-bot-btn > div > div > div.modal-footer > button.btn.btn-secondary")).click();
    55. Alert alert = driver.switchTo().alert();
    56. alert.dismiss();
    57. }
    58. @Test
    59. @Order(4)
    60. void checkUpdate() {
    61. driver.findElement(By.cssSelector("#app > div > div > div.col-9 > div > div.card-body > table > tbody > tr > td:nth-child(3) > button.btn.btn-secondary")).click();
    62. driver.findElement(By.cssSelector("#add-bot-title")).sendKeys("名称1");
    63. driver.findElement(By.cssSelector("#add-bot-description")).sendKeys("简介1");
    64. driver.findElement(By.cssSelector("#add-bot-btn > div > div > div.modal-footer > button.btn.btn-primary")).click();
    65. driver.findElement(By.cssSelector("#add-bot-btn > div > div > div.modal-footer > button.btn.btn-secondary")).click();
    66. }
    67. @Test
    68. @Order(5)
    69. void checkDelete() {
    70. driver.findElement(By.cssSelector("#app > div > div > div.col-9 > div > div.card-body > table > tbody > tr > td:nth-child(3) > button.btn.btn-danger")).click();
    71. }
    72. }

    七、对战测试

    当我们进入到对战页面后,我们可以开始匹配进入游戏,也可以选择让我们的代码替我们出战,此时我们需要测试软件是否能正确展示我们的Bot并且可以选择人或者bot 出战!

    展示测试:

    下拉框测试(Bot列表):

     

     开始匹配功能测试:

     

     

    1. package com.chaoyang.tests;
    2. import com.chaoyang.tests.common.CommonDriver;
    3. import org.junit.jupiter.api.*;
    4. import org.junit.platform.suite.api.SelectClasses;
    5. import org.openqa.selenium.By;
    6. import org.openqa.selenium.WebElement;
    7. import org.openqa.selenium.chrome.ChromeDriver;
    8. import org.openqa.selenium.support.ui.Select;
    9. @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    10. public class PkTest {
    11. private static ChromeDriver driver = CommonDriver.getDriver();
    12. /*
    13. 跳转到pk url (首页默认就是pk)
    14. */
    15. @BeforeAll
    16. static void getUrl() throws InterruptedException {
    17. driver.get("https://app2703.acapp.acwing.com.cn/user/account/login/");
    18. driver.findElement(By.cssSelector("#username")).sendKeys("杨过1");
    19. driver.findElement(By.cssSelector("#password")).sendKeys("123");
    20. driver.findElement(By.cssSelector("#app > div > div > div > div > div > form > button")).click();
    21. }
    22. /*
    23. 测试是否正确展示元素
    24. */
    25. @Test
    26. @Order(1)
    27. void checkFunIsExist() throws InterruptedException {
    28. String pk = driver.findElement(By.cssSelector("#app > div > div > div.col-12 > button")).getText();
    29. Assertions.assertEquals(pk,"开始匹配");
    30. }
    31. /*
    32. 测试切换bot功能
    33. */
    34. @Test
    35. @Order(2)
    36. void checkChange() throws InterruptedException {
    37. WebElement ele = driver.findElement(By.cssSelector("#app > div > div > div:nth-child(2) > div > select"));
    38. Select select = new Select(ele);
    39. select.selectByIndex(0);
    40. }
    41. /*
    42. 测试开始匹配功能
    43. */
    44. @Test
    45. @Order(3)
    46. void checkPK() {
    47. driver.findElement(By.cssSelector("#app > div > div > div.col-12 > button")).click();
    48. }
    49. }

     

    八、测试套件Suite

    一个一个测试很麻烦,我们把所有的测试类集成到一个类RunSuite中:

    删除我们之前每个类中的driver.quit()

    因此我们把这个driver.quit专门抽取为一个类,放在最后执行即可

    1. package com.chaoyang.tests;
    2. import org.junit.platform.suite.api.SelectClasses;
    3. import org.junit.platform.suite.api.Suite;
    4. @Suite
    5. @SelectClasses({FrontPageTest.class,RegisterTest.class,RecordTest.class,DriverQuitTest.class})
    6. public class RunSuite {
    7. }

     

    截图成功! 

  • 相关阅读:
    JS—DOM节点的使用知识整理
    SpringBoot中如何实现业务校验,这种方式才叫优雅!
    GPDB7-新特性-角色创建
    Java NIO 有着一篇足够入门
    【Dison夏令营 Day 18】如何用 Python 中的 Pygame 制作国际象棋游戏
    lavaweb【初识后续问题的解决】
    使用java多线程模拟一个售票系统
    Matplotlib数据可视化基础
    【Linux】Linux工具——gdb
    vue 封装Table组件
  • 原文地址:https://blog.csdn.net/qq_59539549/article/details/128123745