测试类写法以及几种常用方式
这里写自定义目录标题为什么需要测试类常用的jar包测试数据库调用测试Restful接口通过Mock模拟方法调用@Spy注解的使用 依赖注入判读方法调用次数模拟方法返回值when return 使用为什么需要测试类常用的jar包测试数据库调用测试Restful接口通过Mock模拟方法调用@Spy注解的使用 依赖注入判读方法调用次数模拟方法返回值when return 使用是否真的...
为什么需要测试类
-
方便模拟验证已完成功能
开发过程中,代码完成之后,在正式提交测试之前,往往需要自己对功能进行自查,修改一些明显的问题。但是有时候由于本地环境限制,实际流程和数据难以模拟,为了不影响其他模块,我们可以只针对自己新增加的功能进行测试。这时可以使用测试类,针对自己的接口,模拟特定的数据和环境,将测试的接口返回值与预期返回值进行对比,完成验证测试。
-
方便后期维护
由于项目开发过程中,经常有需求变更,有时候会有涉及范围很广的改动,有完善的测试类,可以在大面积改动的时候,检查出一些明显的问题,针对改动能对项目掌控的更好,节省测试时间,减少工作量。
常用的jar包
org.junit.* Junit4 框架
是通用的测试 java 程序的测试框架JUnit可以对Java代码进行白盒测试。
org.junit.Assert.* 断言,用于对比结果是否符合预期,用来判断测试结果的常用框架
org.mockito.* mock
也叫打桩,用来模拟数据,模拟方法调用
下面以SpringBoot为例,展示下测试类的具体使用
测试模拟接口调用
有接口 TestService 参数为String name 返回结果是 "Hello " + name
@Service
public class TestServiceImpl implements TestService {
/**
* 测试接口
*/
@Override
public String testInterface(String name) {
return "Hello " + name;
}
}
这是我们的测试类
@RunWith(SpringRunner.class)
@ActiveProfiles("ut")
@SpringBootTest
public class SiteCacheTest extends BaseTest {
@Autowired
private TestService testService ;
@Test
public void testInterface() {
String result = testService.testInterface("Tom");
Assert.assertEquals("Hello Tom", result );
}
}
测试结果是运行成功,这是一个很简单的例子,没有复杂的参数和业务逻辑,接口内部也没有对其他接口的调用。
在这个测试类中
@ActiveProfiles(“ut”) 表示该测试运行时使用的是 application-ut.properties配置文件,我们实际开发工作中,往往会有多个环境,可以给测试类指定环境,方便控制测试,以及防止测试数据对实际环境造成影响。
@SpringBootTest 该注解表示这个类是一个SpringBoot测试类
Assert.assertEquals(“Hello Tom”, result )
该方法接收两个参数,前者为期望值,后者为实际值,如果不一致,会测试失败
Assert还有很多类似的方法,常用的有
1 - Assert.fail()
让测试直接出错,抛出 AssertionError 。
2 - Assert.fail(String message)
让测试直接出错,并在抛出 AssertionError 时输出 message 作为错误提示信息。
3 - Assert.assertNull(Object object)
猜测 object 为 null,如果不为 null ,抛出 AssertionError 。
4 - Assert.assertSame(Object expected, Object actual)
猜测 expected 对象和 actual 对象引用的是同一个对象。 如果不同,抛出 AssertionError 。
5 - Assert.assertNotSame(Object expected, Object actual)
猜测 expected 对象和 actual 对象引用不同的对象。 如果相同,抛出 AssertionError 。
6 - Assert.assertTrue(boolean condition)
猜测 condition 为 true 。 如果为 false ,抛出 AssertionError 。
7 - Assert.assertFalse(boolean condition)
猜测 condition 为 false 。 如果为 true , 抛出 AssertionError 。
8- Assert.assertEquals(long expected, long actual)
猜测两个 long 类型 expected 和 actual 的值相等。 如果不相等,抛出 AssertionError 。
9 - Assert.assertNotEquals(long unexpected, long actual)
猜测两个 long 类型 unexpected 和 actual 的值不相等。 如果相等,抛出 AssertionError 。
测试Restful接口
前后台返利开发中,针对给前台调用的接口,经常会有一些500错误等,可以模拟请求,根据接口路径来模拟前端调用
通常会传一些错误参数来验证返回错误信息的情况。
@ActiveProfiles("ut")
@RunWith(SpringRunner.class)
@SpringBootTest
public class ControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mvc;
@Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
}
@Test
public void testController() throws Exception {
String json = "{\n" +
"\t\"username\": \"admin\",\n" +
"\t\"password\": \"1234\"\n" +
"}";
String json1 = "{\n" +
"\t\"username\": null,\n" +
"\t\"password\": \"1234\"\n" +
"}";
mvc.perform(MockMvcRequestBuilders.post("/test/login")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json1.getBytes()))//传json参数
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
mvc.perform(MockMvcRequestBuilders.post("/test/login")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes()))//传json参数
.andExpect(MockMvcResultMatchers.status().isInternalServerError())
.andDo(MockMvcResultHandlers.print());
}
}
如上,我们传正常的参数时,期望返回200, 即 MockMvcResultMatchers.status().isOk()
传参数错误时返回 500, 即MockMvcResultMatchers.status().isInternalServerError()
在这个测试类中
首先需要引入WebApplicationContext 用来模拟前台接口调用
@Before 该注解注解的方法,会在测试运行之前先运行,一般用于准备测试数据,初始化对象等,在此例中,我们用来初始化测试用的MockMvc 对象。
@Autowired 测试类可以像正常的类一样,注入你所需要的对象
MockMvcRequestBuilders 来指定测试接口路径,以及请求方式
.andExpect 类似断言, 来指定期望值。
通过Mock模拟方法调用结果
在开发过程中,类都不是独立的,往往一个方法的实现,需要调用多个不同地方的方法组合来实现一个功能,在测试中,我们需要其他方法的返回值满足我们的测试需要,这个时候可以使用@Spy注解来包装某个类,控制它的方法返回值。
下面看例子
@RunWith(SpringRunner.class)
@ActiveProfiles("ut")
@SpringBootTest
public class MockTest {
@Spy
@Autowired
private TestService testService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void codeTest() {
Mockito.when(testService.getUUID()).thenReturn("cdc60ad5609ddced1b128c60ac92340d");
}
}
在这个测试类中,testService中存在一个方法getUUID(),是返回随机id的,但是在我们要测试的方法中调用了这个方法,我们需要用到这个uuid,来模拟数据,以便测试我们的接口,比如我们需要根据它去另外一张表查询数据,但是我们不知道调用时随机的uuid是什么,这时候可以指定它的返回值,当testService.getUUID()被调用时,返回一个指定的值,这样我们就可以继续操作了。
@Spy 对一个注入的类进行包装,以便可以模拟方法返回值
我们需要使用 MockitoAnnotations.initMocks(this); 来初始化对象,该方法会对使用了**@Spy**的类进行初始化。
然后我们可以针对初始化过的对象来模拟返回值。
Mockito.when(xxx).thenReturn(xxx);
从字面可以看出,当…的时候返回…
需要注意的是when(…) thenReturn(…)会调用真实的方法,如果你不想调用真实的方法而是想要mock的话
就使用 doReturn(…) when(…) 不会调用真实方法
判读方法调用次数
有些时候针对循环和和判断较多,或者业务逻辑较为复杂的场景,需要判断方法调用次数。 还有一个场景是判断缓存是否生效,对于加了缓存的方法,多次调用,一般只有一次实际的调用,可以用这个方法来判断调用次数。
@RunWith(SpringRunner.class)
@ActiveProfiles("ut")
@SpringBootTest
public class CallTimesTest {
@MockBean
private TestDao testDao;
@Autowired
private TestService testService;
@Test
public void testCallTimes() {
//设置第一次返回,第二次返回
when(testDao.findUserByName(1))
.thenReturn("user1")
.thenReturn("user2");
String user1 = testService.findUserByName(1);
Assert.assertEquals("user1", user1);
String user2 = testService.findUserByName(1);
Assert.assertEquals("user1", user1);
verify(testDao, times(1)).findWithFullNameByTaskId(1);
}
假设 testService 的findUserByName 方法内部是调用的 testDao的findUserByName方法,我们首先使用**@MockBean** 注解 注解testDao ,对其进行包装,然后我们可以对其进行模拟 ,设置第一次调用返回user1,第二次调用返回user2, 然后testService调用两次,如果在testDao的findUserByName方法上加了缓存,那么对于同样的入参,实际上只会调用一次。
我们可以使用verify来验证方法调用次数。
需要注意的是,findUserByName 方法这里我们的入参是1,在mock中我们有时候不需要写死参数,而是符合一定条件的参数即可,mock提供了一系列方法指定参数,如下图
注意事项
- 测试要注意覆盖率,不要只测试正确的情况,也要测试异常的情况
- 要模拟方法调,设定返回值需要先对类进行包装,@Mock 和@Spy注解均可以实现,区别在于前者是虚假的调用,而且不能mock自动装配的Bean,需要和@InjectMock配合使用;而后者监测真实的调用,方法实际上真的调用了,使用时要注意,想要不调用,可以使doReturn(…) when(…)。
- 在maven项目中,不设置skiptest=true的话,在打包时会默认先执行一遍测试类,所以要注意设置测试类运行环境,以免测试数据对正式环境造成污染,导致不必要的错误,更为规范的做法是,在每个测试用例中,最后对测试数据进行删除。
更多推荐
所有评论(0)