🌲 😄 主要是建立一个最基础的单元测试示例,包含的测试用例主要包括以下的
🐍 我们的原则还是业务都写在Service里边哦
测试的内容
测试的内容需要覆盖70%的代码才对,不然就容易出现意想不到的bug
- Service的测试
- 包含数据库
- 包含web请求
- 包含RPC请求
- dao的测试
- Dao的测试其实更多的是为了验证SQL的正确性。也即是为了验证ibatis/mybatis xml语句的正确性,如果可以伪造验证xml生成的sql语句是正确的,那么数据库这一层面也是可以避免掉的。
- 内部方法的测试
- 不包含数据库以及其他网络请求的测试
那些是不需要测试的
- 静态方法是不测试的
- 那么就需要保证这个静态方法的绝对正确性,输入输出,边界等等
- 不允许Service的实现出现静态方法,因为不好测试,都放在util里边才是对的
测试的边界
不能按照写代码的思考流程进行测试,外部输入是无穷无尽的,人的行为和动作是无限的.
- 边界测试
- 正常测试
- 异常数据测试
如何考虑数据污染的问题
如何保证我们的数据库数据不被本地测试污染
- 事务
- 单数据源
- 多数据源
Example
- Mock 标记一个Mock字段
- Spy
- Mockito does not delegate calls to the passed real instance, instead it actually creates a copy of it. So if you keep the real instance and interact with it, don’t expect the spied to be aware of those interaction and their effect on real instance state. The corollary is that when an unstubbed method is called on the spy but not on the real instance, you won’t see any effects on the real instance.
- InjectMocks 为对象注入Mock字段
- MockitoAnnotations.initMocks(this); 初始化测试用例类中由Mockito的注解标注的所有模拟对象
when(chatRecordDubboService.saveChatRecord(Matchers.isA(ChatRecord.class), Matchers.eq(EnumSourceType.KN_CLIENT))).thenReturn(“123123”);
- 为mock对象和spy对象设置返回值
- when(service.callMethod(param1,param…)).thenReturn()
doNotion().when(service.callMethod(params…))
public class ChatRecordServiceImplTest {
@Mock
StringRedisTemplate redisTemplate;
@Mock
private ValueOperations<String, String> valueOperations;
@Mock
private IChatRecordDubboService chatRecordDubboService;
@Mock
private MongoTemplate mongoTemplate;
@Spy
@InjectMocks
private ChatRecordServiceImpl spy;
@Before
public void setUp() throws Exception {
// 初始化测试用例类中由Mockito的注解标注的所有模拟对象
MockitoAnnotations.initMocks(this);
when(chatRecordDubboService.saveChatRecord(Matchers.isA(ChatRecord.class),
Matchers.eq(EnumSourceType.KN_CLIENT))).thenReturn("123123");
when(mongoTemplate.updateFirst(Matchers.isA(Query.class), Matchers.isA(Update.class), Matchers.eq(MongoTableConst.HISTORY_RECORD)))
.thenReturn(new WriteResult(1, true, "1111"));
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
doNothing().when(valueOperations).set(anyString(), anyString());
doNothing().when(valueOperations).set(anyString(), anyString(), anyInt(), anyObject());
}
@Test
public void testMockDubboService() {
final String s = chatRecordDubboService.saveChatRecord(new ChatRecord(), EnumSourceType.KN_CLIENT);
Assert.assertEquals("123123", s);
}
@Test
public void saveChatRecord() {
KnClientResponse result = new KnClientResponse();
fill(result);
BotResponse response = new BotResponse();
fill(response);
DialogueContext dialogueContext = new DialogueContext();
final String s = spy.saveChatRecord(result, dialogueContext, EnumSpeakerType.BOT, 1, response, "1212");
Assert.assertEquals("123123", s);
}
protected void fill(KnClientResponse knClientResponse) {
knClientResponse.setOpenId(Md5.MD5("随风而走的我"));
knClientResponse.setSourceId("123123");
knClientResponse.setAppId("12312312312312");
List<AnswerDto> answerList = new ArrayList<>();
{
AnswerDto first = new AnswerDto();
first.setTurnSourceType("taskbot");
first.setImageList(null);
first.setTurnItemId("I12313123123");
first.setQuestion("早上好");
first.setAnswer("亲,早上好,请问有什么可以帮助你的吗?");
first.setStatus(0);
first.setScore(200D);
first.setIntentId("I12313123");
first.setRuleName(null);
first.setQaName(null);
first.setPredictSrc("全路径匹配");
first.setMatchKeyWord(null);
answerList.add(first);
}
{
AnswerDto second = new AnswerDto();
second.setTurnSourceType("taskbot");
second.setImageList(null);
second.setTurnItemId("I12313123123");
second.setQuestion("早上好");
second.setAnswer("你好");
second.setStatus(0);
second.setScore(200D);
second.setIntentId("I12313123");
second.setRuleName(null);
second.setQaName(null);
second.setPredictSrc("全路径匹配");
second.setMatchKeyWord(null);
answerList.add(second);
}
knClientResponse.setAnswerList(answerList);
knClientResponse.setQuestion("早上好");
knClientResponse.setAnswerStatus(0);
knClientResponse.setNoticeType(0);
knClientResponse.setMessageId("11111111111");
knClientResponse.setNoticeSource(null);
}
protected void fill(BotResponse botResponse) {
botResponse.setDialogState(null);
botResponse.setResultLevel(EnumResultLevel.GOOD);
List<Map<String, String>> stateVars = new ArrayList<>();
Map<String, String> temp = new HashMap<>();
temp.put("first", "sdsdf");
stateVars.add(temp);
botResponse.setStateVars(stateVars);
botResponse.setRepository(....);
}
}
这样的测试也不依赖数据库,Dubbo服务等等数据,随时都可以启动,随时都可以进行验证的。
体验
单元测试写起来真的很麻烦,需要伪造各种各样的数据,对正确的流程,错误的流程以及边界进行测试。
写完单元测试之后,还要不断的维护测试代码。在某一次重构之后,对参数进行了大的调整,也要对测试代码进行调整。
写测试代码消耗的时间往往比写代码的时间还要长。但是带来的好处也是显而易见的。就是简单的Bug在测试阶段就会被fix掉,剩下的Bug往往是对业务的理解不到位,以及数据库数据错误导致的(这里发生在提测阶段)
参考链接
https://segmentfault.com/a/1190000006746409
https://github.com/hehonghui/mockito-doc-zh