Java-Mock简化单元测试

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"单元测试目的","attrs":{}},{"type":"link","attrs":{"href":"#toc_1","title":null}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"维基百科对单元测试的定义:\n\n单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。\n\n单元测试的目标是隔离程序部件并证明这些单个部件是正确的。\n","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"画外音:单元测试是比较细粒度的测试,是对接口、方法、函数的测试,目的是保障代码按照正确的方式去执行,提高代码质量。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"单元测试实施原则","attrs":{}},{"type":"link","attrs":{"href":"#toc_2","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mock脱离数据库 + 不启动Spring + 优化测试速度 + 不引入项目组件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"单元测试不应该依赖数据,依赖外部服务或组件等,会对其他数据产生影响的情况。启动Spring容器,一般比较慢,可能会启动消息监听消费消息,定时任务的执行等,对数据产生影响。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mock测试就是在测试过程中,对那些当前测试不关心的,不容易构建的对象,用一个虚拟对象来代替测试的情形。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"说白了:就是解耦(虚拟化)要测试的目标方法中调用的其它方法,例如:Service的方法调用Mapper类的方法,这时候就要把Mapper类Mock掉(产生一个虚拟对象),这样我们可以自由的控制这个Mapper类中的方法,让它们返回想要的结果、抛出指定异常、验证方法的调用次数等等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"减少单元测试对外部的依赖和副作用,提高单元测试效率","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"不使用 @Autowired,@Resource, 需要启动 Spring 容器,测试速度慢,会产生副作用;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"不使用 @SpringBootTest,@SpringBootTest(classes = Application.class), 这会启动整个 SpringBoot 服务","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"不应调用数据库,除非是做数据库操作相关的测试,虽然可配置事务回滚,但大多数情况下还是会产生脏数据等问题","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"使用Assert断言,用于判断某个特定条件下某个方法的行为,为了证明某段代码的执行结果和期望的一致","attrs":{}}]}],"attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"画外音:单元测试应小而轻,提交测试效率,较少对外部的依赖,比如数据库、Spring容器、网络服务等,而只关心我们自己的代码,通过Mock来解决对外部的依赖","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Mockito的使用","attrs":{}},{"type":"link","attrs":{"href":"#toc_3","title":null}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"基本使用","attrs":{}},{"type":"link","attrs":{"href":"#toc_4","title":null}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"使用静态方法 mock()","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用注解 @Mock 标注","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果使用@Mock注解, 必须去触发所标注对象的创建. 可以使用 MockitoRule来实现. 它调用了静态方法MockitoAnnotations.initMocks(this) 去初始化这个被注解标注的字段.或者也可以使用@RunWith(MockitoJUnitRunner.class).","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“when thenReturn”和”when thenThrow”","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模拟对象可以根据传入方法中的参数来返回不同的值, when(….).thenReturn(….)方法是用来根据特定的参数来返回特定的值.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们也可以使用像 anyString 或者 anyInt anyLong any 这样的方法来定义某个依赖数据类型的方法返回特定的值.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“doReturn when” 和 “doThrow when”","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"doReturn(…).when(…)的方法调用和when(….).thenReturn(….)类似.对于调用过程中抛出的异常非常有用.而doThrow则也是它的一个变体.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"常用注解","attrs":{}},{"type":"link","attrs":{"href":"#toc_5","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@Mock:对函数的调用均执行mock(即虚假函数),不执行真正部分。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@Spy:对函数的调用均执行真正部分。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@InjectMocks:创建一个实例,简单的说是这个Mock可以调用真实代码的方法,使用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mockito中的Mock和Spy都可用于拦截那些尚未实现或不期望被真实调用的对象和方法,并为其设置自定义行为。二者的区别在于Mock不真实调用,Spy会真实调用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@MockBean: 功能同 @Mock, 只是会将实例放入 Spring 容器管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@SpyBean: 功能同 @Spy, 只是会将实例放入 Spring 容器管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Spy 和 Mock 生成的对象不受 Spring 管理","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Spy 调用真实方法时,其它 bean 是无法注入的,要使用注入,要使用 SpyBean","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"SpyBean 和 MockBean 生成的对象受 Spring 管理,相当于自动替换对应类型 bean 的注入,比如 @Autowired、@Resource 等注入","attrs":{}}]}],"attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"最佳实践","attrs":{}},{"type":"link","attrs":{"href":"#toc_6","title":null}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 不使用 @SpringBootTest(classes = Application.class)\n@RunWith(SpringRunner.class)\npublic class ExamAnswerComponentTest {\n\n // 创建一个实例,会注入Mock变量\n @InjectMocks\n private ExamAnswerComponent examAnswerComponent = new ExamAnswerComponentImpl();\n\n // 相关操作会被Mock掉\n @Mock\n private ExamAnswerCacheObjectiveDAO examAnswerCacheObjectiveDAO;\n\n @Before\n public void setUp() {\n // 初始化Mock\n MockitoAnnotations.initMocks(this);\n\n // given...willReturn 指定方法参数,模拟返回值\n given(examAnswerCacheObjectiveDAO.selectByBizIdAndPaperAndQuestion(any(), any(), any()))\n .willReturn(new ExamAnswerCacheObjectivePO());\n given(examAnswerCacheObjectiveDAO.insert(any())).willReturn(1);\n given(examAnswerCacheObjectiveDAO.updateUserAnswerById(any(), any())).willReturn(1);\n }\n\n\n @Test\n public void saveOrUpdateAnswerCacheObjective() {\n\n ExamAnswerCacheObjectivePO po = new ExamAnswerCacheObjectivePO();\n po.setBizId(100000015L);\n po.setBizType(9);\n po.setUserAnswer(\"A\");\n po.setGroupPaperId(1000320L);\n po.setQuestionId(1000042L);\n po.setQuestionType(1);\n\n int affect = examAnswerComponent.saveOrUpdateAnswerCacheObjective(po);\n System.out.println(\"affect = \" + affect);\n Assert.assertTrue(affect > 0);\n }\n\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"参考","attrs":{}},{"type":"link","attrs":{"href":"#toc_7","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.codenong.com/cs106503150/","title":null},"content":[{"type":"text","text":"https://www.codenong.com/cs106503150/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章