对于代码中涉及到外部资源,包括文件、数据库、中间件、第三方服务等等,需要怎么编写单元测试?
临时文件
为了保证测试是可以重复执行的,我们要确保所有的资源在执行之后要恢复原样。对于文件这种外部资源,JUnit给予一个标准答案:临时文件。
更准确地说,JUnit 给出的方案是临时目录,在这个目录里,你怎么折腾都行。我们只要给一个变量标记上 @TempDir
class FileTodoItemRepositoryTest {
@TempDir
File tempDir;
private File tempFile;
private FileTodoItemRepository repository;
@BeforeEach
void setUp() throws IOException {
this.tempFile = File.createTempFile("file", "", tempDir);
this.repository = new FileTodoItemRepository(this.tempFile);
}
@Test
public void should_find_nothing_for_empty_repository() throws IOException {
final Iterable<TodoItem> items = repository.findAll();
assertThat(items).hasSize(0);
}
...
}
Mock 框架
对于不可控的组件,如何将其可控化?
第一步自然是隔离,第二步就是用一个可控的组件代替不可控的组件。换言之,用一个假的组件代替真的组件。Mock 框架可以完成代替的任务
Mock 框架最核心的两个点:设置模拟对象与校验对象行为。
模拟对象的设置核心就是两点:参数是什么样的以及对应的处理是什么样的。
接下来简单演示Mock 框架的处理流程
1,代码进行初始化
public class XXXTest {
@Autowired
private XXXServiceImpl xxxService;
private XXXApi xxxApi;
@Before
public void setUp() {
this.xxxApi= mock(XXXApi.class);
xxxService.setXXXApi(xxxApi);
}
2,判断参数是什么样的以及对应的处理是什么样的
如像下面,对方法getDeviceFamilyRelations不管传入什么参数,都返回特定的对象
when(xxxApi.getDeviceFamilyRelations(any())).thenReturn(commonGenericResultRes);
3,校验对象行为
模拟对象的另外一个重要行为是校验对象行为,就是知道一个方法有没有按照预期的方式调用。比如,我们可以预期 save 函数在执行过程中得到了调用。
verify(repository).save(any());
集成测试
当单元测试不太能满足测试需求时,比如用到数据,第三方库,组件间的协同测试等。可以考虑把框架集成进来,做一个完整的集成测试。
SpringBootTest & RunWith
SpringBootTest启动spring容器,意思为接下来的测试是把所有组件都集成起来的集成测试。@SpringBootTest与@RunWith这两个是配合使用的
Transactional
@Transactional,说明这个测试是事务性的,在缺省的测试事务中,执行完测试之后,数据是要回滚,也就是不对数据库造成实际的影响。
TestPropertySource
这是在用 classpath 上的 test.properties 这个文件中的配置,去替换掉我们缺省的配置(也就是我们真实的数据库)
WebMvcTest
@WebMvcTest是Spring 提供了模拟的 Web 环境。 执行速度相对于 @SpringBootTest 这种集成了所有组件的做法而言要快一些。配合@MockBean使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SceneManageApplication.class)
@Transactional
@TestPropertySource("classpath:application.yml")
public class SceneRoomRelationServiceTest {
网友评论