SpringBoot项目下的JUnit测试 | JUnit如何测试私有方法
配置依赖
在SpringBoot项目里,要编写单元测试用例,需要依赖3个jar。一个是最基本的JUnit,然后是spring-test和spring-boot-test。
<!--test--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.9.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <version>2.1.7.RELEASE</version> <scope>test</scope> </dependency>
如果在运行testcase时,遇到如下问题,那么,原因是因为没有引入spring-core包(错误消息里说的很明白了,见其中的springframework/core)
java.lang.NoSuchMethodError: org.springframework.core.type.AnnotationMetadata.introspect(Ljava/lang/Class;)Lorg/springframework/core/type/AnnotationMetadata; at org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition.<init>(AnnotatedGenericBeanDefinition.java:58) at org.springframework.context.annotation.AnnotatedBeanDefinitionReader.doRegisterBean(AnnotatedBeanDefinitionReader.java:253) at org.springframework.context.annotation.AnnotatedBeanDefinitionReader.registerBean(AnnotatedBeanDefinitionReader.java:147) at org.springframework.context.annotation.AnnotatedBeanDefinitionReader.register(AnnotatedBeanDefinitionReader.java:137) at org.springframework.boot.BeanDefinitionLoader.load(BeanDefinitionLoader.java:156) at org.springframework.boot.BeanDefinitionLoader.load(BeanDefinitionLoader.java:135) at org.springframework.boot.BeanDefinitionLoader.load(BeanDefinitionLoader.java:127) at org.springframework.boot.SpringApplication.load(SpringApplication.java:687) at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:385) at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
依赖spring-core包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.0.RELEASE</version> </dependency>
一个springboot junit testcase示例
其中,@RunWith是JUnit的一个运行器。@RunWith(JUnit4.class)就是指用JUnit4来运行;@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境
import com.alibaba.fastjson.JSON; import org.apache.dubbo.config.annotation.Reference; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class CarSignatureServiceTest { @Reference(url = "dubbo://localhost:20880") private CarSignatureService carSignatureService; @Test public void getSignedCarList(){ List<ServiceOrderVO> list = carSignatureService.getSignedCarList(new ServiceOrderVO()); System.out.println(JSON.toJSONString(list)); } }
junit如何测试私有方法?
直接的方式是,把private改为public。不过这时,最好配合 @VisibleForTesting 注解。
com.google.common.annotations.VisibleForTesting 是Google Guava开发包里的注解,用于指定此方法方法的可见性仅限于单元测试,借以提示阅读者或使用者。
@VisibleForTesting public static void removeAllSheets(File file) { XSSFWorkbook toWorkbook = null; try { toWorkbook = new XSSFWorkbook(new FileInputStream(file)); } catch (IOException e) { e.printStackTrace(); }
正确的姿势是用java的反射。
Java的反射( rt.jar->java.lang.reflect)机制允许在程序的运行状态中构造任意一个类的对象,获取及调用其成员(变量/方法/静态方法),这种动态获取程序信息以及动态调用对象的功能称为反射(reflection),反射被视为动态语言的关键。
import java.lang.Class; import java.lang.reflect.Method; // 方式一(我们通常是spring容器项目,所以用这种就OK): TradeServiceImpl service = new TradeServiceImpl();// spring容器项目通常是注入bean Method method = service.getClass().getDeclaredMethod("readSheet", Sheet.class, List.class); method.setAccessible(true); List<TradeVO> list = (List<TradeVO>)method.invoke(service, sheet, new ArrayList<>()); // 方式二: Class<TradeServiceImpl> clz = TradeServiceImpl.class; Method method = clz.getDeclaredMethod("readSheet", Sheet.class, List.class); method.setAccessible(true); List<TradeVO> list = (List<TradeVO>)method.invoke(clz.newInstance(), sheet, new ArrayList<>());// spring容器项目通常是注入bean,而不用clz.newInstance()
强大的IDEA插件支持:
1)Class#getDeclaredMethod的第一个参数是被调用方法的名称(字符串)。你有所担心?强大的IDEA帮你解决,快捷键或菜单的方式rename方法名时,这个字符串会自动跟着修改。
2)反射的方法不存在时,强大的IDEA会提示:Cannot resolve method 'readSheet'。执行testcase会抛异常:java.lang.NoSuchMethodException:com.emax.etl.provider.offlinedata.TradeServiceImpl.readSheet(org.apache.poi.ss.usermodel.Sheet, java.util.List)
3)Class#getDeclaredMethod的第一个参数是被调用方法的名称(字符串),鼠标滑到这个字符串上,会弹出对应方法的javadoc说明;点击字符串,可以直接跳转到对应方法。
测试私有方法,还有一种方式,是修改方法的访问性为protected。
JUnitTest类与被测类的package通常是相同的,只不过文件所处位置不同(src/main/java vs src/test/java)。protected成员在相同package内是可访问的,所以就解决了私有方法无法测试的问题。当然,最好也在方法标注 @VisibleForTesting 注解。 相比反射,这种方式更简单,不过,这样会破坏原类的封装性。所以,还是推荐用反射。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/11889207.html