新闻详情

新闻详情

首页 / 资讯中心 / 详情

JUnit 5进阶实战:从断言到Mockito与Spring整合的自动化测试体系

发布时间:2026/9/29 17:38:51来源:尧图网络
JUnit 5进阶实战:从断言到Mockito与Spring整合的自动化测试体系
写测试这件事我见过太多Java开发者从入门到放弃的过程。很多人刚接触JUnit的时候只会写一个Test注解然后在方法里塞几行System.out.println跑一下发现没报错就称之为测试通过。等真正上了生产项目面对几百个类、几千个方法的存量代码你会发现这样写测试等于给自己挖坑——不是测试在保护你而是你在维护测试。JUnit 5入坑两三年我从最早只会TestAssert.assertEquals到现在能设计一套覆盖单元、集成、接口层的自动化测试体系中间踩过的坑、想通的道理值得好好复盘一下。这篇文章不是API文档的翻译而是我实际写测试时总结出来的进阶路径先搞懂JUnit 5的架构和设计思想再逐层掌握断言、生命周期、参数化、嵌套、Mock、Spring整合这些实战技能最后能建立起一套适合自己项目的测试规范和排查思路。适合谁看如果你已经在用JUnit但写起来总觉得别扭如果你正打算给项目补自动化测试却不知从何下手或者你面试时被问到说说你怎么设计测试就语塞那这篇内容应该能帮你把碎片知识串成体系。1. 从只会写Test到理解JUnit 5的三层架构1.1 JUnit 4升级到JUnit 5到底改了什么很多老Java开发者第一次接触JUnit 5时的反应是不就是加了个DisplayName注解吗——这个认知会害死人。JUnit 5不是JUnit 4的小修小补它是一次彻底的重构核心变化是从一个单体框架变成了三个独立模块的组合JUnit Platform、JUnit Jupiter和JUnit Vintage。打个比方JUnit 4像一个一体式收音机——能播电台就行所有功能焊死在一起。而JUnit 5改成了音响系统Platform是功放和音箱接口Jupiter是CD播放器Vintage是黑胶唱片转盘。你可以只插CD也可以给功放再接一个蓝牙接收器比如TestNG引擎平台本身不关心你用什么播放源只要遵循它定义的TestEngine接口就行。JUnit Platform最底层负责发现和运行测试。它只定义了一组接口比如TestEngine、TestDescriptor、TestPlan本身不包含任何测试注解。JUnit Jupiter我们99%的时间都在跟它打交道。Test、ParameterizedTest、ExtendWith这些都是Jupiter提供的注解它是JUnit 5的标准测试语言。JUnit Vintage用来兼容JUnit 3和JUnit 4老用例的引擎。你的项目里有多少历史遗留测试Vintage就是为了让你不用一次性重写几百个老测试而是平滑过渡。理解这层架构后你再遇到为什么我加了junit-jupiter-api依赖还是跑不起来的问题就能秒懂——你没加Platform的运行时实现光有CD没有功放当然没声音。实际项目中最简单的Gradle依赖写法是testImplementation org.junit.jupiter:junit-jupiter:5.10.0它会帮你把junit-jupiter-api、junit-jupiter-engine和junit-jupiter-params一起带进来。1.2 为什么说扩展模型是JUnit 5最被低估的设计JUnit 4时代你想给测试加个自定义逻辑要么继承Runner要么通过Rule和ClassRule硬塞。这套东西的问题在于钩子太粗——一个Runner管整个类一个Rule管所有用例你没法精细地控制某个特定场景的某个特定阶段。JUnit 5的核心抽象是Extension接口配合ExtendWith注解使用。你可能已经在用Spring的SpringBootTest、Mockito的Mock注解却不知道它们底层都是靠Extension机制跑起来的。SpringExtension负责往测试实例里注入Spring容器管理的BeanMockitoExtension负责扫描Mock注解并生成代理对象。这也解释了为什么JUnit 5能吸引整个生态围绕它做扩展。试试自己写一个简单的Extension想在测试失败时自动截图并附加日志只需要实现TestExecutionExceptionHandler接口在handleTestExecutionException方法里写你的逻辑然后通过ExtendWith挂到测试类上。这种接口机械化、逻辑可插拔的设计远比JUnit 4时代改Runner那种侵入式方案优雅得多。提示新手不需要立刻写自定义Extension但理解这个机制后你用Spring、Mockito、Allure这些框架时就不会有黑魔法感排查问题会轻松很多。2. 断言体系与测试生命周期先把测试写像样2.1 断言不是assert一下那么简单JUnit 4时代的断言实质上是Assert类的静态方法集合。面试时背得滚瓜烂熟的assertEquals(expected, actual)在实际工作中却坑人无数——一旦第一个断言失败后面的代码直接终止你连失败细节都看不全。JUnit 5借鉴了AssertJ的思路带来了三个我几乎天天用的利器assertAll、assertTimeout和assertThrows。用assertAll包装一组断言是提升测试信息量最直接的手段Test void testCreateUser() { User user userService.create(alice, aliceexample.com); // 慢着这不是lambda吗对断言逻辑被延迟执行了 assertAll(user properties, () - assertEquals(alice, user.getName()), () - assertTrue(user.getEmail().endsWith(example.com)), () - assertNotNull(user.getCreatedAt()) ); }三个断言问题会全部报告而不是挂了第一个就沉默。这在排查复杂对象时简直救命——你能一次看到所有字段哪里不对而不是反复改代码、反复跑测试。再说assertTimeout它的价值在于测试性能也是功能的一部分。比如一个缓存服务的查询接口你希望它5毫秒内返回断言就可以写成assertTimeout(Duration.ofMillis(5), () - cache.get(key))。注意assertTimeout会让lambda在同线程执行完如果断言失败会抛异常如果想真正控制超时并提前中断要用assertTimeoutPreemptively但后者有几个坑它在另一个线程执行任务如果代码依赖ThreadLocalSpring的事务上下文就依赖会莫名其妙失败。我的经验是能用assertTimeout就用它只有明确知道任务可安全跨线程时才用assertTimeoutPreemptively。最后是异常断言。JUnit 4里常见的写法是Test(expected Exception.class)——这种方法你根本拿不到异常对象。JUnit 5里这样写Test void testInvalidEmail() { IllegalArgumentException ex assertThrows( IllegalArgumentException.class, () - userService.create(bob, not-an-email) ); // 异常对象在手里你怎么查都行 assertTrue(ex.getMessage().contains(invalid email)); }2.2 生命周期钩子怎么用才不会乱JUnit 4的生命周期注解到了JUnit 5里名字变了BeforeClass变成了BeforeAllBefore变成了BeforeEach而且JUnit 5对你有没有加static这件事非常严格——BeforeAll和AfterAll修饰的方法必须用static除非测试类加上了TestInstance(TestInstance.Lifecycle.PER_CLASS)注解。这就引出了一个关键设计问题每个测试方法默认会new一个测试类实例所以BeforeAll必须静态否则你连这个实例是谁的都说不清。如果你希望测试类只实例化一次比如字段在多个测试间共享、想用非静态的BeforeAll可以设置PER_CLASS但这也意味着你的测试类不再是隔离的字段状态会在测试间互相污染——这是一个权衡。比较推荐的实践是能用BeforeEach就别用BeforeAll因为大多数测试需要的是每个用例都是从干净状态开始的。比如数据库测试BeforeEach里清表、插入默认数据保证每个用例看到的世界是一样的。只有当初始化开销极大比如启动整个Spring容器、建立昂贵的连接池时才用BeforeAll并且要小心并发。我还想提一个容易被忽略的点父类和子类的生命周期方法执行顺序。你有基类BaseTest定义了BeforeEach子类也定义了BeforeEach先执行父类的再执行子类的——这个顺序其实很有用你可以把通用的准备逻辑比如初始化MockMvc放在基类把case特定的逻辑放在子类。但一旦你用Nested后面会讲顺序问题就要重新梳理了。注意BeforeEach方法如果声明为privateJupiter会直接跳过它不会报错。这绝对是隐藏最深的坑之一——你看着代码逻辑没问题测试就是不执行那段初始化排查半天才反应过来修饰符不对。JUnit 5要求这些生命周期方法不能是private要么public要么包级可见。3. 参数化测试一份数据跑十遍用例自动化测试的进阶门槛3.1 ValueSource、CsvSource和MethodSource怎么选一个测试方法只能测一种输入是新手最大的思维定式。JUnit 5引入的参数化测试ParameterizedTest彻底改变了这个局面——同一段测试逻辑喂不同数据跑出不同结果这就是数据驱动的雏形。最常用的是MethodSource它也是最灵活的ParameterizedTest MethodSource(stringProvider) void testWithMethodSource(String input, int expectedLength) { assertTrue(input.length() expectedLength); } static StreamArguments stringProvider() { return Stream.of( Arguments.of(abc, 3), Arguments.of(hello world, 11), Arguments.of(, 0) ); }注意方法源必须返回Stream、Iterable、Iterator或参数数组而且必须是staticPER_CLASS生命周期除外。我最常用的就是StreamArguments方式因为可以同时传多个参数还能配合ArgumentMatchers做复杂场景。如果测试参数是简单的基础类型ValueSource就够了ParameterizedTest ValueSource(ints {1, 2, 3, 6, 12}) void testIsFactorOf12(int number) { assertTrue(12 % number 0); }一组字符串数字布尔的混合参数用CsvSource——它用逗号分隔甚至可以处理带引号的字符串。当参数多到代码里写不下或者你希望测试数据跟代码分离用CsvFileSource加载src/test/resources下的CSV文件ParameterizedTest CsvSource({ apple, 1, banana, 2, lemon, lime, 0xF1 }) void testWithCsvSource(String fruit, int rank) { // ... }EnumSource则是枚举参数的首选——测试一个枚举的所有取值分支时特别顺手。我自己的选择逻辑是单个简单参数用ValueSource多个参数且数据量不大用CsvSource参数有复杂逻辑或需要程序化生成用MethodSource数据量庞大、需要维护独立数据文件时用CsvFileSource。3.2 RepeatedTest和动态测试什么场景才用得上RepeatedTest听名字是重复跑N遍实际用途比想象中窄。它适合测试随机性相关的逻辑——比如测试Collections.shuffle后列表内部元素不变、测试并发工具在多次执行下行为一致。一个典型的场景RepeatedTest(5) void testConcurrentCounterIncrement() { // 多线程各执行100次最终计数必须等于线程数*100 }但你得想清楚重复跑5遍不代表覆盖了5组不同数据它只是用同一样本做多次采样。如果真正想要多变的数据参数化测试才是正确的工具。再说说动态测试TestFactory。它跟参数化测试的区别是参数化测试的每个参数组都被包装成一个标准的TestTemplateInvocationContext框架替你跑而TestFactory返回的是一个DynamicNode集合每个动态测试可以有自己的名字、自己的逻辑甚至可以完全动态地决定有几个测试用例。这个特性适合做遍历某个目录下的文件每个生成一个测试用例之类的场景。TestFactory CollectionDynamicTest testAllTransactions() { ListTransaction transactions transactionRepository.findUnsettledOnes(); return transactions.stream() .map(tx - DynamicTest.dynamicTest( 核算交易: tx.getId(), () - assertEquals(tx.getAmount(), tx.getBalance(), 交易金额和余额不匹配) )) .collect(Collectors.toList()); }但是动态测试有个致命的限制IDE里没法像普通Test方法那样单独运行某一个动态节点调试体验极差。所以我的建议是能用参数化测试解决的事不要用TestFactory。后者更适合生成大量动态数据的场景或者测试框架本身在做的发现测试这类元测试。4. 嵌套测试与条件测试让测试结构会说话4.1 用Nested表达测试的场景层次我见过太多测试类几十个测试方法平铺在一层方法名一个比一个长testCreateUserWhenEmailAlreadyExistsShouldThrowException、testCreateUserWhenEmailInvalidShouldThrowException……这些测试类根本没法看你一眼望过去全是逻辑堆叠。Nested注解允许你在测试类内部定义嵌套测试类它们不是继承关系而是场景分组关系——外层的BeforeEach会先跑然后才进入嵌套类的初始化。这种结构特别适合描述业务流程的状态机class UserServiceTest { Nested class 创建用户 { Test void 正常创建时返回用户() { ... } Nested class 邮箱已存在时 { // 这个嵌套类里的BeforeEach可以先准备一个已存在的邮箱 Test void 抛出异常() { ... } } } Nested class 删除用户 { // ... } }注意我用了中文作为方法名——这在国内团队完全是可行的DisplayName注解也能定义可读性极佳的展示名。嵌套测试配上中文方法名测试报告读起来就像一份需求文档。嵌套类还有一个妙用复用外层测试类的准备逻辑。外层BeforeEach创建了通用mock嵌套类里可以再准备更细的上下文内层BeforeEach在外层基础上叠加执行顺序是外BeforeEach→ 内BeforeEach→ 测试方法。反过来AfterEach执行顺序是内 → 外。4.2 条件测试让测试只在该跑的时候跑真实项目的测试环境不是只有一套——CI上要跑全量测试本地开发可能只跑相关模块Windows上有Windows专属测试Linux上有Linux专属的。JUnit 5的条件测试就是解决这个测试什么时候才该执行的问题。最常用的是EnabledOnOs和DisabledOnOsTest EnabledOnOs(OS.WINDOWS) void testWindowsFilePermission() { // 只有Windows下才验证NTFS权限特性 } Test DisabledOnOs(OS.WINDOWS) void testUnixSocket() { // 避免在Windows上报错跑在Linux/Mac上 }还有按JRE版本、系统属性、环境变量来控制的条件。比如某个测试依赖外部服务地址而CI环境已经设置好了STAGING_URL环境变量Test EnabledIfEnvironmentVariable(named STAGING_URL, matches .*) void testExternalPaymentCallback() { // 环境变量存在才跑本地没有就跳过 }我第一次看到这些注解时觉得这不是脱裤子放屁吗直接不写不就行了——后来才明白它们的核心价值是让测试集始终处于绿状态。如果你的代码里有很多if (!System.getenv().containsKey(CI)) return;这种跳过逻辑测试报告里根本看不出这个用例跳过是因为环境不满足而条件失败会显示为skipped而不是passed。这样你在CI上看测试报告时能清晰区分真的通过了和被跳过了不相关的用例。对自动化测试来说让测试结果可信可比让测试数量多重要得多。5. 与Mockito和Spring整合真实项目的测试姿势5.1 Mockito扩展从硬编码mock到干净注入单元测试有个铁律测试一个类就别把它依赖的类也一起测了。依赖数据库就mock掉DAO层依赖第三方接口就mock掉HTTP客户端。JUnit 4时代一个经典的写法是UserDao userDao Mockito.mock(UserDao.class); when(userDao.findById(1L)).thenReturn(user);然后在Before里把这些mock对象组装到一个service上。这套逻辑跑得好但和测试类代码耦合太深——每次新增依赖都要改创建逻辑。JUnit 5的MockitoExtension让这个过程变成了声明式的ExtendWith(MockitoExtension.class) class UserServiceTest { Mock UserDao userDao; Mock EmailSender emailSender; InjectMocks UserService userService; // Mockito自动把mock塞进userService的字段/构造器 Test void testNotifyUser() { when(userDao.findById(1L)).thenReturn(Optional.of(new User(1L, alice))); userService.notifyUser(1L); verify(emailSender).send(aliceexample.com); } }几个关键点InjectMocks会根据类型把Mock注入进去但如果UserService同时依赖真实对象可能需要用一个真实实例替换某个mock——Spy就是为此准备的。verify的用法是检验方法到底有没有被调用、调用了几次这是测试交互逻辑的核心比单纯的返回值断言重要得多。还有一个容易忽视的细节stub参数匹配。when(userDao.findById(1L)).thenReturn(...)只有在实参正好是1L时才生效。如果测试里传入了别的idMockito会返回默认值null、0或空集合然后你可能在下一行NPE追查半天才发现是stub没匹配上。如果要匹配任意值用anyLong()如果要匹配特定条件可以用argThat(argument - argument 0)。5.2 Spring Boot测试SpringBootTest之外的切片测试很多团队写接口测试上来就SpringBootTest——启动整个Spring上下文每个测试加载所有Bean。结果测试跑一次要几十秒模块之间还互相牵涉本地开发时跑一次测试等得人心烦。Spring Boot 2.x开始支持的测试切片Test Slice是更合理的方案。它的思想是只加载被测模块所需的那一层Bean。比如测Controller层用WebMvcTestSpring只会创建Controller和MockMvc相关的Bean不启动整个服务WebMvcTest(UserController.class) class UserControllerTest { Autowired MockMvc mockMvc; MockBean UserService userService; // Controller依赖的service用mock Test void testGetUser() throws Exception { when(userService.getUser(1L)).thenReturn(new User(1L, alice)); mockMvc.perform(get(/api/users/1)) .andExpect(status().isOk()) .andExpect(jsonPath($.name).value(alice)); } }测数据访问层用DataJpaTest它会把Spring Data JPA相关的Bean配好连接换成内嵌数据库H2每次测试结束自动回滚事务。测Redis相关用DataRedisTest测消息队列用KafkaTest……这些切片的共同点是轻、快、隔离对于自动化测试体系来说测试跑得快才能让你有动力频繁跑。SpringBootTest也不是一无是处——集成测试、端到端冒烟测试它是最接近真实环境的验证方式。我的经验是分层策略单元测试用Mockito硬隔离Controller层用WebMvcTestRepository层用DataJpaTest只有当涉及多个模块协作、比如几个Service联动的事务场景才动用SpringBootTest。这样整条测试链路的总体执行时间能控制在合理范围。注意Spring Test和JUnit 5整合时别忘了导入spring-boot-starter-test它会自动把JUnit Jupiter、Mockito、AssertJ都带进来。另外Spring上下文默认是跨测试类缓存的也就是多个测试类如果配置相同会复用同一个Context这是好消息——省启动时间坏消息是Bean状态可能被上次测试污染所以涉及到共享的Bean最好在测试里做好清理。6. 项目中的测试策略与工程化把自动化测试变成日常6.1 Maven/Gradle配置与并行测试工具链配置这件事看起来不起眼却能卡住一批人。Maven项目要用JUnit 5第一步就可能在surefire版本上翻车——太老的surefire只认JUnit 4你的JUnit 5用例跑出来显示Tests run: 0怎么排查都摸不着头脑。直接指定版本maven-surefire-plugin至少2.22.0。Gradle的话更简单testImplementation org.junit.jupiter:junit-jupiter:5.10.0之后默认的Test任务会自动探测到Jupiter引擎。如果遇到Gradle 6.8以下版本对JUnit 5支持不完整的情况可以显式配置test { useJUnitPlatform() maxParallelForks 4 // 并行跑 }这里我要多说两句并行测试。JUnit 5支持配置并行执行——junit.jupiter.execution.parallel.enabledtrue。但并行测试最大的敌人不是配置而是共享状态如果你的测试访问同一个数据库、同一个缓存或者一个类里的实例字段被多个线程读写并行跑就会出现随机性失败。你以为自己在压测实际是在制造薛定谔的测试。我的建议是先保证测试的隔离性再考虑并行。每个测试要么rollback事务要么用独立的测试数据要么依赖成熟的testcontainer方案起一个临时容器——隔离好了之后并行带来的性能收益才是真实的。根据我实测一个中等规模项目的全量测试串行跑10分钟隔离做好后并行4线程能压到3分钟左右性价比极高。6.2 从覆盖率到测试金字塔的分层设计JaCoCo插件的配置几乎是标配plugin groupIdorg.jacoco/groupId artifactIdjacoco-maven-plugin/artifactId version0.8.11/version executions execution goals goalprepare-agent/goal /goals /execution execution idreport/id phaseverify/phase goals goalreport/goal /goals /execution /executions /plugin覆盖率数字我见得多了有些团队追求100%行覆盖率这是我完全不认同的。看覆盖率不如看测试的有效性——行为被验证了没有异常分支测了没有边界值测了没有覆盖率是参考不是KPI。我见过一个项目覆盖率90%但所有setter/getter都测了核心的业务流程反而没人测——这种覆盖率毫无意义。我在项目里推的是经典的测试金字塔底层是大量快速、低耦合的单元测试JUnitMockito中间是接口/切片测试WebMvcTest、DataJpaTest顶端是少量端到端集成测试SpringBootTest或者接Testcontainers跑真实中间件。金字塔的形状决定了测试的稳定性和成本——单元测试越多构建越快排查问题越精准E2E测试多了就会变成每次跑都飘红没人敢改测试的烂摊子。7. 常见问题与排查技巧实录7.1 高频问题速查表现象排查思路解决方案测试运行显示0个测试surefire/Gradle探测不到Jupiter引擎确认maven-surefire-plugin版本2.22.0Gradle确认test任务useJUnitPlatform()Test方法不执行生命周期方法/测试方法修饰符不对确认不是private方法不能返回非void除非用了TestFactory参数化测试找不到MethodSource方法名或可见性不对确认方法名完全匹配且是static方法或用了PER_CLASS生命周期中文断言信息乱码Maven surefire编码不对设置project.build.sourceEncodingUTF-8surefire配置argLine -Dfile.encodingUTF-8Spring测试Bean冲突某个切片加载了不该加载的Bean切片测试用MockBean替代真实依赖或用WebMvcTest而不是SpringBootTestMockito when不生效参数不匹配确认使用了anyLong()/any()等或者检查equals/hashCode实现偶发性失败单跑却能过测试间共享状态/并行冲突检查静态字段、数据库数据残留补全事务回滚或测试数据清理7.2 几个长期折磨我的坑第一个是surefire和IDEA的版本不一致。同一份代码在IDEA里跑得好好的Jenkins上就0个测试。原因基本是surefire版本太老或者只加了junit-jupiter-api没加junit-jupiter-engine——engine才是真正干活的api只是注解定义。这个问题我当时排查了整整一个下午最后把依赖树打出来才找到原因。第二个是**BeforeEach的初始化被用户名注释骗了**。有一次重构同事把初始化方法复制到AfterEach下却没改注解然后所有人都在跟为什么测试变量是null搏斗。从此之后我特别注意生命周期注解的执行模型一定要印在脑子里不要靠IDE自动补全猜意思。第三个和Mockito有关在BeforeEach里stub但每个测试方法需要的返回数据不一样。导致的结果是第一个测试过了第二个测试因为同一个stub返回了旧数据而失败。后来我改成在需要的地方再stub而不是在BeforeEach里做统一stub——stub应该在测试方法内、离断言最近的地方写这样你读测试时也能一眼看到这个用例到底测的是什么数据。还有个感受测试失败信息要写人话。默认的assertEquals(2, 3)报错是expected: 2 but was: 3看起来挺清楚但如果你测试的是一个复杂对象还是建议像这样加个描述信息assertEquals(expectedUser, actualUser, 创建用户后返回的用户信息不符);信不信由你这个描述信息在CI上排查问题时会节省你10分钟甚至半小时——自动化测试维护成本的大头永远是理解失败原因的时间。我自己的体会是写测试跟写业务代码一样是在表达意图。JUnit 5整个设计都在鼓励你让测试结构清晰、数据驱动、条件明确。别把测试当成给代码补的作业把它当作可维护的软件模块来对待。你现在多花的一小时设计测试会在未来每次跑测试、修bug时加倍还给你。最后再分享一个小技巧每次写新测试前先问自己这个测试失败时报错信息能不能让我3分钟内定位到问题如果不能就改进断言和命名。这套标准用下来我后来几乎不需要看测试代码之外的日志才能找到bug——测试自己就能告诉你坏在哪。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AI Agent知识获取管道:从RAG到Agentic RAG的工程实践指南 2026/9/29 18:35:31

AI Agent知识获取管道:从RAG到Agentic RAG的工程实践指南

做 AI Agent 这段时间,我最大的体会是:模型本身决定下限,知识获取管道决定上限,而 RAG(Retrieval-Augmented Generation)就是这条管道最基础、也最关键的形态。之前几篇我们聊过 Agent 的总体架构、推理循环…

阅读更多 →
国产代码大模型接入Claude Code实战指南 2026/9/29 18:35:31

国产代码大模型接入Claude Code实战指南

1. 项目概述:这不是“换脑手术”,而是一次国产大模型在代码场景下的实战适配最近在好几个技术群和开发者论坛里,看到有人发截图:Claude Code 的界面右下角,模型选择器里赫然出现了GLM-5.2、DeepSeek-Coder-V2、Qwen2.5…

阅读更多 →
MATLAB/Simulink微电网潮流方向动态模拟实战 2026/9/29 18:35:31

MATLAB/Simulink微电网潮流方向动态模拟实战

1. 项目概述:为什么微电网潮流方向模拟不是“算个数”那么简单?微电网,这个词在电力系统圈里已经不新鲜了,但真正能动手搭出一个可运行、可观察、可验证潮流方向的Simulink模型的人,远比你想象中少。我带过十几届电气工…

阅读更多 →
Excel截图jar包实战:用Aspose.Cells渲染引擎替代POI 2026/9/29 18:35:31

Excel截图jar包实战:用Aspose.Cells渲染引擎替代POI

简介:面向Java开发者的Excel图表截图方案,基于Aspose.cells 19.3版本,实现带格式地导出Excel内容为图片,并解决默认库在输出时附带水印的问题,适合需要生成报表截图、在线预览缩略图或文档批注配图的开发场景。资源共5…

阅读更多 →
用ESP32搭建低成本TDOA室内定位系统:从原理到校准 2026/9/29 18:35:30

用ESP32搭建低成本TDOA室内定位系统:从原理到校准

从标题看,这是个非常经典的“看起来很难,拆开全是细节”的硬件项目。我最早接触TDOA(到达时间差)定位,是被无人机室内编队逼的,GPS信号在室内完全没法用,UWB模块又贵,一个基站一百多…

阅读更多 →
NVIDIA板级设计校招笔试核心考点:门电路与理想放大器详解 2026/9/29 18:35:24

NVIDIA板级设计校招笔试核心考点:门电路与理想放大器详解

1. 从一道校招笔试题说起:板级设计工程师到底在考什么NVIDIA 的 Board Design Engineer 校招笔试,圈外人听起来可能觉得就是“画电路板的”,但真正做过板级设计的人都知道,这个岗位横跨了模拟电路、数字电路、信号完整性、电源完整…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉