单元测试中的ParseException

发表于:2018-4-10 13:35

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:苏聪CK1    来源:51testing软件测试网采编

  1.问题说明
  最近在给代码写单元测试,遇到了一个比较有趣的问题,问题描述如下图。
  Parse Exception
  java.text.ParseException: Unparseable date: "2018-04-08T21:45:00+08:00"
  这是一个日期解析的错误,场景是利用Mockito和JUnit进行Android的单元测试时发生的。之所以要记录这个问题是因为在StackoverFlow上并没有看到太好的solution,所以我调查后成文,记录一下。
  2.问题原因
  问题的root在这里(方法调用顺序就是代码罗列顺序):
  // MeetingListBasicPresenterTest.java
  // 在这个测试类的setup()方法中,需要创建一个MeetingInfoProxy的对象
  // 因为MeetingInfoProxy是代理类,所以需要代理一个meeting对象,所以在inject(...)方法中传入一个meeting对象。
  ...
  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
    PowerMockito.mockStatic(TextUtils.class, MeetingListBasicPresenter.class, DateFormat.class, Util.class);
    meetingProxy = MeetingInfoProxy.newInstance();
    meetingProxy.inject(recoverItem);
  }
  // MeetingInfoProxy.java
  // 在MeetingInfoProxy的inject方法中,
  public void inject(@NonNull ZRCMeetingListItem sourceData) {
    String st = sourceData.getStartTime();
    String et = sourceData.getEndTime();
    MeetingState ms = getMeetingState(st, et);
  }
  public MeetingState getMeetingState(String startTime, String endTime) {
    ...
    try {
          currentDate.setTime(System.currentTimeMillis());
          Date startDateTime = formatter.parse(startTime.replaceAll("Z$", "+00:00"));
          Date endDateTime = formatter.parse(endTime.replaceAll("Z$", "+00:00"));
         } catch(Exception e) {
             ...
         }
   ...
  }
  问题就出在formatter调用parse的时候,程序正常在真机上运行没有任何问题,但是在本地的JVM上运行对应单元测试就会抛异常。
  3.问题分析
  发现这个问题之后,我的第一反应是没有办法来解决,只好去查。看了几个帖子,没有什么太好的方案,那么只好又重头出发,在报错的地方找找原因。
  我查的第一条信息是formatter中的pattern是否与startTime的对应,也就说它能否正确的格式化时间。
  private final static String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZZZ";
  看到我的pattern之后,我马上联想到StackoverFlow上的一个solution,我便去Android官方文档去查每个解析token对应的含义。果不其然,问题发生在Z这个token上。我们来看看到底是什么问题:
  zToken.png
  javaZToken.png
  我在这里摆上了两份文档,上面的是Google Android的SimpleDateFormatdoc,下面的是Oracle的Java的SimpleDateFormatdoc。
  我在Android Platform和Java Platform利用同一段代码分别作了实验:
  try {
    String pattern = "Z";
    String tz = "+08:00";
    SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault());
    Date d = sdf.parse(tz);
  } catch (ParseException e) {
    ...
  }
  测试结果是:在Android平台上运行不会出现问题,在Java平台上运行一定会报错。
  这就很奇怪了。接着我去查parse方法的源码,对应Java和Android的版本都看了一遍,果然有收货!在android.jar包中的SimpleDateFormat.java类的subParseNumericZone方法中,有这样一段解释:
  Android subParseNumberZone(...)
  相信大家看到这里就应该明白了为什么在Android和Java的平台上测试结果不相同。我在下面详尽的解释一下:
  问题的起因是TimeZone与pattern的格式不匹配。上文给出的pattern是格式化TimeZone的,内容是Z,这意味着Z可以格式化那些RFC 822 TimeZone的时区字符串。RFC 822 TimeZone的模板是:<i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>,例如:“-0800”,
  Sign :+ / -
  TwoDigitHours:08
  Minutes:00
  也就是说,严格来说以RFC 822 TimeZone去格式化时区的话,不应该包含中间的冒号(:),所以我们在Java平台上运行上面的那一段代码的时候会抛出ParseException,因为在解析的时候遇到了不符合pattern的字符。而对于Android平台来说,不出错的原因在于SimpleDateFormat.java在android.jar(我使用的是 API 26的jar包)重新修改了subParseNumbericZone方法,虽然Z对应的RFC 822 TimeZone中不允许有冒号出现,但是如果你的TimeZone字符串中仍然有冒号,这也是允许的,并不会抛出异常,解析过程会继续向下执行,并不会break掉,而OpenJDK的版本则会中断解析并返回一个index为0的值,这在上图中展示的注释中得到了验证。而无论是Unit Test还是Java Platform,SimpleDateFormat.java都是来自于OpenJDK的版本,所以这种带冒号的TimeZone字符串是无法完成格式化的。
  以上就是问题出现的原因,如果您想了解更加详细的内容,可以去阅读文档和源码。
  4.解决方案
  解决方案有如下几种:
  1.如果你是在写Unit Test,像我给code写单元测试的话,实际的代码找已经指定了pattern就是Z,那么最好的方案就是将测试的TimeZone字符串改写成+0800,去掉中间的冒号,这样就严格遵守了Z的格式化。
  2.如果可以修改源码,那么也可以调整Pattern,使用X也可以,因为X代表的是ISO 8601时区标准,它支持的模式更多一些,不过最大的限制就是Android的API了,要求是24+。



上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号