【问题标题】:How does exactly custom Shadow objects work in Robolectric?自定义阴影对象在 Robolectric 中究竟是如何工作的?
【发布时间】:2011-08-30 21:33:15
【问题描述】:

如果我为我的 Activity 编写自定义 Shadow,并将其注册到 RobolectricTestRunner,框架是否会在启动时拦截带有我自定义 Shadow 的 Activity?

谢谢。

【问题讨论】:

    标签: android unit-testing robolectric


    【解决方案1】:

    简短的回答是否定的。

    Robolectric 对拦截的类别和仪器有选择性。在撰写本文时,唯一将被检测的类必须具有完全限定的类名,以匹配以下选择器之一:

    android.* 
    com.google.android.maps.* 
    org.apache.http.impl.client.DefaultRequestDirector
    

    Robolectric 存在的全部原因是 Android SDK jar 中提供的类在 JVM 中调用时会引发异常(即不在模拟器或设备上)。您的应用程序的 Activity 具有非“敌对”的源(在调用方法或构造函数时它可能不会引发异常)。 Robolectric 的预期目的是允许您对应用程序的代码进行测试,否则由于 SDK 的编写方式,这是不可能的。创建 Robolectric 的其他一些原因是:

    • SDK 并不总是具有允许您查询由应用程序代码操作的 Android 对象状态的方法。可以编写阴影来提供对此状态的访问。
    • Android SDK 中的许多类和方法都是最终的和/或私有的或受保护的,因此很难创建应用程序代码所需的依赖项,否则这些依赖项将可用于您的应用程序代码。

    可以清楚地更改代码以隐藏任何类。过去曾讨论过将阴影特征提取到独立库中,以帮助使用其他一些不利于测试的 api 编写测试。

    为什么要隐藏 Activity?

    【讨论】:

    • 感谢您的解释。我想隐藏我的活动的原因是因为它是由我的应用程序通过调用startActivityForResult(..) 启动的。我有这个代码:ShadowActivity shadowActivity = shadowOf(activityA); Intent startedIntent = shadowActivity.getNextStartedActivity(); ShadowIntent shadowIntent = shadowOf(startedIntent); assertThat(shadowIntent.getComponent().getClassName(), equalTo(activityB.class.getName())); 我想从 activityB 获取视图。使用本机 API,我使用了 ActivityMonitor,但我想知道如何使用 Robolectric。
    【解决方案2】:

    这在 Robolectric 2 中发生了显着变化。您可以指定 custom shadows in the configuration 而不是编写自己的 TestRunner。

    例如:

    @Config(shadows = {ShadowAudioManager.class, ShadowContextWrapper.class})
    

    【讨论】:

      【解决方案3】:

      是的,如果您将 RobolectricTestRunner 子类化,请将自定义包添加到构造函数并在 bindShadowClasses 方法中加载您的 Shadow 类。无需使用 android.* 包技巧。

      (注意:这是 robolectric-1.1)

      RobolectricTestRunner#setupApplicationState 中提供了许多可以覆盖的钩子。

      这是我对 RobolectricTestRunner 的实现。

      import org.junit.runners.model.InitializationError;
      
      import com.android.testFramework.shadows.ShadowLoggerConfig;
      import com.xtremelabs.robolectric.Robolectric;
      import com.xtremelabs.robolectric.RobolectricTestRunner;
      
      public class RoboRunner extends RobolectricTestRunner {
      
      public RoboRunner(Class<?> clazz) throws InitializationError {
          super(clazz);
          addClassOrPackageToInstrument("package.you're.creating.shadows.of");
      }
      
      @Override
      protected void bindShadowClasses() {
          super.bindShadowClasses(); // as you can see below, you really don't need this
          Robolectric.bindShadowClass(ShadowClass.class);
      }
      

      }

      您可以子类化的更多方法(来自 RobolectricTestRunner.class)

      /**
       * Override this method to bind your own shadow classes
       */
      protected void bindShadowClasses() {
      }
      
      /**
       * Override this method to reset the state of static members before each test.
       */
      protected void resetStaticState() {
      }
      
         /**
       * Override this method if you want to provide your own implementation of Application.
       * <p/>
       * This method attempts to instantiate an application instance as specified by the AndroidManifest.xml.
       *
       * @return An instance of the Application class specified by the ApplicationManifest.xml or an instance of
       *         Application if not specified.
       */
      protected Application createApplication() {
          return new ApplicationResolver(robolectricConfig).resolveApplication();
      }
      

      这是在 Robolectric TestRunner 中调用它们的地方:

       public void setupApplicationState(final RobolectricConfig robolectricConfig) {
          setupLogging();
          ResourceLoader resourceLoader = createResourceLoader(robolectricConfig);
      
          Robolectric.bindDefaultShadowClasses();
          bindShadowClasses();
      
          resourceLoader.setLayoutQualifierSearchPath();
          Robolectric.resetStaticState();
          resetStaticState();
      
          DatabaseConfig.setDatabaseMap(this.databaseMap);//Set static DatabaseMap in DBConfig
      
          Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
      }
      

      【讨论】:

        【解决方案4】:

        作为更新,我已经能够创建自己的类的影子,只要在任何可能的加载器作用于该类之前小心绑定影子类。因此,按照说明,在我所做的 RoboRunner 中:

        @Override protected void bindShadowClasses() {
            Robolectric.bindShadowClass(ShadowLog.class);
            Robolectric.bindShadowClass(ShadowFlashPlayerFinder.class);
        }
        

        我有没有提到我有点作弊?上面的原始答案(当然)是正确的。所以我将它用于我的真实课程:

        package android.niftyco;
        
        public class FlashPlayerFinder {
          .. . 
        

        正如人们所预料的那样,我的模拟(影子)在我的测试包中:

        package com.niftyco.android.test;
        
        @Implements(FlashPlayerFinder.class)
        public class ShadowFlashPlayerFinder {
            @RealObject private FlashPlayerFinder realFPF;
        
            public void __constructor(Context c) {
                //note the construction
            }
        
            @Implementation
            public boolean isFlashInstalled() {
                System.out.print("Let's pretend that Flash is installed\n");
                return(true);
            }
        }
        

        【讨论】:

          【解决方案5】:

          可能会迟到,但从这里:org.robolectric.bytecode.Setup,您可能会找到有关检测哪些类的更多详细信息。

            public boolean shouldInstrument(ClassInfo classInfo) {
              if (classInfo.isInterface() || classInfo.isAnnotation() || classInfo.hasAnnotation(DoNotInstrument.class)) {
                return false;
              }
          
              // allow explicit control with @Instrument, mostly for tests
              return classInfo.hasAnnotation(Instrument.class) || isFromAndroidSdk(classInfo);
            }
          
            public boolean isFromAndroidSdk(ClassInfo classInfo) {
              String className = classInfo.getName();
              return className.startsWith("android.")
                  || className.startsWith("libcore.")
                  || className.startsWith("dalvik.")
                  || className.startsWith("com.android.internal.")
                  || className.startsWith("com.google.android.maps.")
                  || className.startsWith("com.google.android.gms.")
                  || className.startsWith("dalvik.system.")
                  || className.startsWith("org.apache.http.impl.client.DefaultRequestDirector");
            }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-01-04
            • 1970-01-01
            • 2023-04-01
            • 2011-06-26
            • 2021-08-15
            相关资源
            最近更新 更多