2010-10-16 15 views
18

Ich arbeite an einer Android-App, in der ich JSON-Daten von einem Web-Service herunterladen. Die Klasse Analysieren der Daten sieht etwa so aus:Wie Unit Test JSON Parsing

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; 
    } 
} 

Dies baut ganz gut und es funktioniert, wenn ich parseCourse() aus meiner app nennen, aber wenn ich versuche, diese Methode in einer Unit-Test zu testen, bekomme ich diese Ausnahme:

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 

Es scheint, dass die Klassen des org.json-Pakets, das mit der Android-Framework gibt es in Java nicht standardmäßig verfügbar sind.

Gibt es eine Möglichkeit, das zu beheben, damit ich die Klassen testen kann, die JSON parsen?

Antwort

46

Alles, was Sie tun müssen, ist die folgende Zeile in dem Abhängigkeitsabschnitt in Ihrem build.gradle hinzufügen:

testCompile 'org.json:json:20140107' 

Beachten Sie, dass Sie auch müssen Android Studio 1.1 oder höher verwenden und mindestens Tools Version 22.0.0 oder höher zu diesem Zweck zu arbeiten!

testCompile bedeutet, dass die Abhängigkeit enthalten ist, da eine Testabhängigkeit nur verwendet wird, wenn Ihr Projekt zum Ausführen von Komponententests kompiliert wird. Das mitgelieferte JAR wird nicht in Ihrem APK enthalten sein und wird nur zum Ersetzen der fehlenden Klassen im org.json-Paket verwendet.

+0

Danke für diesen Xaver! Ich begann zu denken, dass niemand sonst versuchte Unit Tests mit den neuesten Build-Tools zu verwenden. –

19

Edit: Antwort aktualisiert am

Ende sehe ich die Antwort auf diese gefunden habe. Es löst nicht mein Problem, aber zumindest erklärt es warum gibt es ein Problem.

Zuerst machte ich die fehlerhafte Annahme, dass die JSONObject (aus dem org.json Paket importiert) als Teil der JRE enthalten war. Es ist nicht - in einem Android-Projekt befindet sich dies in android.jar (klassische "Duh" Moment).

Diese Entdeckung erhöhte mein Selbstvertrauen ein wenig. Dies könnte leicht gelöst werden, indem ich einen Verweis auf die android.jar in meinem Unit-Test-Projekt hinzufügen - oder zumindest dachte ich für einen kurzen Moment. Dadurch gab mir so nur einen weiteren Fehler, wenn mein Test ausgeführt wird:

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

Wenigstens das gab mir etwas mehr googlen. Was ich jedoch fand, war nicht wirklich ermutigend (noch ein Klassiker "Duh" Moment) ...

Dieser Blog beschreibt das Problem ziemlich gut: Why Android isn’t ready for TDD, and how I tried anyway. Wenn Sie nicht die Mühe, die ganze Sache zu lesen, die kurze Erklärung ist wie folgt:

The problem here is that the android.jar supplied with the SDK is stubbed out with no implementation code. The solution that seems to be expected is that you should run your unit tests on the emulator or a real phone.

Wenn weiter in diesem Sinne einige googeln zu machen, ich mehrere Artikel, Blogs und auch Fragen hier auf SO in Bezug auf die gefunden Problem. Ich werde ein paar Links hier am Ende, für diejenigen hinzufügen, die suchen werden:

Und es gibt viel mehr, wenn Sie sich umsehen.

Es gibt mehrere Vorschläge/Lösung/alternative Ansätze zum Komponententest in Android in vielen dieser Links, aber ich werde nicht versuchen, eine gute Antwort darauf basierend zu machen (wie es ist offensichtlich viel zu viel Ich weiß immer noch nicht über Android-Entwicklung).Wenn jemand irgendwelche netten Tipps hat aber, werde ich froh sein, über sie zu hören :)

UPDATE:
Nach etwas mehr experimentieren, ich tatsächlich geschaffen, eine funktionierende Lösung zu diesem spezifischen Problem zu finden. Der Grund, warum ich das nicht zuerst versucht habe, war, dass ich dachte, ich hätte irgendwo gelesen, dass es problematisch wäre, "normale" Java-Bibliotheken in meine Android-App aufzunehmen. Ich habe so viele verschiedene Dinge versucht, um mein Problem zu umgehen, also dachte ich mir, ich probiere es einfach aus - und es hat wirklich funktioniert! Hier ist, wie:

  • ich die Quelle für das "echte" org.json Paket von hier heruntergeladen http://www.json.org/java/index.html
  • Next mir den Quellcode zusammengestellt und verpackt sie zusammen in meinem eigenen json.jar
  • I hinzugefügt, um die neu json.jar erstellt auf den Build-Pfad meines Projekts (das Hauptprojekt meiner Android-Anwendung, nicht das Testprojekt)

keine Änderungen an meinem Code, keine Änderungen an meinem Test, nur diese Bibliothek hinzufügen. Und alles funktioniert, sowohl meine Android-App als auch meine Unit-Tests.

Getestet habe ich auch im Debug-Modus durch den Code schrittweise, und wenn die Android Debug-App, die JSONObject in meinem JsonCourseParser abgerufen aus dem Android SDK (android.jar), aber wenn meine Unit-Test-Debugging ist es geholt aus meine eigene json.jar. Nicht sicher, ob das bedeutet, dass die json.jar nicht enthalten ist, wenn meine App erstellt wird, oder wenn die Laufzeit intelligent auswählt die richtige Bibliothek zu verwenden. Auch nicht sicher, ob diese zusätzliche Bibliothek andere Auswirkungen auf meine finale App haben könnte. Ich denke, nur die Zeit wird es zeigen ...

+0

danke für Ihre detaillierte Analyse und Beschreibung des Problems! Und hat dir die Zeit etwas für dieses Problem gesagt? – paweloque

+0

Der Bau des Glases aus der Quelle hat für mich funktioniert! Es scheint wirklich albern, dass Android dies nicht im SDK irgendwo enthält – lifeson106

0

Ich hatte das gleiche Problem, aber ich fand einen besseren Weg. Das Framework Roboelectric erledigt den Job. Zuerst habe ich die roboelectric.jar als externe Bibliothek (weil ich Maven nicht verwende) zum Build-Pfad meines Java JUnit-Testprojekts hinzugefügt und als "Klassen-Annotation" habe ich @RunWith(RobolectricTestRunner.class) hinzugefügt. Aber dann habe ich eine NoClassDefFoundError: android/net/Uri. Nachdem ich noch die android.jar selbst hinzugefügt habe, die vom entsprechenden Android-Projekt verwendet wurde (obwohl die Android-Bibliothek mit ihrer android.jar bereits Teil meines Build-Pfades war), funktioniert es.

-1

Ich hatte dieses genaue Problem in meinen JSON-Tests. Ihre aktualisierte Antwort führte mich zu einem einfacheren Ansatz, der für mein Projekt funktionierte. Es erlaubt mir, JUnit-Tests laufen zu lassen, die 'echte' org.json JAR verwenden, auf meinen Nicht-Android-Klassen aus Eclipse:

  1. Herunterladen org.json dem json.jar von hier (oder wo auch immer): http://mvnrepository.com/artifact/org.json/json
  2. eines Ordners 'libs-Test' zu einem Projekt hinzufügen, die json.jar Datei kopieren
  3. In Eclipse Open: Run/Run-Konfigurationen wählen JUnit/YourTestClass
  4. auf der Classpath Registerkarte entfernen " Google APIs "aus Bootstrap-Einträgen
  5. Klicken Sie auf" Add JARs ", navigieren Sie zu /libs-test/json.jar und fügen Sie es hinzu.
  6. Klicken Sie auf „Schließen“
  7. Führen Sie Ihre Tests
2

Ich benutze Björn Quentin Unmock Gradle Plugin, mit dem Sie stubbed-Klassen in der Standard-Android.jar mit echten Versionen von einem anderen android.jar (z. B. Robolectric) ersetzen können.

+0

'keepStartingWith" org.json. "' –