李祥乾
风控RD & QA
Test fails on case
java.lang.AssertionError
expected: 单元测试
ACTUAL: 吐槽
风控RD & QA
Text
还是这个case
public GpsLocationInfo getInfoByLocation(Object location) {
GPSLocation gpsLocation = null;
if (location instanceof Map) {
gpsLocation = getGPSLocation((Map<String, String>) location);
} else if (location instanceof String) {
gpsLocation = getGPSLocation((String) location);
}
if (null == gpsLocation) {
return null;
}
GpsLocationInfo gpsLocationInfo = new GpsLocationInfo();
LocationCoordinate locationCoordinate = new LocationCoordinate
(gpsLocation.getLatitude(),gpsLocation.getLongitude());
try {
gpsLocationInfo = getGpsLocationInfo(locationCoordinate);
} catch (Exception e){
//deal with exception
}
return gpsLocationInfo;
}
Spock是一个JVM上基于动态语言Groovy的单元测试框架
Text
class GpsInfoServiceTest extends Specification {
def "getInfoByLocation specifications"() {
setup: "Setup the service"
GpsInfoService service = new GpsInfoService()
when: "Given an argument of a type that cannot be handled"
def result = service.getInfoByLocation(new Integer(4))
then: "It should return null"
result == null
when: "An empty string is given"
result = service.getInfoByLocation("")
then: "return null"
result == null
when: "A valid type argument is given but is invalid logically"
def argStr = "333333 -34232544"
def serviceMock = Mock(GpsInfoService)
serviceMock.getGpsLocationInfo(_ as LocationCoordinate) >> { throw new
Exception() } //This is a MOCK.
result = serviceMock.getInfoByLocation(argStr)
then: "An exception is handled internally and return null"
result == null
}
}
刻意追求高的单测覆盖率是不对的。
测试覆盖是一种“学习手段”。学习什么呢?学习为什么有些代码没有被覆盖到,以及为什么有些代码变了测试却没有失败。理解“为什么”背后的原因,程序员就可以做相应的改善和提高,相比凭空想象单元测试的有效性和代码的好坏,这会更加有效。
----测试覆盖率有什么用?
可读性差。单元测试里缺乏文字说明信息,只有assertTrue,assertEquals。
Mockito(http://mockito.org/)
@RunWith(MockitoJUnitRunner.class)
public class ServiceTest{
@Test
public void methodTest() {
//verify method was called
Service service = Mockito.mock(Service.class);
//do some tests....
//verifying
Mockito.verify(service).method(args..);
//verify method was never called
Mockito.verify(service, Mockito.times(0)).method(args..);
//verify method was called 3 times
Mockito.verify(service, Mockito.times(3)).method(args..);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(RiskService.class)
public class RiskServiceTest {
@Test
public void methodTest(){
RiskService riskService = PowerMockito.spy(new RiskService());
PowerMockito.when(riskService, "privateMethod", arg0, arg1, ...)
.thenReturn(...);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(RiskHelper.class)
public class RiskService {
@Test
public void methodTest(){
RiskService riskService = new RiskService();
PowerMockito.mockStatic(RiskHelper.class);
PowerMockito.when(RiskHelper.staticMethod(arg...)).thenReturn(...);
...
}
}
@Service
public class RiskService {
@Autowired
private FingerPrintService fingerPrintService;
public String getImsiByFingerprintDecode(String fingerprint,String version, Integer app,
Integer platform) {
String imsi = null;
if (CollectionsUtil.empty(fingerprint) || CollectionsUtil.empty(version) || (app == null)
|| (platform == null)) {
return imsi;
}
Map fingerprintDecode = fingerPrintService.decryptFingerprint(fingerprint, version, app,
platform);
if (CollectionsUtil.empty(fingerprintDecode)) {
return imsi;
}
if (fingerprintDecode.containsKey("imsi")) {
imsi = (String) fingerprintDecode.get("imsi");
}
return imsi;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("unit-test-spring-config.xml")
public class RiskServiceTest {
@Autowired
RiskService riskService;
@Autowired
FingerPrintService fingerPrintServiceMocked;
@Test
public void getImsiByFingerprintDecodeTest() {
String args = ...;
when(fingerPrintService.decryptFingerprint(args)).thenReturn(...);
//more test
}
}
<beans...>
<context:component-scan base-package="com.utest.example"/>
<bean id="fingerPrintServiceMock" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.utest.example.FingerPrintService" />
</bean>
<bean id="riskService" class="com.utest.example.RiskService">
<property name="fingerPrintService" ref="fingerPrintServiceMock" />
</bean>
</beans>
//object to test
RiskService riskService = new RiskService();
FingerPrintService fingerPrintService = MockUtil.mockField(riskService, "fingerPrintService");
when(fingerPrintService.decryptFingerprint(args)).thenReturn(...);
//or
when(fingerPrintService.decryptFingerprint(illArgs)).thenThrow(new Expcetion());
import spock.lang.Specification
class GpsInfoServiceTest extends Specification {
def "getInfoByLocation specifications"() {
setup: "Setup the service"
def serviceMock = Mock(GpsInfoService)
when: "A valid type argument is given but is invalid logically"
def argStr = "333333 -34232544"
serviceMock.getProphetGpsLocationInfo(_ as LocationCoordinate) >> { throw new
ProphetException() } //This is a MOCK.
result = serviceMock.getInfoByLocation(argStr)
then: "An exception is handled internally and return null"
result == null
}
}
public void methodToTestWithBlockingDependencies(args...) {
//...some params checking
//A external dependency code block, not easy to mock externally.
HttpConnection conn = ...
//some code to deal with results
}
参数检查 -> Http请求 -> 阻塞 -> 处理结果
只测试本函数的逻辑,应当迅速完成,不应当有任何等待、阻塞操作(IO,数据库访问,http请求等,即外部依赖)。
对外部依赖的可能的所有情况进行假设(mock),测试本函数能够做出如设计一致的反馈。
means Questions & Answering.