虽然直接操作DB能更容易验证DAO层的正确性,但是也容易被线下数据库的脏数据污染,导致单测无法通过的问题。笔者以前遇到直连数据库的单测代码,经常改个5分钟代码,数据库里脏数据清一个小时。第二就是集成测试需要启动整个应用的容器,违背了提高效率的初衷。
如果实在要测DAO层的正确性,可以整合H2嵌入式数据库。这个网上教程非常多,不再赘述。 3、单测里时间相关的内容
笔者曾经在工作中遇到过一个极端case,一个CI平时都正常运行,有一次深夜发布, CI跑不过,后来经过第二天check才发现有前人在单测中取了当前时间,在业务逻辑中含有夜间逻辑(夜间消息不发),导致了CI无法通过。那么时间在单测中要如何处理呢?
在使用Mockito时,可以使用mock(Date.class)来模拟日期对象,然后使用when(date.getTime()).thenReturn(time)来设置日期对象的时间。
如果你使用了calendar.getInstance(),如何获取当前时间?Calendar.getInstance()是static方法,无法通过Mockito进行mock。需要引入powerMock,或者升级到mockito 4.x才能支持:
@RunWith(PowerMockRunner.class)
@PrepareForTest({Calendar.class, ImpServiceTest.class})
public class ImpServiceTest {
@InjectMocks
private ImpService impService = new ImpServiceImpl();
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
Calendar now = Calendar.getInstance();
now.set(2022, Calendar.JULY, 2 ,0,0,0);
PowerMockito.mockStatic(Calendar.class);
PowerMockito.when(Calendar.getInstance()).thenReturn(now);
}
}
4、final类,static类等的单元测试
如第3点提到的calendar的例子,static类的mock需要mockito4.x的版本。否则就要引入powermock,powermock不兼容mockito3.x版本,不兼容mockito 4.x版本。由于老的应用引入了非常多的mockito3.x的版本,直接使用mockito4.x对final和static类进行mock需要排包。实践中看,[url=]JUnit[/url]、Mockito、Powermock三者之间的版本号有兼容性问题,可能会出现java.lang.NoSuchMethodError,需要根据实际的情况选择版本进行mock。
但是在新项目立项的时候,要确定好使用的mockito和junit版本,是否引入powermock等框架,确保环境稳定可用。老项目建议不要大规模改动mockito和powermock的版本,容易排包排到怀疑人生。 5、应用启动报 Can not load this fake sdk class 的异常
这是因为阿里的tair,metaq基于pandora容器的,fake-sdk默认是pandora模块类加载加载的。具体原理可以参考下图:
解决方案1,引入pandoraboot环境。
@RunWith(PandoraBootRunner.class)
这样其实减慢了单测的运行速度,是违背了高效性原理的。但是相比较运行整个容器,运行pandora容器的时间大概在10s左右,还是能够容许的。
那么有没有不让pandoraboot起来,纯mock的方法。我个人认为mock要比ut更优先 ,特别是有些外部依赖,经常迁移或者下线,可能改了1行代码,需要修1个小时测试用例。tair,lindorm等中间件也没有办法本地起环境进行mock,直接依赖外部资源非常不优雅。 解决方案2,直接mock
以tair为例:
@RunWith(PowerMockRunner.class)
@PrepareForTest({DataEntry.class})
public class MockTair {
@Mock
private DataEntry dataEntry;
@Before
public void hack() throws Exception {
//solve it should be loaded by Pandora Container. Can not load this fake sdk class. please refer to http://gitlab.alibaba-inc.com/mi ... dora-boot/wikis/faq for the solution
PowerMockito.whenNew(DataEntry.class).withNoArguments().thenReturn(dataEntry);
}
@Test
public void mock() throws Exception {
String value = "value";
PowerMockito.when(dataEntry.getValue()).thenReturn(value);
DataEntry tairEntry = new DataEntry();
//值相等
Assert.assertEquals(value.equals(tairEntry.getValue()));
}
}
//获取Listener
MessageListenerConcurrently messageListener = (MessageListenerConcurrently)metaPushConsumer.getMessageListener();
List<MessageExt> list = new ArrayList<>();
//这个需要依赖PandoraBootRunner
MessageExt messageExt = new MessageExt();
list.add(messageExt);
Event event = new Event();
event.setUserType(3);
String text = JSON.toJSONString(event);
messageExt.setBody(text.getBytes());
messageExt.setMsgId(""+System.currentTimeMillis());
//测试consumeMessage方法
messageListener.consumeMessage(list, new ConsumeConcurrentlyContext(new MessageQueue()));
doThrow(new RuntimeException()).when(dynamicService).triggerEventV2(any());
messageListener.consumeMessage(list, new ConsumeConcurrentlyContext(new MessageQueue()));
messageExt.setBody(null);
messageListener.consumeMessage(list, new ConsumeConcurrentlyContext(new MessageQueue()));
}
}