背景:某天,team内某童鞋叫我帮忙一起看个问题,原本运行正常的一个case(本人在一年前编写的),突然跑不过了,而且现象还非常诡异,搞了好久,不知道啥原因,严重影响移交进度(单测必须全过才能进行后续的发版流程)。
现象:1,本地跑单元测试能够全部通过(我们是直接执行gradle clean test),打个tag,一拿到单测服务器上跑,fail了
2,单独跑这个fail的case,居然是成功的
我的第一反应,是不是改了啥,仔细一看,只是在这个case的java文件中加了一行打印日志,没有改动任何逻辑。再根据现象1发现本地机器和单测服务器的环境不同,本地是在windows操作系统下跑的单测,而服务器则是linux。赶紧换成linux的跑了下,fail,复现了这个现象。接下来,使用ide(idea) debug这个case,居然是成功的,现象诡异!没办法,只能按照操作步骤进行调试了。我们跑ut执行的命令是gradle test,debug下gradle的执行过程,然后在有问题的地方打上断点,提示有问题的代码如下:
@Service
public class P2PTransferDetailService {
@Autowired
private P2PTransferRequestsDAO transferRequestsDAO;
...
@Transactional
public void updateRequestAndHistory(P2PTransferRequestsDTO transferRequest) {
P2PTransferRequestsHisDTO history = buildP2PTransferRequestsHis(transferRequest);
transferRequestsHisDAO.insert(history);
int count = transferRequestsDAO.updateIsProductCreated(transferRequest);//这里正常情况下应该不会有并发
if(1!=count){
throw new OptimisticLockException("update product created failed");
}
}
}
看下上面那个备注,ut跑了一整年,这里的count返回值都是1,且相关业务逻辑从未改动过。但是这次,这里的count返回0!!!,用ide单独debug这个case,count返回的值=1.这就是比较诡异的地方。我们debug下gradle test的执行过程,在if(1!=count)这个地方打个断点,执行一把,发现count的值确实为1,但是我们发现了一个问题transferRequestsDAO这个实例居然是由Mockito生成的(我们用Mockito这个工具对一些case进行mock),居然不是spring生成的实例:
问题就出在这里了!接下来我们需要找出P2PTransferDetailService中P2PTransferRequestDAO究竟是在哪个地方被替换掉了并且忘记还原。正常情况下被mock的对象在推出当前测试类的时候都会进行还原的,确保不能影响其他类的测试。经过查找代码,我们终于找到有问题的那段代码了:
public class P2PTransferDetailServiceTest {
@InjectMocks
@SpringBeanByType
private P2PTransferDetailService p2PTransferDetailService;
@MockObject(P2PTransferDetailService.class)
@Mock
private P2PTransferRequestsDAO transferRequestsDAO;
}
P2PTransferDetailService这个类被@InjectMocks,@SpringBeanByType标记,那么p2pTransferDetailService这个实例刚初始化时,其成员transferRequestsDAO会替换成Mockito生成的一实例(因为被@Mock标记了),spring容器中P2pTransferDetailService的实例中成员变量transferRequestsDAO就一直被更改了(没有被还原)。这样凡是UT跑在 上面这个case之后的ut,使用到P2pTransferDetailService中成员变量transferRequestsDAO的case都有可能失败!上面那个case失败就是这个原因,但是问题来了:为什么之前没有问题?猜测:本次加了一个日志,改变了ut执行时java文件的执行顺序。如何验证?还是debug下gradle clean test的执行过程,看下ut对应的类的执行顺序:
看上去,果然如此!那另一个问题来了,ut执行时究竟是根据什么规则进行读取的呢,读取出来的文件是根据什么规则进行排序的?或者没有排序规则?查了下相关资料,操作系统在读取某个目录下的所有文件时,不保证以特定的顺序返回,也不能保证它们按照文件名字母顺序返回。这个返回的顺序不同的操作系统间还有差异!这或许就能解释windows下ut是ok的,linux下不行。但是操作系统在读取目录下的文件时,到底默认是按照什么样的顺序返回呢?后续等自己搞明白后再来补充吧。。。。