用好 JUnit 5 的高级特性:提升单测效率和质量
写在前面
使用注解@DisplayName给测试方法命名
适用场景
使用示例
@DisplayName("用户服务测试")
class UserServiceTest {
@Test
@DisplayName("根据ID获取用户 - 当ID无效时抛出异常")
void testGetUserByIdWithInvalidId() {
// 测试逻辑
}
@Test
@DisplayName("创建用户 - 成功场景 ")
void testCreateUserSuccessfully() {
// 测试逻辑
}
}
使用嵌套注解@Nested组织代码
适用场景
@DisplayName("订单服务测试")
class OrderServiceTest {
@Nested
@DisplayName("创建订单")
class CreateOrder {
@Test
@DisplayName("当库存充足时 - 成功创建订单")
void whenStockSufficient_thenCreateOrderSuccessfully() {
// 测试逻辑
}
@Test
@DisplayName("当库存不足时 - 抛出异常")
void whenStockInsufficient_thenThrowException() {
// 测试逻辑
}
}
@Nested
@DisplayName("取消订单")
class CancelOrder {
@Test
@DisplayName("当订单状态为新订单时 - 成功取消")
void whenOrderStatusIsNew_thenCancelSuccessfully() {
// 测试逻辑
}
@Test
@DisplayName("当订单状态为已发货时 - 不允许取消")
void whenOrderStatusIsShipped_thenNotAllowCancel() {
// 测试逻辑
}
}
}
注意事项
- 嵌套层级控制:建议嵌套层级不超过3层,过深的嵌套会降低可读性。
- 生命周期方法:嵌套类可以有自己的@BeforeEach和@AfterEach方法,但不会继承外部类的这些方法。
- 共享资源:如果需要在外部和嵌套类之间共享资源,可以考虑使用@BeforeAll在外部类中初始化。
使用注解@RepeatedTest进行重复测试
适用场景
- 验证随机数生成的质量
- 测试并发安全性
- 验证资源清理是否彻底
- 检测偶发的竞态条件
使用示例
@DisplayName("随机数生成测试")
class RandomNumberTest {
@RepeatedTest(value = 100, name = "第{currentRepetition}次测试,共{totalRepetitions}次")
@DisplayName("验证随机数在合理范围内")
void testRandomNumberInRange(RepetitionInfo repetitionInfo) {
int random = RandomUtils.nextInt(1, 101);
assertTrue(random >= 1 && random <= 100,
() -> "第"repetitionInfo.getCurrentRepetition() + "次测试失败: "random);
}
}
- value:指定重复次数
- name:自定义测试显示名称,可以使用{currentRepetition}和{totalRepetitions}占位符
- 可以通过RepetitionInfo参数获取当前重复信息
使用参数化测试,避免大量重复代码
适用场景
各类数据源
@ValueSource 基础数据源
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, -3, 15})
@DisplayName("测试奇数验证")
void testIsOdd(int number) {
assertTrue(MathUtils.isOdd(number),
() -> number + " 应被识别为奇数");
}
@ParameterizedTest
@ValueSource(strings = {"racecar", "radar", "madam"})
@DisplayName("回文字符串验证")
void testPalindrome(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
@ParameterizedTest
@ValueSource(doubles = {1.5, 2.0, 3.8})
@DisplayName("双精度数验证")
void testDouble(double num) {
assertTrue(num > 1.0);
}
@EnumSource 数据源
enum Status {
NEW, PROCESSING, COMPLETED, CANCELLED
}
@ParameterizedTest
@EnumSource(Status.class)
@DisplayName("测试所有状态转换")
void testStatusTransition(Status status) {
assertDoesNotThrow(() -> OrderService.transitionStatus(status));
}
// 测试枚举子集
@ParameterizedTest
@EnumSource(value = Status.class, names = {"NEW", "PROCESSING"})
@DisplayName("测试可编辑状态")
void testEditableStatus(Status status) {
assertTrue(OrderService.isEditable(status));
}
// 模式匹配排除枚举值
@ParameterizedTest
@EnumSource(value = Status.class, mode = EXCLUDE, names = {"CANCELLED"})
@DisplayName("测试非取消状态")
void testNonCancelledStatus(Status status) {
assertNotEquals("CANCELLED", status.name());
}
@NullSource 和 @EmptySource 数据源
@ParameterizedTest
@NullSource
@DisplayName("测试处理null输入")
void testWithNullInput(String input) {
assertThrows(IllegalArgumentException.class,
() -> StringUtils.calculateLength(input));
}
@ParameterizedTest
@EmptySource
@DisplayName("测试处理空字符串")
void testWithEmptyString(String input) {
assertEquals(0, StringUtils.calculateLength(input));
}
// 组合使用
@ParameterizedTest
@NullAndEmptySource
@DisplayName("测试处理null和空字符串")
void testWithNullAndEmpty(String input) {
assertTrue(input == null || input.isEmpty());
}
@CsvSource 结构化数据源
@ParameterizedTest
@CsvSource({
"1, 1, 2", // 正常加法
"2, 3, 5", // 正常加法
"10, -5, 5", // 正负相加
"0, 0, 0" // 零值相加
})
@DisplayName("加法运算测试")
void testAdd(int a, int b, int expected) {
assertEquals(expected, MathUtils.add(a, b),
() -> String.format("%d + %d 应等于 %d", a, b, expected));
}
// 支持不同类型参数
@ParameterizedTest
@CsvSource({
"apple, 1",
"banana, 2",
"'', 0"
})
@DisplayName("字符串长度测试")
void testStringLength(String input, int expected) {
assertEquals(expected, input.length());
}
// 使用特殊分隔符
@ParameterizedTest
@CsvSource(delimiter = '|', value = {
"John Doe | 30 | true",
"Alice | 25 | false"
})
@DisplayName("用户验证测试")
void testUserValidation(String name, int age, boolean isAdult) {
assertEquals(isAdult, age >= 18);
}
@CsvFileSource 数据源
addend1,addend2,sum
1,1,2
2,3,5
-5,5,0
1000000,1000000,2000000
@ParameterizedTest
@CsvFileSource(resources = "/test-data/add_test_cases.csv", numLinesToSkip = 1)
@DisplayName("CSV文件数据驱动加法测试")
void testAddWithCsvFile(int addend1, int addend2, int sum) {
assertEquals(sum, Calculator.add(addend1, addend2),
() -> String.format("%d + %d 应等于 %d", addend1, addend2, sum));
}
// 使用不同分隔符的CSV文件
@ParameterizedTest
@CsvFileSource(resources = "/test-data/user_test_cases.tsv", delimiter = '\t')
void testUserImport(String username, String email, boolean expectedValid) {
assertEquals(expectedValid, UserValidator.isValid(username, email));
}
@MethodSource 复杂数据源
@ParameterizedTest
@MethodSource("stringProvider")
@DisplayName("字符串长度验证")
void testLength(String input, int expectedLength) {
assertEquals(expectedLength, input.length(),
() -> "'"input + "' 的长度应为 "expectedLength);
}
// 基础数据提供方法
static Stream<Arguments> stringProvider() {
return Stream.of(
Arguments.of("hello", 5),
Arguments.of("world", 5),
Arguments.of("", 0),
Arguments.of(" ", 2)
);
}
// 复杂对象测试
@ParameterizedTest
@MethodSource("userProvider")
@DisplayName("用户年龄验证")
void testUserAge(User user, boolean expected) {
assertEquals(expected, user.isAdult());
}
static Stream<Arguments> userProvider() {
return Stream.of(
Arguments.of(new User("Alice", 25), true),
Arguments.of(new User("Bob", 17), false),
Arguments.of(new User("Charlie", 18), true)
);
}
// 多参数组合测试
@ParameterizedTest
@MethodSource("rangeProvider")
@DisplayName("数字范围验证")
void testInRange(int number, int min, int max, boolean expected) {
assertEquals(expected, MathUtils.isInRange(number, min, max));
}
static Stream<Arguments> rangeProvider() {
return Stream.of(
Arguments.of(5, 1, 10, true),
Arguments.of(15, 1, 10, false),
Arguments.of(0, 0, 0, true)
);
}
组合使用多种数据源
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "\t", "\n"})
@DisplayName("测试各种空白输入")
void testBlankInputs(String input) {
assertTrue(StringUtils.isBlank(input));
}
@ParameterizedTest
@EnumSource(TimeUnit.class)
@ValueSource(ints = {1, 5, 10})
@DisplayName("测试时间单位转换")
void testTimeUnitConversion(TimeUnit unit, int value) {
assertNotNull(unit.toMillis(value));
}
各种数据源对比
数据源
|
适用场景
|
优点
|
缺点
|
@ValueSource
|
基本数据类型简单测试
|
使用简单
|
不支持复杂对象
|
@EnumSource
|
枚举值测试
|
自动生成所有枚举用例
|
仅适用于枚举
|
@CsvSource
|
结构化多参数测试
|
可读性好
|
维护大量数据时代码臃肿
|
@CsvFileSource
|
大量测试数据
|
数据与代码分离
|
需要维护外部文件
|
@MethodSource
|
需要动态生成或复杂对象的测试
|
最灵活,支持任意数据类型
|
需要额外编写提供方法
|
参数化测试的高级用法:自定义参数提供器
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
assertNotNull(argument);
}
static class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of("apple", "banana").map(Arguments::of);
}
}
条件测试
适用场景
- 只在特定操作系统上运行的测试
- 需要特定环境变量或系统属性的测试
- 只在集成测试环境中运行的耗时测试
- 针对特定租户的测试
- ...
内置注解
@Test
@EnabledOnOs(OS.LINUX)
void onlyOnLinux() {
// 只在Linux系统上运行
}
@Test
@DisabledOnJre(JRE.JAVA_8)
void notOnJava8() {
// 不在Java 8上运行
}
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitecture() {
// 只在64位架构上运行
}
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "ci")
void onlyOnCiServer() {
// 只在CI服务器上运行
}
@Test
@EnabledIf("customCondition")
void onlyIfCustomCondition() {
// 只在自定义条件满足时运行
}
boolean customCondition() {
return // 自定义条件逻辑;
}
使用示例
@Test
@EnabledForTenant("tenantA")
void testFeatureForTenantA() {
// 只对租户A运行的测试
}
@Test
@EnabledForTenant({"tenantA", "tenantB"})
void testFeatureForMultipleTenants() {
// 对租户A和B运行的测试
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(EnabledForTenantCondition.class)
public @interface EnabledForTenant {
String[] value();
}
public class EnabledForTenantCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Optional<EnabledForTenant> annotation = context.getElement()
.map(e -> e.getAnnotation(EnabledForTenant.class));
if (annotation.isPresent()) {
String currentTenant = System.getProperty("tenant");
if (Arrays.asList(annotation.get().value()).contains(currentTenant)) {
return ConditionEvaluationResult.enabled("租户匹配");
}
return ConditionEvaluationResult.disabled("当前租户不匹配");
}
return ConditionEvaluationResult.enabled("无租户限制");
}
}
使用Lambda表达式
Lambda表达式在单元测试中的优势
- 延迟计算:断言消息可以延迟计算,只有断言失败时才计算消息字符串,提高性能
- 简洁性:简化了异常断言等场景的代码
- 灵活性:可以轻松实现自定义断言逻辑
应用场景
@Test
void testComplexObject() {
ComplexObject obj = service.createComplexObject();
assertNotNull(obj, () -> "创建的对象不应为null");
assertEquals("expected", obj.getValue(),
() -> String.format("对象值不匹配,实际值:%s", obj.getValue()));
}
@Test
void testException() {
Executable executable = () -> service.methodThatShouldThrow();
assertThrows(ExpectedException.class, executable,
() -> "方法应抛出ExpectedException");
}
@Test
void testCollection() {
List<String> result = service.getItems();
assertAll("集合验证",
() -> assertFalse(result.isEmpty(), "集合不应为空"),
() -> assertTrue(result.contains("expected"), "应包含'expected'"),
() -> assertEquals(3, result.size(), "集合大小应为3")
);
}
注意事项
- 保持简洁:Lambda表达式应保持简短,复杂逻辑应提取到单独方法中。
- 合理命名:对于复杂的断言逻辑,可以使用方法引用或命名Lambda。
- 避免副作用:Lambda中不应修改测试状态。
- 平衡可读性:不要过度使用Lambda,有时传统写法更易读。
动态测试(DynamicTest)
适用场景
- 测试数据需要从外部源动态获取时
- 需要测试多种参数组合时
- 测试用例需要根据环境条件动态生成时
- 需要对大量相似但不同的对象进行测试时
- 需要构建复杂测试层次结构时
使用示例
运行时数据驱动的测试
@TestFactory
Stream<DynamicTest> databaseRecordTests() {
// 从数据库动态获取测试数据
List<Product> products = productRepository.findAllActive();
return products.stream()
.map(product -> dynamicTest(
"测试产品ID: "product.getId(),
() -> {
assertNotNull(product.getName());
assertTrue(product.getPrice() > 0);
assertFalse(product.isExpired());
}
));
}
多维度组合测试
@TestFactory
Stream<DynamicTest> crossBrowserTests() {
List<String> browsers = Arrays.asList("Chrome", "Firefox", "Safari");
List<String> osList = Arrays.asList("Windows", "macOS", "Linux");
List<String> resolutions = Arrays.asList("1920x1080", "1366x768", "800x600");
return browsers.stream()
.flatMap(browser -> osList.stream()
.flatMap(os -> resolutions.stream()
.map(res -> dynamicTest(
browser + " on "os + " @"res,
() -> testCompatibility(browser, os, res)
))
)
);
}
条件测试生成
@TestFactory
Stream<DynamicTest> environmentSpecificTests() {
Stream.Builder<DynamicTest> builder = Stream.builder();
// 基础测试(所有环境都执行)
builder.add(dynamicTest("基本功能测试", this::testBasicFunctionality));
// 仅预发布环境执行的测试
if ("pre".equals(System.getenv("APP_ENV"))) {
builder.add(dynamicTest("预发布环境专有测试", this::testProductionFeature));
}
// 当有GPU时才执行的测试
if (GraphicsEnvironment.isHeadless()) {
builder.add(dynamicTest("GPU加速测试", this::testGpuAcceleration));
}
return builder.build();
}
性能基准测试/渐进式测试
@TestFactory
Stream<DynamicTest> performanceBenchmarks() {
intloadLevels = {100, 1000, 5000, 10000};
return Arrays.stream(loadLevels)
.mapToObj(load -> dynamicTest(
load + "并发请求负载测试",
() -> {
long startTime = System.nanoTime();
simulateLoad(load);
long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
assertTrue(duration < getThresholdFor(load),
() -> String.format("%d请求耗时%dms超过阈值", load, duration));
}
));
}
动态测试容器
@TestFactory
Stream<DynamicNode> hierarchicalAPITests() {
return Stream.of(
DynamicContainer.dynamicContainer("用户API",
Stream.of(
dynamicTest("创建用户", this::testUserCreation),
dynamicTest("查询用户", this::testUserQuery)
)),
DynamicContainer.dynamicContainer("订单API",
Stream.of(
dynamicTest("创建订单", this::testOrderCreation),
DynamicContainer.dynamicContainer("支付流程",
Stream.of(
dynamicTest("支付请求", this::testPaymentRequest),
dynamicTest("支付回调", this::testPaymentCallback)
)
)
))
);
}
条件过滤测试
@TestFactory
Stream<DynamicTest> employeeWorkflowTests() {
// 测试数据准备
List<Employee> employees = Arrays.asList(
new Employee(1, "Fred"), // 有firstName的员工
new Employee(2), // 无firstName的员工(测试边界条件)
new Employee(3, "John") // 有firstName的员工
);
EmployeeService service = new EmployeeService();
// 测试流1:测试基础保存功能(所有员工)
Stream<DynamicTest> basicSaveTests = employees.stream()
.map(emp -> dynamicTest(
"基础保存-员工ID: "emp.getId(),
() -> {
Employee saved = service.save(emp.getId());
assertEquals(emp.getId(), saved.getId());
}
));
// 测试流2:测试带名称的保存功能(仅过滤有firstName的员工)
Stream<DynamicTest> namedSaveTests = employees.stream()
.filter(emp -> emp.getFirstName() != null && !emp.getFirstName().isEmpty())
.map(emp -> dynamicTest(
"带名称保存-"emp.getFirstName(),
() -> {
Employee saved = service.save(emp.getId(), emp.getFirstName());
assertEquals(emp.getId(), saved.getId());
assertEquals(emp.getFirstName(), saved.getFirstName());
}
));
// 合并两个测试流
return Stream.concat(basicSaveTests, namedSaveTests);
}
小结
- 适用于需要针对同一数据集执行不同验证规则的场景
- 特别适合验证多版本API的兼容性
- 可扩展用于权限分级测试
注意事项
工厂方法声明
- 注解要求:必须使用@TestFactory明确标记方法,以指示该方法为动态测试工厂
- 方法签名:与静态测试方法不同,工厂方法必须声明返回测试实例集合
返回类型限制
- 合法返回类型:仅允许以下集合类型:
Stream<DynamicTest> // 最常用的流式处理
Collection<DynamicTest> // 基础集合类型
Iterable<DynamicTest> // 可迭代接口
Iterator<DynamicTest> // 迭代器实现
- 类型安全:JUnit 5会在运行时验证返回类型,非法类型将抛出JUnitException
方法修饰符约束
- 禁止修饰符private和 static 。换句话说,方法必须能够访问测试类实例状态(非static)且对框架可见(非private)
生命周期回调差异
- 不支持的回调:
@BeforeEach // 每个动态测试前不会执行
@AfterEach // 每个动态测试后不会执行
- 替代方案:需在动态测试内部自行管理初始化和清理逻辑,例如:
dynamicTest("自定义生命周期", () -> {
// 初始化代码
try {
// 测试逻辑
} finally {
// 清理代码
}
})
与静态测试的关键区别
特性
|
动态测试
|
静态测试 (@Test)
|
生成时机
|
运行时动态生成
|
编译时静态定义
|
用例独立性
|
每个测试完全独立
|
共享相同测试方法体
|
组织结构
|
支持嵌套容器复杂结构
|
仅支持平面结构
|
生命周期支持
|
无自动回调
|
完整生命周期支持
|
数据驱动方式
|
完全自由的数据生成
|
通过参数化注解限定
|
总结
- @DisplayName 使测试报告更加可读,便于团队沟通和维护
- @Nested 帮助组织复杂测试场景,呈现清晰的测试结构
- @RepeatedTest 简化幂等性和稳定性测试
- 参数化测试 减少重复代码,提高测试覆盖率
- 条件测试 使测试更加灵活,适应不同环境需求
- Lambda表达式 使断言更加简洁和高效
- 动态测试赋予了更高的灵活性
后记
- JUnit 的官方文档非常全面,可以当做字典来用:https://junit.org/junit5/docs/5.1.0/user-guide/。
- JUnit(包括4和5)本身是一个非常优秀的框架,大家有精力可以看看源码。比如JUnit 使用了多种经典设计模式,如工厂模式、装饰器模式、组合模式、职责链模式、模板方法模式、观察者模式等,核心代码量不大但功能完备,且扩展性很强。
- 有兴趣的读者,可以去学习一下 TDD 测试驱动开发的理念,这种开发模式与现有的模式正好是反过来的,可以有效提升代码质量(但有一定的学习成本,推行难度也较大)。
用好 JUnit 5 的高级特性:提升单测效率和质量的更多相关文章
- Visual Studio 2015 速递(4)——高级特性之移动开发
系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...
- mysql笔记04 MySQL高级特性
MySQL高级特性 1. 分区表:分区表是一种粗粒度的.简易的索引策略,适用于大数据量的过滤场景.最适合的场景是,在没有合适的索引时,对几个分区进行全表扫描,或者是只有一个分区和索引是热点,而且这个分 ...
- (升级版)Spark从入门到精通(Scala编程、案例实战、高级特性、Spark内核源码剖析、Hadoop高端)
本课程主要讲解目前大数据领域最热门.最火爆.最有前景的技术——Spark.在本课程中,会从浅入深,基于大量案例实战,深度剖析和讲解Spark,并且会包含完全从企业真实复杂业务需求中抽取出的案例实战.课 ...
- Spark Streaming高级特性在NDCG计算实践
从storm到spark streaming,再到flink,流式计算得到长足发展, 依托于spark平台的spark streaming走出了一条自己的路,其借鉴了spark批处理架构,通过批处理方 ...
- Python进阶:全面解读高级特性之切片!
导读:切片系列文章连续写了三篇,本文是对它们做的汇总.为什么要把序列文章合并呢?在此说明一下,本文绝不是简单地将它们做了合并,主要是修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔 ...
- Redis基础、高级特性与性能调优
本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导. ...
- Redis 基础、高级特性与性能调优
本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导. ...
- Visual Studio 2015速递(4)——高级特性之移动开发
系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...
- jvm高级特性(6)(线程的种类,调度,状态,安全程度,实现安全的方法,同步种类,锁优化,锁种类)
JVM高级特性与实践(十三):线程实现 与 Java线程调度 JVM高级特性与实践(十四):线程安全 与 锁优化 一. 线程的实现 线程其实是比进程更轻量级的调度执行单位. 线程的引入,可以把一个检查 ...
- jvm高级特性(5)(1)(原子性,可见性,有序性,volatile,概述)
JVM高级特性与实践(十二):高效并发时的内外存交互.三大特征(原子.可见.有序性) 与 volatile型变量特殊规则 简介: 阿姆达尔定律(Amdahl):该定律通过系统中并行化与串行化的比重来描 ...
随机推荐
- 【Guava工具类】Strings&Ints
String相关工具 Strings Guava 提供了一系列用于字符串处理的工具: 对字符串为null或空的处理 nullToEmpty(@Nullable String string):如果非空, ...
- String常见面试题
第一题:打印的结果是true还是false呢? 在之前我们就说过这题,执行s1时,检查字符串常量池,发现没有"abc",于是创建"abc",执行s2时,接着检查 ...
- 【Web】Servlet三大作用域、JSP四大作用域
request 生命周期: 创建:客户端向服务器发送一次请求,服务器就会创建request对象. 销毁:服务器对这次请求作出响应后就会销毁request对象. 有效:仅在当前请求中有效. 作用:常用于 ...
- Git 查看修改历史
# 查看某个文件的 commit 历史日志 1. git log filename # 查看每次提交的diff 2. git log -p filename # git show abe69804bb ...
- 调用 restful的api的方法
var // myurl : string; tmpstr : String;// string; RespData :TStringStream; sendData : TStringList; j ...
- Modernize DevOps
https://www.harness.io/ Continuous Delivery and gitops: while CD automates application deployment, G ...
- python调用QQ机器人向指定QQ发消息
暂时没想到这个能用来干什么,只是刚好看到相关文章,学习一下,就拿获取基金信息来做试验把 爬取基金的信息就不介绍了,请参考https://www.cnblogs.com/becks/p/14500495 ...
- FreeSWITCH中SIP网关(Gateway)操作
freeswitch是一款简单好用的VOIP开源软交换平台. 以下是一篇关于FreeSWITCH中SIP网关(Gateway)操作的技术指南,基于提供的官方文档内容整理: 一.网关生命周期管理 1. ...
- Sentinel源码—6.熔断降级和数据统计的实现
大纲 1.DegradeSlot实现熔断降级的原理与源码 2.Sentinel数据指标统计的滑动窗口算法 1.DegradeSlot实现熔断降级的原理与源码 (1)熔断降级规则DegradeRule的 ...
- 揭秘AI自动化框架Browser-use(四):Browser-use记忆模块技术解析
一.从一次失败的景点采集说起 在 AI 自动化任务中,记忆模块是实现复杂任务处理的关键组件.Browser-use 项目通过引入记忆模块,解决了 LLM 在连续性任务中的无状态性问题,使代理能够维持上 ...