赞
踩
前言
我们在购买电脑的时候,一次就可以开机了,这是因为在出厂的时候厂家就帮我们做好了测试。那如果没有厂家这步,我们会面临显示器无法显示的问题,磁盘无法挂载等情况。那运气好可能一次就能定位,运气不好,还需要排查显示器,内存条,主板,显卡等一系列组件。等我们排查就花费了大量的时间和精力。那如果在组装之前就测试好了每个组件情况,也就能避免这样的事情发生了。如果把电脑的生产,测试和软件的开发测试类比,就会发现。
显卡,内存条就像是软件中的单元,通常是函数或者类,对单个元器件的测试就像是软件测试中的单元测试;
组装完成的功能机箱,显示器就像是软件中的模块,对机箱显示器的测试就像是软件中的集成测试; 电脑全部组装完成就像是软件完成了预发布版本。
电脑全部组装完成后的开机测试就像是软件中的系统测试。
单元测试是指对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数或者类。
驱动代码是用来调用被测函数的,而桩代码和 Mock 代码是用来代替被测函数调用的真实代码的。
Stub(桩对象):Stub通常用于替代测试对象的某些部分,以便进行单元测试等测试。例如,当被测代码需要访问外部数据源或者调用其他函数时,我们可以使用Stub来模拟这些依赖项的行为,从而使得测试过程更加独立和可控。
Mock(模拟对象):Mock通常用于模拟函数或对象的行为,以便更好地进行单元测试或功能测试。例如,当被测代码需要与某个对象进行交互时,我们可以使用Mock来模拟该对象的行为和响应,并判断被测代码的行为是否正确。
一般测试方法如下:
当有如下场景的时候就可以考虑采用单元测试
@SpringBootTest
:获取启动类,加载配置,寻找主配置启动类(被 @SpringBootApplication 注解
@RunWith(SpringRunner.class)
:让JUnit运行Spring的测试环境,获得Spring环境的上下文的支持
@Test
:测试方法,可以测试期望异常(配置expected )和超时时间。
@Mock
:是 Mockito.mock() 方法的简写。创建的是全部mock的对象,即在对具体的方法打桩之前,mock对象的所有属性和方法全被置空(0或null)。
@Spy
:会调用真实的方法,有返回值的调用真实方法并返回真实值;如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。定义了mock方法的则执行mock(即虚假函数);默认生成后所有依赖的对象都会null,且要一个无参构造。
@InjectMocks
:创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。如果使用spring的@Autowired注解一起使用,则会直接使用spring容器的对象,并将@Mock(或@Spy)对象注入。
@MockBean
: Spring Boot 中的注解。我们可以使用 @MockBean 将 mock 对象添加到 Spring 应用程序上下文中。该 mock 对象将替换应用程序上下文中任何现有的相同类型的 bean。如果应用程序上下文中没有相同类型的 bean,它将使用 mock 的对象作为 bean 添加到上下文中。
@SpyBean
:同上。
@InjectMocks
由mock框架管理,所以只能注入@Mock和@Spy的对象。@Mock
AService aService;
@InjectMocks
AController aController; //这里会注aService
@Autowired
AController aController;//这里不会注aService
class BController{
AService aService;
}
@MockBean
AService aService;
@Autowired
AController aController; //这里会注入aService
@Mock
AService aService;
@Autowired
@InjectMocks
AController aController;//这里会注入aService
@SpyBean
存在循环依赖问题,其原因主要是早期暴露和正常暴露会创建不同对象,造成对象不一致。通过如下方式也没办法解决,因为spy的是spring增强的对象,而不是像@SpyBean
注解代理的是原生对象。AService bean = context.getBean(AService.class);
AService spy = Mockito.spy(bean);
设置 spy 逻辑时不能再使用 when(某对象.某方法).thenReturn(某对象)
的语法,而是需要使用 doReturn(某对象).when(某对象).某方法
或者 doNothing(某对象).when(某对象).某方法
。
对于 static 、 final 、private修饰的方法和equals()、hashCode()方法, Mockito 无法对其进行when(…).thenReturn(…) 操作。
SpyBean
, 为外部依赖打桩,考虑使用MockBean
测试代码
@Service public class AService { public String hasReturnAndArgs(String str){ return "10"; } public String hasReturn(){ return "10"; } public void hasArgs(String str){ System.out.println(1000); } public void noArgs(){ System.out.println(1000); } } @RestController public class AController { @Autowired private AService aService; public String hasReturnAndArgs(String str){ return aService.hasReturnAndArgs(str); } public String hasReturn(){ return aService.hasReturn(); } public void hasArgs(String str){ aService.hasArgs(str); } public void noArgs(){ aService.noArgs(); } }
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class) public class ServiceTest { @Before public void before() { // 启用 Mockito 注解 MockitoAnnotations.initMocks(this); } @Mock AService aService; @InjectMocks AController aController; @Test public void test() { //1.不调用真实方法,默认返回null String value = aService.hasReturnAndArgs("10"); Assert.assertEquals(value, null); //2.打桩 //当传参是10L时,返回response Mockito.when(aService.hasReturnAndArgs("10")).thenReturn("30"); //当传参是20L时,真实调用 Mockito.when(aService.hasReturnAndArgs("20")).thenCallRealMethod(); //当传参是30L时,抛出异常 Mockito.when(aService.hasReturnAndArgs("30")).thenThrow(new Exception("test error")); Assert.assertEquals(aService.hasReturnAndArgs("10"), "30"); //入口为真实方法,内部mock对象调用的也是mock方法 Assert.assertNotEquals(aService.hasReturnAndArgs("20"), "30"); try { Assert.assertNotEquals(aService.hasReturnAndArgs("30"), "30"); } catch (Exception e) { System.out.println(e.getMessage()); } //3.注入对象 Assert.assertEquals(aController.hasReturnAndArgs("10"), "30"); } }
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class) public class ServiceTest { @Before public void before() { // 启用 Mockito 注解 MockitoAnnotations.initMocks(this); } @Spy AService spy; @Test public void test() { //AService spyTemp = new AService(); //AService spy = Mockito.spy(spyTemp); //1.调用真实方法 Assert.assertEquals(spy.hasReturnAndArgs("20"), "10"); //2.打桩 Mockito.doReturn("30").when(spy).hasReturnAndArgs("20"); Assert.assertEquals(spy.hasReturnAndArgs("20"), "30"); //验证是否被调用了一次 Mockito.verify(spy,times(1)).hasReturnAndArgs("20"); //设置任何hasReturnAndArgs调用都返回30 Mockito.doReturn("30").when(spy).hasReturnAndArgs(Mockito.anyString()); Assert.assertEquals( spy.hasReturnAndArgs("-2"), "30"); Mockito.verify(spy,times(2)).hasReturnAndArgs(Mockito.anyString()); //不支持这样 Mockito.when(spy.hasReturnAndArgs("20")).thenReturn("10"); Assert.assertEquals(spy.hasReturnAndArgs("20"), "10"); } }
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class) public class ServiceTest { @Before public void before() { // 启用 Mockito 注解 MockitoAnnotations.initMocks(this); } @SpyBean private AService spy; @Autowired AController aController; @Test public void test() { //调用真实方法 Assert.assertEquals(spy.hasReturnAndArgs("20"), "10"); Mockito.doReturn("30").when(spy).hasReturnAndArgs(Mockito.anyString()); Assert.assertEquals(spy.hasReturnAndArgs("20"), "30"); Mockito.verify(spy,times(1)).hasReturnAndArgs(Mockito.anyString()); Assert.assertEquals(aController.hasReturnAndArgs("20"), "30"); } }
//是否调用过一次
Mockito.verify(spy).hasReturnAndArgs(Mockito.anyString());
//是否调用过N次
Mockito.verify(spy,times(1)).hasReturnAndArgs(Mockito.anyString());
//没有被调用,相当于 times(0)
Mockito.verify(spy,never()).hasReturnAndArgs(Mockito.anyString());
//atLeast(N) 至少被调用 N 次
//atLeastOnce() 相当于 atLeast(1)
//atMost(N) 最多被调用 N 次
@Test public void testAssert() { // allOf: 所有条件都必须满足,相当于&& assertThat("myname", allOf(startsWith("my"), containsString("name"))); // anyOf: 其中一个满足就通过, 相当于|| assertThat("myname", anyOf(startsWith("na"), containsString("name"))); // both: && assertThat("myname", both(containsString("my")).and(containsString("me"))); // either: 两者之一 assertThat("myname", either(containsString("my")).or(containsString("you"))); // everyItem: 每个元素都需满足特定条件 assertThat(Arrays.asList("my", "mine"), everyItem(startsWith("m"))); // hasItem: 是否有这个元素 assertThat(Arrays.asList("my", "mine"), hasItem("my")); // hasItems: 包含多个元素 assertThat(Arrays.asList("my", "mine", "your"), hasItems("your", "my")); // is: is(equalTo(x))或is(instanceOf(clazz.class))的简写 assertThat("myname", is("myname")); assertThat("mynmae", is(String.class)); // anything(): 任何情况下,都匹配正确 assertThat("myname", anything()); // not: 否为真,相当于! assertThat("myname", is(not("you"))); // nullValue(): 值为空 String str = null; assertThat(str, is(nullValue())); // notNullValue(): 值不为空 String str2 = "123"; assertThat(str2, is(notNullValue())); // -------------------------字符串匹配 // containsString:包含字符串 assertThat("myname", containsString("na")); // stringContainsInOrder: 顺序包含,“my”必须在“me”前面 assertThat("myname", stringContainsInOrder(Arrays.asList("my", "me"))); // endsWith: 后缀 assertThat("myname", endsWith("me")); // startsWith: 前缀 assertThat("myname", startsWith("my")); // isEmptyString(): 空字符串 assertThat("", isEmptyString()); // equalTo: 值相等, Object.equals(Object) assertThat("myname", equalTo("myname")); assertThat(new String[]{"a", "b"}, equalTo(new String[]{"a", "b"})); // equalToIgnoringCase: 比较时,忽略大小写 assertThat("myname", equalToIgnoringCase("MYNAME")); // equalToIgnoringWhiteSpace: 比较时, 首尾空格忽略, 比较时中间用单个空格 assertThat(" my \t name ", equalToIgnoringWhiteSpace(" my name ")); // isOneOf: 是否为其中之一 assertThat("myname", isOneOf("myname", "yourname")); // isIn: 是否为其成员 assertThat("myname", isIn(new String[]{"myname", "yourname"})); // toString() 返回值校验 assertThat(333, hasToString(equalTo("333"))); //------------------------ 数值匹配 // closeTo: [operand-error, operand+error], Double或BigDecimal类型 assertThat(3.14, closeTo(3, 0.5)); assertThat(new BigDecimal("3.14"), is(BigDecimalCloseTo.closeTo(new BigDecimal("3"), new BigDecimal("0.5")))); // comparesEqualTo: compareTo比较值 assertThat(2, comparesEqualTo(2)); // greaterThan: 大于 assertThat(2, greaterThan(0)); // greaterThanOrEqualTo: 大于等于 assertThat(2, greaterThanOrEqualTo(2)); // lessThan: 小于 assertThat(0, lessThan(2)); // lessThanOrEqualTo: 小于等于 assertThat(0, lessThanOrEqualTo(0)); // -----------------------------------------------------集合匹配 // array: 数组长度相等且对应元素也相等 assertThat(new Integer[]{1, 2, 3}, is(array(equalTo(1), equalTo(2), equalTo(3)))); // hasItemInArray: 数组是否包含特定元素 assertThat(new String[]{"my", "you"}, hasItemInArray(startsWith("y"))); // arrayContainingInAnyOrder, 顺序无关,长度要一致 assertThat(new String[]{"my", "you"}, arrayContainingInAnyOrder("you", "my")); // arrayContaining: 顺序,长度一致 assertThat(new String[]{"my", "you"}, arrayContaining("my", "you")); // arrayWithSize: 数组长度 assertThat(new String[]{"my", "you"}, arrayWithSize(2)); // emptyArray: 空数组 assertThat(new String[0], emptyArray()); // hasSize: 集合大小 assertThat(Arrays.asList("my", "you"), hasSize(equalTo(2))); // empty: 空集合 assertThat(new ArrayList<String>(), is(empty())); // isIn: 是否为集合成员 assertThat("myname", isIn(Arrays.asList("myname", "yourname"))); // -------------------------------------------------Map匹配 Map<String, String> myMap = new HashMap(); myMap.put("name", "john"); // hasEntry: key && value匹配 assertThat(myMap, hasEntry("name", "john")); // hasKey: key匹配 assertThat(myMap, hasKey(equalTo("name"))); // hasValue: value匹配 assertThat(myMap, hasValue("john")); }
//有参有返回 Mockito.doReturn("haha").when(spy).hasReturnAndArgs(Mockito.anyString()); Assert.assertEquals(spy.hasReturnAndArgs("str"), "haha"); //无参有返回 Mockito.doReturn("hasReturn").when(spy).hasReturn(); Assert.assertEquals(spy.hasReturn(), "hasReturn"); //有参无返回 Mockito.doNothing().when(spy).hasArgs(Mockito.anyString()); spy.hasArgs("haha"); Mockito.verify(spy).hasArgs(Mockito.anyString()); //无参无返回 Mockito.doNothing().when(spy).noArgs(); spy.noArgs(); Mockito.verify(spy).noArgs(); //调用真实方法 Mockito.doCallRealMethod().when(spy).hasReturnAndArgs("hha"); Assert.assertEquals(spy.hasReturnAndArgs("hha"),"10"); //静态方法 //抛出异常 Mockito.doThrow(new RuntimeException()).when(spy).hasReturnAndArgs(eq("20")); try { spy.hasReturnAndArgs("20"); } catch (Exception e) { System.out.println(e.getMessage()); } //---------------------------------------------------------其他 //参数匹配器 //anyInt()、anyString()、anyDouble()、anyList()、anyMap(),eq(1) Mockito.doReturn("haha").when(spy).hasReturnAndArgs(eq("20")); //模拟返回值 Mockito.doAnswer(invocation -> { //获取参数值 Object[] args = invocation.getArguments(); String arg = (String) args[0]; if ("hallo".equals(arg)){ return "helloWorld"; } return arg; }).when(spy).hasReturnAndArgs(Mockito.anyString()); Assert.assertEquals(spy.hasReturnAndArgs("hallo"), "helloWorld"); Assert.assertEquals(spy.hasReturnAndArgs("ha"), "ha");
导入pom
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.0.0</version>
</dependency>
MockitoAnnotations.initMocks(this);
Mockito.mockStatic(XXX.class).when(XXX::getXXX)
.thenReturn("xxx");
//如果用多次需要关闭
try(MockedStatic<XXX> xx= Mockito.mockStatic(XXX.class)) {
xx.when(() -> A.b(params)).thenReturn(null);
}
红色为尚未覆盖的行,绿色为覆盖的行。class,method,line分别表示类/方法/行代码测试覆盖率。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。