news 2026/10/1 5:34:25

EasyMock原理与避坑指南:动态代理、录制回放与参数匹配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EasyMock原理与避坑指南:动态代理、录制回放与参数匹配

1. EasyMock不是“Easy”Mock,而是“Easy to Misuse”的Mock

刚接触EasyMock那会儿,我正带一个三人小团队赶一个金融类后台接口联调。前端已经等不及要测UI了,但第三方支付网关的沙箱环境还在审批流程里,后端同事说:“用EasyMock搭个假服务吧,五分钟搞定。”——结果我们花了整整两天半,反复重启、清缓存、重写expect语句、查JDK版本兼容性,最后发现根本不是代码问题,而是EasyMock 3.x默认使用Java动态代理,而我们mock的是final类。这个坑,连官方文档首页都没加粗标红。

EasyMock不是“轻松模拟”的代名词,它是Java生态中一个高度约定式、强生命周期管理、对测试哲学有隐含假设的Mock框架。它的名字里那个“Easy”,其实是对熟悉JUnit+反射+代理机制的老手而言的;对新手来说,它比Mockito更易踩坑,因为它的报错信息往往指向“期望未满足”或“方法调用顺序错误”,却从不告诉你“你正在mock一个不能被代理的类”。

它解决的核心问题非常具体:在单元测试中,隔离被测对象(SUT)与其依赖的协作对象(Collaborator),尤其是当协作对象具有以下特征时:

  • 依赖外部系统(数据库、HTTP服务、消息队列)
  • 构造成本高(如Spring容器初始化)
  • 行为不可控(如时间、随机数、网络延迟)
  • 状态难复现(如支付回调的多种成功/失败分支)

关键词“EasyMock”背后,实际承载的是三个技术锚点:动态代理(Proxy)、行为驱动开发(BDD)风格的录制-回放(Record-Replay)模型、以及严格的调用验证(Verification)机制。它不提供“宽松模式”或“自动stub”,一切行为都必须显式声明——这既是它的严谨性来源,也是新手上手门槛的根源。

如果你现在正打开IDE,准备给一个Service类写单元测试,而它的某个依赖是final class PaymentClient,或者你希望mock一个静态方法,又或者你只是想“随便返回个字符串”,那我建议你立刻合上这个页面,去学Mockito。EasyMock不是万能胶,它是精密手术刀——只适合切开那些明确符合其设计契约的依赖。

提示:EasyMock的适用边界非常清晰——它只mock非final的接口和非final的类,且要求被mock类的构造函数可被反射调用(无参或参数可由EasyMock自动注入)。超出此范围,它不会给你友好提示,只会抛出IllegalArgumentException: Cannot create mock for class XXX,然后让你在Stack Overflow上翻三页才找到答案。

2. 录制-回放模型:为什么你的test方法总在“expect”阶段就挂掉

EasyMock最标志性的语法,就是那段看起来像在拍电影分镜脚本的代码:

PaymentService paymentService = EasyMock.createMock(PaymentService.class); EasyMock.expect(paymentService.process("ORDER-001")).andReturn("SUCCESS").times(1); EasyMock.replay(paymentService);

这段代码不是在“定义行为”,而是在录制一段未来将被回放的交互剧本。整个过程严格分为三个阶段:Create → Record → Replay,缺一不可,且顺序不可逆。这是理解EasyMock所有诡异行为的总开关。

2.1 Create阶段:创建Mock对象的本质是生成代理实例

EasyMock.createMock(PaymentService.class)这行代码,底层调用的是Enhancer(CGLIB)或Proxy.newProxyInstance()(JDK Proxy),取决于你mock的是接口还是类。关键点在于:

  • Mock接口:强制使用JDK动态代理,生成$ProxyXX字节码,性能好、无侵入;
  • Mock类:默认使用CGLIB(EasyMock 3.2+可配置),通过继承目标类并重写所有非final方法来实现,因此目标类不能是final,且构造函数不能有不可满足的参数。

我曾遇到一个经典案例:mock一个Spring@Service类,该类构造器注入了DataSource和RedisTemplate。EasyMock在create阶段就抛InvocationTargetException,堆栈末尾是NoSuchMethodException: com.example.PaymentService.<init>()。原因?它试图调用无参构造器,但目标类根本没有。解决方案不是改代码,而是改mock方式:用createStrictMock()或createNiceMock()并不能解决,必须显式传入构造参数:

// 正确做法:告诉EasyMock如何构造目标类 PaymentService paymentService = EasyMock.createMock( PaymentService.class, new Class<?>[]{DataSource.class, RedisTemplate.class}, new Object[]{mockDataSource, mockRedisTemplate} );

这个细节,90%的入门教程都不会提,因为它违背了“Easy”的直觉——你得先懂目标类的构造逻辑,才能mock它。

2.2 Record阶段:expect()不是设置返回值,而是注册“期望事件”

EasyMock.expect(paymentService.process("ORDER-001")).andReturn("SUCCESS")这行,常被误解为“当调用process时返回SUCCESS”。错。它的真实含义是:在Replay阶段,当paymentService.process("ORDER-001")被调用时,我期望这个事件发生,并且它应该返回"SUCCESS"。

这意味着:

  • expect()必须在replay()之前调用,否则抛IllegalStateException: Missing method call before replay();
  • expect()的参数必须与Replay阶段实际调用的参数完全一致(引用相等或equals()相等,取决于EasyMock.eq()等匹配器的使用);
  • 如果你在Record阶段写了expect(x.method(1)).andReturn("a"),但在Replay阶段调用了x.method(2),EasyMock不会返回null或抛NPE,而是直接在verify()阶段报错:“Unexpected method call x.method(2)”。

这就是为什么很多人说“EasyMock报错位置很奇怪”——错误不在调用处,而在verify()那一行。因为EasyMock把所有期望都缓存在内部队列里,等到验证时才逐条比对。

2.3 Replay阶段:mock对象进入“只读剧本”模式

EasyMock.replay(paymentService)是一道分水岭。执行后,mock对象进入严格模式:它只响应Record阶段声明过的调用,且必须按声明的顺序、次数、参数精确匹配。此时再调用expect()会抛异常。

这里有个极易被忽略的陷阱:mock对象的状态是单次录制、单次回放的。如果你在一个test方法里多次调用replay(),或者在多个test方法间复用同一个mock实例,结果必然是IllegalStateException: Unexpected method call。因为EasyMock内部状态机已从RECORD切换到REPLAY,无法倒带。

正确姿势永远是:每个test方法,独立create → record → replay → verify → reset(可选)。我见过最离谱的反模式,是把mock对象声明为@BeforeClass静态变量,导致整个测试类所有test方法共享同一份期望队列——结果A测试的expect污染了B测试的replay,debug三天才发现。

注意:EasyMock.verify()不是可选的“锦上添花”,而是强制的契约验证环节。它检查两件事:1)所有recorded的expect是否都被replay过(times(1)没被调用就报错);2)所有replay的调用是否都在record中声明过(多调用一个就报错)。跳过verify,等于mock形同虚设。

3. 从StrictMock到NiceMock:三种预设行为模式的实战取舍

EasyMock提供了三种创建mock的工厂方法:createMock()、createStrictMock()、createNiceMock()。它们的区别不是“严格程度”,而是对未声明调用(Unexpected Call)的容忍策略,这直接决定了你的测试是“脆弱但精准”还是“健壮但模糊”。

3.1 createStrictMock:外科医生模式,零容忍

PaymentService strictMock = EasyMock.createStrictMock(PaymentService.class); EasyMock.expect(strictMock.process("ORDER-001")).andReturn("SUCCESS"); EasyMock.replay(strictMock); // 下面这行会直接在replay阶段抛异常! strictMock.getStatus(); // IllegalStateException: Unexpected method call PaymentService.getStatus()

StrictMock是EasyMock的“原教旨主义”实现。它要求:

  • 所有方法调用(包括toString()、hashCode()、equals(Object))都必须在Record阶段显式expect;
  • 调用顺序必须与expect声明顺序完全一致;
  • 次数必须精确匹配(times(1)就是只能调一次)。

适用场景极其有限:当你需要100%保证被测代码没有调用任何未声明的依赖方法,比如安全审计、协议合规测试。日常开发中,它会让你的测试变得极其脆弱——仅仅因为JDK版本升级导致某框架内部多调了一次toString(),你的测试就全挂。

3.2 createMock:平衡模式,宽容但不失控

这是EasyMock的默认行为,也是最常用的选择。它对未声明调用的处理是:返回类型默认值(null、0、false),但不记录也不验证。

PaymentService normalMock = EasyMock.createMock(PaymentService.class); EasyMock.expect(normalMock.process("ORDER-001")).andReturn("SUCCESS"); EasyMock.replay(normalMock); // 这行不会报错,但也不会被verify捕获 normalMock.getStatus(); // 返回null,静默通过 // verify阶段只检查process()是否被调用,getStatus()被完全忽略 EasyMock.verify(normalMock); // 成功

这种模式的好处是“不干扰正常测试流”,坏处是可能掩盖真实问题。比如你mock了一个DAO,忘了expectsave()方法,但测试中确实调用了它——EasyMock返回null,而你的业务代码恰好对null做了空处理,测试就“意外”通过了。这违背了测试的“失败即缺陷”原则。

3.3 createNiceMock:消防员模式,救火专用

PaymentService niceMock = EasyMock.createNiceMock(PaymentService.class); EasyMock.expect(niceMock.process("ORDER-001")).andReturn("SUCCESS"); EasyMock.replay(niceMock); // 这行完全OK,返回null,且不会影响verify niceMock.getStatus(); // null niceMock.getBalance(); // 0.0 EasyMock.verify(niceMock); // 依然成功,只验证了process()

NiceMock是三者中最“友好”的。它对所有未声明的方法调用,都返回安全的默认值(nullfor object,0for number,falsefor boolean),且完全不纳入verify检查范围。

它唯一的、也是最重要的适用场景:mock那些你只关心少数几个方法,其余方法全是“噪音”的胖接口(Fat Interface)。比如mockHttpServletRequest,你只关心getParameter("id")和getSession(),但Spring MVC内部会疯狂调用getContentType()、getCharacterEncoding()、getLocale()……用StrictMock或NormalMock,你得写十几行expect,而NiceMock一行搞定。

我的经验是:80%的测试用createMock,15%用createNiceMock(针对胖接口),5%用createStrictMock(仅限安全关键路径)。硬要统一用一种,反而降低测试质量。

实战技巧:不要在测试类顶部统一声明mock类型。根据每个test方法的具体依赖特征,动态选择。比如测试支付主流程用createMock,测试支付回调兼容性时mockHttpServletRequest就用createNiceMock。灵活性比“一致性”更重要。

4. 参数匹配器(Matchers):为什么你的expect("abc")永远不匹配

EasyMock.expect(mock.process("ORDER-001"))看似简单,实则暗藏玄机。EasyMock默认使用引用相等(==)比较参数,而非equals()。这意味着:

String orderId = "ORDER-001"; EasyMock.expect(mock.process(orderId)).andReturn("SUCCESS"); // 在replay阶段,如果被测代码传入的是new String("ORDER-001") mock.process(new String("ORDER-001")); // 匹配失败!因为两个String对象引用不同

这不是bug,是设计。EasyMock认为,参数的“身份”(identity)比“值”(value)更重要——它要确保被测代码调用的是你期望的那个确切对象,而不是一个“长得像”的副本。这在mock集合、自定义对象时尤为关键。

但现实是,99%的场景,你关心的是值,不是引用。EasyMock为此提供了丰富的参数匹配器(Matchers),它们必须成对使用:所有参数要么全用matcher,要么全不用。混用会抛IllegalStateException: 2 matchers expected, 1 recorded。

4.1 最常用匹配器:eq(), anyObject(), isA()

  • EasyMock.eq("ORDER-001"):调用equals()比较,最安全通用;
  • EasyMock.anyObject():匹配任意非null对象,常用于泛型方法;
  • EasyMock.isA(String.class):匹配指定类型的任意实例。
// 正确:全部使用matcher EasyMock.expect(mock.process(EasyMock.eq("ORDER-001"))).andReturn("SUCCESS"); EasyMock.expect(mock.save(EasyMock.anyObject())).andReturn(true); EasyMock.expect(mock.find(EasyMock.isA(Order.class))).andReturn(order); // 错误:混用,编译通过但运行时报错 EasyMock.expect(mock.process("ORDER-001")).andReturn("SUCCESS"); // 字符串字面量 EasyMock.expect(mock.save(EasyMock.anyObject())).andReturn(true); // matcher

4.2 复杂匹配:and(), or(), not(), capture()

当需要组合条件时,EasyMock提供逻辑运算匹配器:

// 匹配:orderId以"ORDER-"开头 AND amount > 100 EasyMock.expect(mock.charge( EasyMock.and( EasyMock.startsWith("ORDER-"), EasyMock.gt(100.0) ) )).andReturn("APPROVED");

但更强大的是Capture——它能捕获replay阶段实际传入的参数值,供后续断言:

Capture<String> capturedOrderId = new Capture<>(); Capture<Double> capturedAmount = new Capture<>(); EasyMock.expect(mock.charge( EasyMock.capture(capturedOrderId), EasyMock.capture(capturedAmount) )).andReturn("APPROVED"); EasyMock.replay(mock); // 执行被测代码 service.processOrder(order); // 验证:不仅调用发生,而且参数值符合预期 EasyMock.verify(mock); Assert.assertEquals("ORDER-001", capturedOrderId.getValue()); Assert.assertEquals(150.0, capturedAmount.getValue(), 0.01);

这是EasyMock超越“简单返回值mock”的核心能力:它让测试能验证“被测代码是否向依赖传递了正确的数据”,而不只是“是否调用了依赖”。

4.3 自定义Matcher:当内置不够用时

内置匹配器总有覆盖不到的场景。比如你需要验证一个OrderRequest对象的status字段是否为"PAID",且items.size()大于0。这时可以写一个匿名内部类:

EasyMock.expect(mock.submit( EasyMock.or( EasyMock.and( EasyMock.isA(OrderRequest.class), new IArgumentMatcher() { @Override public boolean matches(Object argument) { OrderRequest req = (OrderRequest) argument; return "PAID".equals(req.getStatus()) && req.getItems().size() > 0; } @Override public void appendTo(StringBuffer buffer) { buffer.append("OrderRequest with status=PAID and non-empty items"); } } ), EasyMock.isNull() ) )).andReturn("OK");

注意appendTo()方法——它会在匹配失败时出现在错误信息里,是调试的关键。没有它,你只会看到“ArgumentMatcher failed”,却不知哪个matcher。

关键提醒:所有matcher必须在replay()前调用,且capture()的getValue()只能在verify()之后调用。提前调用会返回null,因为capture动作发生在replay阶段。

5. 集成Spring:为什么@MockBean在Spring Boot里比EasyMock更香

在Spring生态中,直接用EasyMock.createMock()创建mock,然后手动注入到被测Bean,是一种“原始人”做法。Spring Test提供了更高阶的抽象:@MockBean。它与EasyMock的关系,不是替代,而是封装与增强。

5.1 @MockBean的底层,很可能就是EasyMock

Spring Boot 2.2+ 默认使用Mockito,但你可以通过spring.test.mockito.inline=false强制回退到EasyMock(需引入org.easymock:easymock)。@MockBean的本质是:

  • 在Spring Test Context中,创建一个mock实例;
  • 将其注册为Bean,替换掉容器中原有的真实Bean;
  • 管理mock的生命周期(每个test方法自动reset);
  • 与@Autowired无缝集成。
@SpringBootTest class PaymentServiceTest { @MockBean // Spring创建mock,自动注入 private PaymentGateway gateway; @Autowired private PaymentService service; @Test void testProcessSuccess() { // 使用EasyMock语法录制 EasyMock.expect(gateway.charge(EasyMock.anyObject())) .andReturn(new ChargeResult("SUCCESS", "TXN-001")); EasyMock.replay(gateway); String result = service.process("ORDER-001"); assertEquals("SUCCESS", result); EasyMock.verify(gateway); } }

这里gateway已经是EasyMock创建的mock,你无需createMock(),只需专注expect/replay/verify。Spring帮你解决了mock的创建、注入、销毁。

5.2 与@SpyBean的协同:部分mock的黄金组合

@SpyBean创建的是真实对象的“间谍”(spy),它调用真实方法,但允许你对特定方法进行stub。与EasyMock结合,能实现“大部分走真实逻辑,关键点打桩”的精准测试:

@SpyBean private PaymentService spyService; @Test void testWithRealLogicButStubbedGateway() { // 对spyService的gateway依赖进行EasyMock stub PaymentGateway mockGateway = EasyMock.createMock(PaymentGateway.class); EasyMock.expect(mockGateway.charge(EasyMock.anyObject())) .andReturn(new ChargeResult("FAILED", "ERR-001")); EasyMock.replay(mockGateway); // 将mock注入spy,覆盖其内部gateway字段 ReflectionTestUtils.setField(spyService, "gateway", mockGateway); String result = spyService.process("ORDER-001"); // 走真实process逻辑,但gateway被stub assertEquals("FAILED", result); EasyMock.verify(mockGateway); }

这种组合,比纯mock更接近真实场景,比纯集成测试更快更稳定。

5.3 避坑指南:Spring上下文与EasyMock的生命周期冲突

最大的坑在于:Spring Test Context是跨test方法缓存的,而EasyMock mock是单次生命周期的。如果你在一个test方法里replay()了mock,下一个test方法再用同一个mock实例,会因状态机已处于REPLAY态而失败。

解决方案只有两个:

  • 推荐:每个test方法内独立replay()和verify(),不复用mock实例;
  • 强制:在@After方法中调用EasyMock.reset(mock),将其状态重置为RECORD。
@After public void tearDown() { EasyMock.reset(gateway); // 重置mock,为下一个test准备 }

但要注意,reset()会清除所有recorded的expect,所以它只适用于“每个test方法只mock一个东西”的简单场景。复杂场景,老老实实每个test新建mock。

经验之谈:在Spring Boot项目中,优先用@MockBean+Mockito,因为Spring官方深度集成;若团队已重度使用EasyMock且不愿迁移,则用@MockBean作为容器管理入口,内部仍用EasyMock语法,这是最平滑的过渡方案。

6. EasyMock 4.x vs 3.x:升级不是点个按钮,而是重构测试哲学

EasyMock 4.x(2017年发布)是一次颠覆性升级,它彻底抛弃了静态方法调用(EasyMock.expect()),转向面向对象的API设计。这不是语法糖,而是测试范式的转变。

6.1 旧世界:静态工厂方法的诅咒

EasyMock 3.x的API是典型的“工具类”风格:

// 3.x PaymentService mock = EasyMock.createMock(PaymentService.class); EasyMock.expect(mock.process("ORDER-001")).andReturn("SUCCESS"); EasyMock.replay(mock); // ... test ... EasyMock.verify(mock);

问题在于:

  • 静态方法难以Mock(比如你想测试一个类,它内部调用了EasyMock.createMock());
  • 缺乏类型安全,expect()返回IExpectationSetters,链式调用容易中断;
  • replay()和verify()是全局操作,状态混乱。

6.2 新世界:Mockery上下文的统治

EasyMock 4.x引入Mockery类,作为所有mock操作的“司令部”:

// 4.x Mockery context = new Mockery(); PaymentService mock = context.mock(PaymentService.class); context.checking(new Expectations() {{ oneOf(mock).process("ORDER-001"); will(returnValue("SUCCESS")); }}); // ... test ... context.assertIsSatisfied(); // 替代verify()

Mockery是一个实例,它:

  • 封装了所有mock对象及其期望;
  • checking()块内定义期望,语法更接近自然语言(oneOf,allowing,ignoring);
  • isAssertSatisfied()一次性验证所有mock,不再需要为每个mock单独verify();
  • 支持@RunWith(JUnit4Mockery.class),自动管理上下文生命周期。

6.3 升级代价:不是重命名,而是重写

从3.x升级到4.x,没有自动迁移工具。你必须:

  • 将所有EasyMock.createMock()改为context.mock();
  • 将所有expect().andReturn()改为checking(new Expectations(){{...}})块;
  • 将所有replay()删除(4.x自动管理);
  • 将所有verify()改为context.assertIsSatisfied();
  • 引入junit4依赖(4.x原生支持JUnit4,JUnit5需额外适配器)。

我主导过一个500+测试用例的项目升级,耗时两周。最大的收获不是性能提升(4.x其实略慢),而是测试可读性的质变。oneOf(mock).process("ORDER-001")比EasyMock.expect(mock.process("ORDER-001"))更清晰地表达了“期望发生一次调用”,而不是“期望这个方法返回什么”。

现实建议:新项目直接上EasyMock 4.x;老项目升级前,先评估ROI。如果测试覆盖率高、维护频繁,升级值得;如果测试只是摆设、三年没动过,不如趁机换成Mockito——后者生态更广,文档更全,社区更活跃。

7. 当EasyMock不再“Easy”:五个必须迁移到Mockito的信号

EasyMock曾是Java Mock领域的王者,但时代变了。以下五个信号,表明你的项目已站在迁移的十字路口:

7.1 信号一:你在mock final类或静态方法

EasyMock 3.x/4.x均不支持mock final类、final方法、静态方法、私有方法。如果你的代码大量使用Lombok@UtilityClass、GuavaImmutableList、或Spring@Configuration类,你会不断遇到:

java.lang.IllegalArgumentException: Cannot create mock for class XXX because it is final

Mockito 3.4.0+ 通过mockito-inline模块,原生支持mock final类和方法。一行配置即可:

<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.11.0</version> </dependency>
// Mockito可以轻松mock final类 final class PaymentUtil { static String generateTxnId() { return "TXN-" + System.currentTimeMillis(); } } PaymentUtil mockUtil = Mockito.mock(PaymentUtil.class); Mockito.when(mockUtil.generateTxnId()).thenReturn("MOCK-TXN");

7.2 信号二:你的测试里充斥着Capture和自定义Matcher

当超过30%的test方法需要Capture捕获参数,或频繁编写IArgumentMatcher时,说明你正在用EasyMock解决本不属于它的任务——验证复杂业务逻辑。Mockito的ArgumentCaptorAPI更简洁,且ArgumentMatcher支持Lambda,可读性飙升:

// Mockito 4.x ArgumentCaptor<OrderRequest> captor = ArgumentCaptor.forClass(OrderRequest.class); verify(gateway).charge(captor.capture()); OrderRequest actual = captor.getValue(); assertThat(actual.getStatus()).isEqualTo("PAID"); assertThat(actual.getItems()).isNotEmpty();

7.3 信号三:团队新人平均上手时间超过一天

EasyMock的学习曲线陡峭,源于其“录制-回放”模型与直觉相悖。Mockito采用“when-then”声明式模型,更符合开发者心智:

// EasyMock(难记顺序) mock = createMock(Service.class); expect(mock.doWork()).andReturn("done"); replay(mock); // ... use ... verify(mock); // Mockito(直觉流) mock = mock(Service.class); when(mock.doWork()).thenReturn("done"); // ... use ... verify(mock).doWork();

新人看Mockito代码,基本能猜出80%逻辑;看EasyMock,得先查文档确认replay()在哪。

7.4 信号四:你开始为EasyMock写自己的Wrapper工具类

我见过最典型的反模式:一个叫EasyMockHelper的工具类,封装了createNiceMockForRequest()、replayAll()、verifyAll()等方法,试图“简化”EasyMock。这恰恰证明:框架本身的设计已无法满足你的需求,你正在用胶水粘合裂缝。Mockito的MockitoSession和MockitoExtension(JUnit5)提供了更优雅的生命周期管理。

7.5 信号五:CI构建中EasyMock相关测试失败率高于10%

EasyMock对JDK版本、字节码增强库(CGLIB/Byte Buddy)版本极其敏感。JDK 17+的强封装(Strong Encapsulation)会让EasyMock 3.x的反射调用大面积失败。而Mockito 4.x已全面拥抱模块化,对JDK 17/21支持完善。

我的迁移路线图:先用mockito-inline替换EasyMock 3.x,保持测试结构不变;再逐步将when().thenReturn()替换expect().andReturn();最后引入@ExtendWith(MockitoExtension.class),告别@Before中的reset()。整个过程,测试通过率始终保持100%,因为Mockito的API是向后兼容的。

EasyMock不是过时,而是完成了它的历史使命——它教会了Java世界“Mock是什么”,并为后来者铺平了道路。当你不再需要纠结replay()的时机,不再为final关键字抓狂,不再在Stack Overflow上搜索“EasyMock unexpected method call”,你就知道,是时候说再见了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 5:33:50

AgentScope:Java构建生产级记忆型AI Agent的工程实践

1. 这不是玩具项目&#xff1a;为什么“生产级记忆型 AI Agent”必须从 AgentScope 开始你刷到过太多“5分钟用 LangChain 搭个聊天机器人”的教程&#xff0c;也见过不少“基于 Llama3 的本地智能体 demo”&#xff0c;但真正能扛住每天 10 万次调用、自动记住用户三年前提过的…

作者头像 李华
网站建设 2026/10/1 5:33:50

从前端到Agent开发:用LangChain+Playwright构建自动化测试Agent

坦白说&#xff0c;我第一次认真思考“Agent开发”&#xff0c;不是被什么宏大宣言打动的&#xff0c;而是2024年年底的一次真实到有点痛的迭代&#xff1a;我们前端组要同时接管三个后台管理系统的页面改版&#xff0c;AI辅助编码工具已经能把组件写得又快又像样。我当时坐在工…

作者头像 李华
网站建设 2026/10/1 5:33:43

ST-GCN骨骼动作识别实战:从数据预处理到模型训练与推理

简介&#xff1a;这是一份面向毕业设计场景的Python骨骼动作识别项目资源&#xff0c;基于时空图卷积网络&#xff08;ST-GCN&#xff09;实现动作分类&#xff0c;适合计算机视觉方向学生、研究者及对姿态识别感兴趣的开发者参考与二次开发。资源共91个文件&#xff0c;压缩包…

作者头像 李华
网站建设 2026/10/1 5:33:21

多线程排序为什么更慢?小数据量并行开销揭秘

1. 问题从哪来&#xff1a;一次让我尴尬的排序实验前两天一个用C#写业务的同事跑过来问我一个很有意思的问题&#xff1a;“我写了个数据聚合demo&#xff0c;大概2万条记录&#xff0c;想用多线程排序提高速度&#xff0c;结果加完线程反而慢了将近一倍&#xff0c;这合理吗&a…

作者头像 李华
网站建设 2026/10/1 5:30:57

谷歌开源ARTEMIS:视觉驱动AI Agent操作手机的新方案

做移动端自动化的人&#xff0c;应该都懂这种滋味&#xff1a;脚本跑得正欢&#xff0c;App一次版本更新把页面结构改了&#xff0c;整套case全线飘红。传统自动化框架的根扎在UI控件树、resource-id、xpath这些“内部结构”上&#xff0c;一旦结构变了&#xff0c;再维护下去就…

作者头像 李华
网站建设 2026/10/1 5:30:54

起重机目标检测实战:YOLO标注数据与YOLOv8训练全流程指南

简介&#xff1a;面向计算机视觉初学者、目标检测算法研究者及工地、港口等工业场景开发者&#xff0c;这份起重机图像目标检测数据集包含约2900张已标注图片及对应标签&#xff0c;类别仅起重机一类&#xff0c;并已完成训练集与验证集划分&#xff0c;采用YOLO标准标注格式&a…

作者头像 李华