Day924.自动化测试 -系统重构实战

news/2024/5/4 1:21:05/文章来源:https://blog.csdn.net/qq_43284469/article/details/129718871

自动化测试

Hi,我是阿昌,今天学习记录的是关于自动化测试的内容。

自动化测试是一个很容易产生“争议”的话题,也经常会有一些很有意思的问题。

  • 自动化测试不是应该由测试同学来编写吗,开发是不是没有必要学吧?
  • 之前一个自动化测试都没写过,怎么开始落地呢?
  • 编写自动化测试代码意味着要写更多的代码,这能带来什么好处呢?

在这个过程中的代码”


一、示例介绍

这个示例是一个登录的场景。

当用户在登录页面输入正确的账户和密码时,能正常跳转到登录界面,否则提示登录失败的信息。

下面是关键的代码。

  • 登录页面代码
public class LoginActivity extends AppCompatActivity {private LoginLogic loginLogic = new LoginLogic();@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);final EditText usernameEditText = findViewById(R.id.username);final EditText passwordEditText = findViewById(R.id.password);final Button loginButton = findViewById(R.id.login);loginButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {boolean success = loginLogic.login(LoginActivity.this,usernameEditText.getText().toString(),passwordEditText.getText().toString());if (success) {//登录成功跳转主界面startActivity(new Intent(LoginActivity.this, MainActivity.class));} else {//登录失败进行提示Toast.makeText(LoginActivity.this, "login failed", Toast.LENGTH_LONG).show();}}});}
}
  • 登录逻辑代码
public class LoginLogic {public boolean login(Context context,String username, String password) {if (!isUserNameValid(username) || !isPasswordValid(password)) {return false;} else {//通过服务器判断账户及密码的有效性boolean result = checkFromServer(username, password);if (result) {//登录成功保持本地的信息SharedPreferencesUtils.put(context, username, password);}return result;}}// 为了进行演示,去除通过服务器鉴定的逻辑,当用户输入特定账号及密码为时则验证成功private static boolean checkFromServer(String username, String password) {if (username.equals("123@163.com") && password.equals("123456")) {return true;}return false;}private boolean isUserNameValid(String username) {if (username == null) {return false;}if (username.contains("@")) {return Patterns.EMAIL_ADDRESS.matcher(username).matches();} else {return !username.trim().isEmpty();}}private boolean isPasswordValid(String password) {return password != null && password.trim().length() > 5;}
}

注意,这里为了简化演示,将验证的逻辑写死在本地了。另外,账户密码有两个核心的验证规则。

  • 账户不能为空,需要符合邮箱规则。
  • 密码不能为空,长度需要超过 5 个字符。

二、搭建测试环境

当通过默认的编辑器创建新的项目工程时,编辑器会自动创建好测试的运行配置,一般无需修改。

如果要增加测试框架,就把测试框架的 Maven 坐标添加到对应的 dependencies 中即可。

Gradle 中的测试相关配置代码是后面这样。

android{defaultConfig {testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}dependencies {testImplementation 'junit:junit:4.13.2'androidTestImplementation 'androidx.test.ext:junit:1.1.3'androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'}
}

接下来,在默认的 /src/test 或 /src/androidTest 目录下编写用例。

注意,test 目录的用例运行不依赖于设备,androidTest 目录下的用例运行需要依赖设备。

在这里插入图片描述

一般来说,自动化测试分为小型、中型和大型三种,逐个看看这三种测试怎么落地。


三、小型自动化测试实践

小型测试是指单元测试,用于验证应用的行为,一次验证一个类。

在这个示例中,LoginLogic 主要承担的是登录逻辑,这里就以账户密码的验证逻辑为例,演示一下小型测试的编写,这两个逻辑的主要规则是这样。

  • 账户不能为空,需要符合邮箱规则。
  • 密码不能为空,长度需要超过 5 个字符。

接着,设计对应的测试用例。

注意,用例的设计应该包含正常和异常的验证场景,具体的测试场景是后面这样。

  • 输入大于 6 个字符长度的密码,验证成功。
  • 输入为 Null 的字符,验证失败。
  • 输入小于 5 个字符长度的密码,验证失败。
  • 输入等于 5 个字符长度的密码,验证失败。

下面新建一个 LoginLogicTest 的测试类,按照上述的测试场景编写用例。

这里我会采用 given(输入)、when(执行)、then(结果)的形式,让用例更加结构化,便于理解和维护。

具体的测试用例代码如下:


public class LoginLogicTest {@Testpublic void should_return_false_when_password_is_null() {LoginLogic loginLogic = new LoginLogic();String password = null;boolean result = loginLogic.isPasswordValid(password);Assert.assertFalse(result);}@Testpublic void should_return_false_when_password_length_is_less_than_5() {LoginLogic loginLogic = new LoginLogic();String password = "1234";boolean result = loginLogic.isPasswordValid(password);Assert.assertFalse(result);}@Testpublic void should_return_false_when_password_length_is_equal_5() {LoginLogic loginLogic = new LoginLogic();String password = "12345";boolean result = loginLogic.isPasswordValid(password);Assert.assertFalse(result);}@Testpublic void should_return_true_when_password_length_greater_than_5() {LoginLogic loginLogic = new LoginLogic();String password = "123456";boolean result = loginLogic.isPasswordValid(password);Assert.assertTrue(result);}
}

通过点击用例旁的运行箭头可以执行用例,如下图所示。

在这里插入图片描述

运行完就可以直接查看运行结果了,如下图所示。

在这里插入图片描述

可以看出小型测试的执行时间还是比较快的,4 个用例总共用了 7 ms。

此外,还可以用另一种方式执行测试用例:使用命令行./gradlew test,运行 test 目录下的测试用例,如下图所示。
在这里插入图片描述

执行完测试后,在 /build/reports/tests 下可以查看到对应的测试报告,报告截图如下所示,从中能得到每个用例具体的执行情况和执行时间。

在这里插入图片描述


四、中型自动化测试实践

中型测试是指集成测试,用于验证模块内堆栈级别之间的互动或相关模块之间的互动。

常用的测试框架有两种:Robolectric 和 Espresso。

在登录示例中,当 LoginLogic 的 login 方法被调用时,程序主逻辑首先会执行对账户名和密码的校验,接着通过服务器对账户密码的有效性做校验。

当登录成功时,程序主逻辑会通过 SharedPreferences 保存用户的信息,并在最后返回登录的状态。

从示例代码中可以看出,LoginActivity 类主要都是 UI 的操作,所以对该类主要覆盖的是 UI 相关的测试;

对于 LoginLogic 的类核心方法,login 主要负责整体的业务验证逻辑。

如何通过 Espresso 和 Robolectric 对这两个类进行中型自动化测试的覆盖。

1、Espresso 的使用

Espresso 是 Google 官方提供的界面测试框架,使用简洁且可靠。

它可以声明预期、交互和断言,不用直接访问底层应用的 Activity 和视图,可以防止测试不稳定,提高测试运行的速度。

根据用户 UI 上的主要操作,将覆盖以下两个主要的业务场景。

  • 用户输入正确的用户名(123@163.com)和密码(123456),点击登录按钮能成功跳转到登录界面。
  • 用户输入错误的用户名(123)和密码(456),点击登录按钮提示登录失败的 Toast。

根据测试场景,使用 Espresso 对 LoginActivity 设计的测试用例代码如下。

 public class LoginActivityTest {@Testpublic void should_start_main_activity_when_execute_login_given_valid_username_and_password() {ActivityScenario.launch(LoginActivity.class);onView(withId(R.id.username)).perform(typeText("123@163.com"));onView(withId(R.id.password)).perform(typeText("123456"));Intents.init();onView(withId(R.id.login)).perform(click());intended(allOf(toPackage("com.jkb.junbin.autotestdemo"),hasComponent(hasClassName(MainActivity.class.getName()))));}@Testpublic void should_show_failed_toast_when_execute_login_given_invalid_username_and_password() {ActivityScenario<LoginActivity> launch = ActivityScenario.launch(LoginActivity.class);onView(withId(R.id.username)).perform(typeText("123"));onView(withId(R.id.password)).perform(typeText("456"));onView(withId(R.id.login)).perform(click());View decorView = null;launch.onActivity(activity -> {activity.getWindow().getDecorView();});onView(withText("login failed")).inRoot(withDecorView(not(decorView))).check(matches(isDisplayed()));}
}

Espresso 提供的 API 能方便地进行元素的定位、执行操作和断言。

在上述两个用例中,用 onView 定位元素,用 perform 执行操作,用 check 进行断言。

如果想了解 Espresso 更多的操作 API,可以参考官网的介绍。

执行完上述用例后,运行结果是下图这样:

在这里插入图片描述

可以看到,这两个测试用例在模拟器中整体的运行时间在 5s 左右。

相比小型测试,中型测试耗时会更长。

并且用例运行需要依赖设备,这让运行测试的成本更高。

用例的执行过程如下图所示:

在这里插入图片描述


2、Robolectric 的使用

Robolectric 框架能为 Android 带来快速可靠的测试。

具体来说,依赖该框架的测试用例无须在真机或者模拟器上运行,在本地工作站上的 JVM 内完成运行即可,一般只需要几秒。

下面以 LoginLogic 的 login 方法为例,介绍一下 Robolectric 的使用。

这里需要覆盖下面三个主要的业务场景。

  • 传入空的字符串或者密码,返回失败。
  • 传入错误的账户及密码,返回失败。
  • 传入正确的账户及密码,返回成功,并且进行数据缓存。

后面是测试用例的代码。

@RunWith(RobolectricTestRunner.class)
public class LoginLoginMediumTest {private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();@Testpublic void should_return_false_when_given_invalid_username_or_password() {LoginLogic loginLogic = new LoginLogic();boolean nullUserNameResult = loginLogic.login(mContext, null, "123");Assert.assertFalse(nullUserNameResult);boolean nullPasswordResult = loginLogic.login(mContext, "123", null);Assert.assertFalse(nullPasswordResult);}@Testpublic void should_return_false_when_given_error_username_and_password() {//验证错误的账户及密码boolean result = new LoginLogic().login(mContext, "123", "456");Assert.assertFalse(result);}@Testpublic void should_return_true_when_given_correct_username_and_password() {String username = "123@163.com";String password = "123456";//验证正确的账户及密码boolean result = new LoginLogic().login(mContext, username, password);Assert.assertTrue(result);//验证存在缓存信息String cachePassword = (String) SharedPreferencesUtils.get(mContext, username, "");Assert.assertEquals(password, cachePassword);}
}

关于 Robolectric 的 API,可以参考官网的介绍。

执行上述测试用例后,运行结果如下图。

在这里插入图片描述

从结果可以看出,用 Robolectric 框架进行测试,用例的执行时间在毫秒到秒之间。其中第二个用例的执行耗时超过 2s,是因为启动 Robolectric 框架需要一定的时间。同时我们也能体会到 Robolectric 的核心优势:无须依赖设备,可以快速在本地的 JVM 进行验证,得到快速的反馈。


五、大型自动化测试实践

大型测试是指端到端测试,用于验证跨越了应用的多个模块的用户操作流程。

前面介绍的 Espresso 和 Robolectric 主要是针对单个页面的测试场景,在实际的应用业务场景中,还有涉及跨应用和系统 UI 交互的场景。

通常会用 UI Automator 完成大型测试。

UI Automator 是一个界面测试框架,适用于整个系统和多个已安装应用间的跨应用功能界面测试。

它提供了一组 API,用于构建在用户应用和系统应用上执行交互的界面测试。

通过 UI Automator API,可以在测试设备中执行打开“设置”菜单或应用启动器等操作。

相比 Espresso 和 Robolectric 编写的白盒测试用例,UI Automator 测试框架非常适合编写黑盒式自动化测试,此类测试的测试代码不依赖于目标应用的内部实现细节。下面继续对登录示例进行完整的功能测试。

完整的用户测试场景是这样的:

用户在手机的任意界面,返回桌面启动测试的应用登录界面,然后输入正确的用户名和密码,成功跳转到主界面,验证主界面上显示的用户信息是否正确。这个用户场景会涉及到多个应用,其中包括了桌面和目标测试应用。

另外,应用内还会涉及到多个页面,主要是登录界面和主界面。

现在使用 UI Automator 框架进行测试,用例代码是这样。


@RunWith(AndroidJUnit4.class)
public class SmellTest {private static final String BASIC_SAMPLE_PACKAGE= "com.example.sample";private static final int LAUNCH_TIMEOUT = 5000;private UiDevice mDevice;@Beforepublic void startActivityFromHomeScreen() {// 初始化UiDevicemDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());// 回到主界面mDevice.pressHome();// 等待launcherfinal String launcherPackage = mDevice.getLauncherPackageName();assertThat(launcherPackage, notNullValue());mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),LAUNCH_TIMEOUT);// 启动目标APPContext context = ApplicationProvider.getApplicationContext();final Intent intent = context.getPackageManager().getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);context.startActivity(intent);// 等待应用启动mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),LAUNCH_TIMEOUT);}//账户密码登录成功后主界面显示用户名@Testpublic void should_show_username_in_main_activity_when_login_success() {//输入账户名mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "username")).setText("123");//输入密码mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "password")).setText("123");//点击登录mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "login")).click();//验证主界面上显示用户名信息UiObject2 text = mDevice.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "text")),500);assertEquals(text.getText(), "123");}
}

UI Automator 测试框架提供了一个 UiDevice 类,用于在运行目标应用的设备上访问和执行操作。

通过调用 findObject 方法,可以定位到元素和执行操作。

关于 UI Automator 更多的 API 使用,你可以参考官网文档 。

执行上述测试用例后,运行结果如下图所示:

在这里插入图片描述

下面这个动图展示了用例的执行过程,可以对比一下,这个过程如果用手工执行需要多久。

在这里插入图片描述

通过执行结果可知,该用例的执行时间实际为 9s,比中小型测试的执行时间更长,并且需要依赖真机或模拟器。

不过,该用例基本都是模拟用户对界面的点击操作,更贴近实际用户的真实使用场景。


六、总结

  • 小型测试能够快速帮验证代码中的核心逻辑和算法,通常使用的是 Junit 或者 Robolectric 等测试框架;
  • 中型测试能够帮验证代码中的一些核心组件交互流程,通常会用 Espresso 或者 Robolectric 等框架来完成;
  • 大型测试则能帮验证端到端的用户使用场景,通常使用的是 UIAutomator 或者 Appium 等框架。

在这里插入图片描述

重新思考一下开头的三个问题:

  • 第一个是测试由谁来写的问题。中小型的测试大部分都是根据代码设计来编写的。编写者需要了解原来代码的设计,精确到各个方法以及方法内部的条件分支和异常处理。所以,中小型测试应该由开发人员来编写。另外,也鼓励开发人员参与到大型端到端自动化测试的编写中。因为只有开发代码和测试代码一起共同维护,成本才是最低的。
  • 第二个问题是:之前一个自动化测试都没写过,怎么开始落地?对于开发人员来说,编写自动化测试用例的难度其实比功能开发设计还简单。
  • 第三个问题是关于自动化测试价值的问题。这里有一个前提:不认为开发完代码就意味着结束,结束应该是在有足够的质量保证的前提下。

所以,自动化测试是应用开发过程中不可或缺的一部分。通过持续运行测试,可以在发布版本之前验证其正确性、功能行为和易用性。

具体来讲,自动化测试给开发同学带来的帮助有这样三点:

  1. 自动化测试能提供多样化的编译调试。通常测试的运行时间在毫秒至秒之间,有助于提高我们编译调试的效率。
  2. 自动化测试能加强开发代码自测,帮我们快速获得故障反馈。通常我们在本地编写完代码后,就可以马上运行测试,检查功能是否正确。这样的好处是能在开发早期尽早发现问题。
  3. 自动化测试还能提供更安全的代码重构,当有了自动化测试这个安全守护网,可以放心地优化代码,不必担心引发新的问题,也可以尽可能避免其他人乱改代码破坏原有的逻辑。因为一旦有修改破坏了之前的自动化测试用例,CI 门禁就会立即检查出来,避免代码合入。

虽然自动化测试可以提升开发的效率和质量,但对于遗留系统来说,还有另外一个非常棘手的问题,那就是代码可测试性低。


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

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

相关文章

前端 Gulp 详细介绍与案例使用

一、简介 前端常见打包构建工具&#xff1a;gulp、webpack … gulp 是基于 流 的打包构建工具。webpack 是基于 js 文件的打包构建工具。 什么是 流 &#xff1f; 流文件&#xff1a;一种文件传输的格式&#xff0c;一段一段的文件传输&#xff0c;全部下载完成后再转换成指定…

自动化测试入门

一、在什么情况下做自动化测试&#xff0c;为什么做自动化测试 刚刚做测试的时候&#xff0c;对于在什么情况下做自动化测试只局限功能比较固定&#xff0c;需求变化不是很频繁且项目周期比较长的时候考虑做自动化测试。但是为什么要做自动化测试呢&#xff0c;具体有什么好处呢…

ASEMI代理FS32K142HAT0MLLT原装现货NXP车规级FS32K142HAT0MLLT

编辑&#xff1a;ll ASEMI代理FS32K142HAT0MLLT原装现货NXP车规级FS32K142HAT0MLLT 型号&#xff1a;FS32K142HAT0MLLT 品牌&#xff1a;NXP /恩智浦 封装&#xff1a;LQFP-100 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;100 类型&…

3.4.2数据库系统-逻辑结构设计:逻辑结构设计流程、关系模式转换

3.4.2数据库系统-逻辑结构设计&#xff1a;逻辑结构设计流程、关系模式转换逻辑结构设计流程关系模式转换逻辑结构设计流程 ER图向关系模式的转换&#xff1a; 实体向关系模式的转换联系向关系模式的转换 关系模式的泛化&#xff1a; 这个后面再说&#xff0c;内容比较多 确…

LLVM PASS pwn

LLVM LLVM的核心是一个库,其设计了一种通用的LLVM IR,并提供一系列接口来操作LLVM IR,生成目标平台代码等等后端的功能. LLVM Pass就是遍历传入的IR并进行一些处理,在实现上,LLVM的核心库中存在一些Pass类,通过继承这些类并重载一些方法,就可以方便的处理传入的IR. LLVM Pas…

关于OpenResty+doujiang24/lua-resty-kafka写入kafka故障转移模拟测试

关于OpenRestydoujiang24/lua-resty-kafka写入kafka故障转移模拟测试 PS&#xff1a;文章中用到的ip和代码已脱敏 1. 环境 请查看这篇文章https://editor.csdn.net/md/?articleId122735525 2. 配置 kafka地址&#xff1a; kafka_broker_list{{host"193.168.1.2"…

电子拣货标签10代系统简介

CK_Label_v10一、产品参数 1. 外接供电版 产品型号 CK_Label_v10 尺寸 0.8寸/位 屏幕显示 数码管显示&#xff08;3位数&#xff09; 数码管颜色 红色 显示内容 0-999 外观尺寸 114.5x44.5x19mm 外观颜色 蓝色 按键 4 指示灯 1 RGB 灯 灯光颜色 7种(红/绿…

GPU推理服务性能优化之路 | 得物技术

1背景 随着CV算法在业务场景中使用越来越多&#xff0c;给我们带来了新的挑战&#xff0c;需要提升Python推理服务的性能以降低生产环境成本。为此我们深入去研究Python GPU推理服务的工作原理&#xff0c;推理模型优化的方法。最终通过两项关键的技术: 1.Python的GPU与CPU进程…

最佳实践| 探索 Authing 企业级云原生权限治理平台

在现代企业中&#xff0c;数据已经成为最重要的资产之一。 有数据显示&#xff0c;全球大约有一半的组织在过去的一年中经历了至少一次成功的网络攻击事件&#xff0c;其中&#xff0c;39% 的攻击事件是由内部人员造成的。为了保护企业的数据和信息资产&#xff0c;许多政府和…

Linux应用开发之文件与IO流

与大多数操作系统一样&#xff0c;Linux为程序运行提供了大量的服务&#xff0c;包括打开文件、读文件、启动一个新程序、分配存储区以及获得当前时间等&#xff0c;这些服务被称为系统调用接口&#xff08;system call interface&#xff09;。另外&#xff0c;glibc库还提供了…

移除链表元素

给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 示例 2&#xff1a; 输入&#xff…

使用IDEA把项目上传到gitee仓库

使用IDEA把项目上传到gitee仓库在gitee上建立一个仓库第一步&#xff08;新建仓库&#xff09;第二步&#xff08;点击创建&#xff09;第三步&#xff08;复制仓库地址&#xff09;创建工程第一步&#xff08;选择工程所在文件夹&#xff09;第二步&#xff08;文件加入git&am…

qq怎么安装不了(QQ怎么都安装不上重装也不行,是哪里出了问题?)

qq怎么安装不了(QQ怎么都安装不上重装也不行&#xff0c;是哪里出了问题&#xff1f;) 一、发现问题 今天有朋友说他电脑怎么都装不上QQ&#xff0c;总是弹出“安装包可能被非法改动导致安装失败&#xff0c;请从官网下载最新安装包重新安装”&#xff0c;操作系统是XP&#…

[数据结构高频面试题]用两个栈实现队列详解

文章目录 一、栈实现队列的特点分析 1、1 具体分析 1、2 整体概括 二、用栈模拟队列代码的实现 2、1 手撕 栈 代码 2、1、1 stack.h 2、1、2 stack.c 2、2 用栈实现队列代码 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x1f64b;‍♂️ &#x1f440; 专栏&#xff1a;…

Flink- 物理分区、Sink输出

物理分区 随机分区&#xff08;shuffle&#xff09; 轮询分区&#xff08;Round-Robin&#xff09; 重缩放分区&#xff08;rescale&#xff09; 广播&#xff08;broadcast&#xff09; 全局分区&#xff08;global&#xff09; 自定义分区&#xff08;Custom&#xff09; …

Studio One6中文语言版DAW数字音频音乐创作软件

Studio One6是一款非常实用的数字音乐创作软件&#xff0c;专门用于创作现代化音乐&#xff0c;软件具有简洁的界面和强大的功能&#xff0c;能够很好地辅助用户创作音乐。顾名思义就是“一个工作室”的意思&#xff0c;它所倡导的制作理念是直接在一个制作软件里完成音乐制作的…

Android 解包payload.bin文件,获取system.img

解析payload.bin获取.img文件 payload.bin payload.bin是Android OTA镜像打包文件&#xff0c;将包括system.img、boot.img和lk.img等在内的Android系统进行&#xff0c;打包为一个payload.bin文件。 在系统OTA过程中&#xff0c;系统会自动解压安装。 前期准备 需要安装py…

学习Java日志框架之——搞懂日志门面(JCL+SLF4J)

文章目录一、什么是日志门面1、门面模式&#xff08;外观模式&#xff09;2、日志门面二、了解JCL1、JCL组件结构2、JCL案例&#xff08;1&#xff09;JCL默认实现&#xff08;2&#xff09;导入log4j测试原有程序三、SLF4J简介四、SLF4J基本使用1、入门案例2、动态打印信息3、…

一次内存泄露排查

前因&#xff1a; 因为测试 长时间压测导致 接口反应越来越慢&#xff0c;甚至 导致服务器 崩溃 排查过程 1、top 查看是 哪个进程 占用 内存过高 2、根据 进程 id 去查找 具体是哪个 程序的问题 ps -ef| grep 41356 可以看到 具体的 容器位置 排查该进程 对象存活 状态…

23年PMP考试会使用第七版教材吗?

大家都知道了&#xff0c;今年的考纲是改版了的&#xff0c;为啥要改版呢&#xff0c;因为《PMBOK指南》更新到第七版了&#xff0c;考纲自然也要更新&#xff0c;据PMI的市场调查&#xff0c;近年来&#xff0c;项目管理行业新趋势在第六版和旧考纲中未收纳&#xff0c;为了确…