如何对JSON解析进行单元测试

23

我正在开发一个Android应用,其中我从Web服务下载JSON数据。负责解析数据的类大致如下:

public class JsonCourseParser implements CourseParser {

    public Course parseCourse(String courseData) {
        Course result;

        try {
            JSONObject jsonObject = new JSONObject(courseData);

            ...

            result = new Course("foo", "bar");
        } catch (JSONException e) {
            result = null;
        }

        return result;
    }
}

这个构建非常好,并且在我从我的应用程序内调用parseCourse()时有效,但是当我尝试在单元测试中测试此方法时,我会收到以下异常:

这个代码可以正常编译,而且当我在我的应用程序中调用parseCourse()方法时也能够正常工作。但是当我尝试在一个单元测试中测试这个方法时,我会得到以下的异常信息:

java.lang.NoClassDefFoundError: org/json/JSONException
    at JsonCourseParserTest.initClass(JsonCourseParserTest.java:15)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.ClassNotFoundException: org.json.JSONException
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    ... 16 more

看起来,Android框架自带的org.json包中的类在Java中默认情况下是不可用的。

有没有办法解决这个问题,以便我可以对解析JSON的类进行单元测试?

5个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
50

你只需要在 build.gradle 文件的 dependency 部分添加以下行:

testImplementation 'org.json:json:20180813'

请注意,为使此工作正常,您还需要使用Android Studio 1.1或更高版本和至少构建工具版本22.0.0或更高版本!

testImplementation表示该依赖项作为测试依赖项包含在内,仅在编译执行单元测试时使用。提供的jar文件将不会包含在您的APK中,只用于替换org.json包中缺失的类。


谢谢你的分享,Xaver!我开始觉得没有其他人尝试使用最新的构建工具进行单元测试了。 - Billy Lazzaro

19

编辑:请看结尾处的更新答案

我找到了这个问题的答案。虽然它没有解决我的问题,但至少它解释了为什么会有问题。

首先,我犯了一个错误的假设,即认为从org.json包导入的JSONObject是JRE的一部分。它不是——在Android项目中,它位于android.jar中(典型的“duh”时刻)。

这个发现提高了我的自信心。这很容易通过在我的单元测试项目中添加对android.jar的引用来解决——或者至少我曾经这样认为。但这样做只会在运行我的测试时给我另一个错误:

java.lang.RuntimeException: Stub!
    at org.json.JSONObject.<init>(JSONObject.java:8)
    ...

至少这让我有更多的谷歌搜索内容。然而,我发现的并不是很令人鼓舞(又是一个经典的“duh”时刻)...

这篇博客很好地描述了问题:为什么Android尚未准备好进行TDD,以及我如何尝试。如果你不想读整篇文章,简要说明如下:

问题在于,SDK提供的android.jar被存根化,没有实现代码。似乎期望的解决方案是在模拟器或真实手机上运行单元测试。

进一步搜索时,我发现了几篇文章、博客和在SO上的相关问题。我会在这里添加一些链接,供那些正在寻找答案的人参考:

如果你看一下,还有很多。

在许多这些链接中,有关于在Android中进行单元测试的几个建议/解决方案/替代方法,但我不会试图基于此提供一个好的答案(因为显然我对Android开发还有太多不了解)。如果有人有任何好的技巧,我会很乐意听到他们的意见 :)

更新:
经过一番尝试,我实际上成功找到了一个解决这个特定问题的方法。之所以我一开始没有尝试这种方法,是因为我觉得在我的Android应用程序中包含“普通”的Java库可能会有问题。我试了很多不同的方法来解决我的问题,所以我想也试试这个方法——结果它真的起作用了!方法如下:

  • 从这里http://www.json.org/java/index.html下载“真正的”org.json软件包的源代码
  • 接下来,我编译了源代码并将其打包成了自己的json.jar
  • 我将新创建的json.jar添加到了我的项目的构建路径中(我的Android应用程序的主项目,而不是测试项目)

我的代码没有任何改变,我的测试也没有任何改变,只是添加了这个库。一切都可以工作,我的Android应用程序和我的单元测试。

我还测试了在调试模式下逐步执行代码时,当调试Android应用程序时,JsonCourseParser中的JSONObject是从Android SDK(android.jar)获取的,但当调试我的单元测试时,它是从我的json.jar获取的。不确定这是否意味着在构建我的应用程序时没有包含json.jar,还是运行时智能地选择正确的库来使用。也不确定这个额外的库是否会对我的最终应用程序产生其他影响。我想只有时间才能告诉我们……


感谢您对问题进行详细分析和描述!对于这个问题,时间给您带来了什么启示吗? - paweloque
从源代码构建jar对我很有效!安卓没有在SDK中包含这个似乎真的很傻。 - lifeson106

2
我正在使用Björn Quentin的Unmock Gradle插件,它允许您用另一个android.jar(例如Robolectric)中的真实版本替换默认android.jar中的存根类。

keepStartingWith "org.json." - Dmitry Gryazin

0

我曾遇到同样的问题,但找到了更好的解决方案。框架 Roboelectric 可以胜任此项工作。起初我只是将 roboelectric.jar 作为外部库(因为我不使用 Maven)添加到我的 Java JUnit 测试项目的构建路径中,并添加了 @RunWith(RobolectricTestRunner.class) 作为“类注释”。但后来我出现了 NoClassDefFoundError: android/net/Uri 的问题。在将与相应 Android 项目一起使用的 android.jar 自身添加到构建路径中之后(尽管Android库与其android.jar已经成为构建路径的一部分),就可以解决这个问题。


-1

我在我的JSON测试中碰到了这个确切的问题。您的更新答案让我尝试了一种更简单的方法,该方法适用于我的项目。它允许我在Eclipse中从非Android类中运行Junit测试,使用“真实”的org.json JAR:

  1. 从此处(或其他位置)下载org.json的json.jar:http://mvnrepository.com/artifact/org.json/json
  2. 为你的项目添加一个名为'libs-test'的文件夹,将json.jar文件复制到其中
  3. 在Eclipse中打开:运行/运行配置,选择JUnit/YourTestClass
  4. 在“类路径”选项卡上,从引导条目中删除“Google API”
  5. 单击“添加JAR文件”,浏览到/libs-test/json.jar并添加它。
  6. 单击“关闭”
  7. 运行您的测试

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,