如何检测 android app 是否 运行 UI 用 Espresso 测试

How to detect whether android app is running UI test with Espresso

我正在为 Android 编写一些 Espresso 测试。我 运行 正在解决以下问题:

为了让某个测试用例正常运行,我需要禁用应用程序中的某些功能。因此,在我的应用程序中,我需要检测我是否正在 运行ning Espresso 测试,以便我可以禁用它。但是,我不想使用 BuildConfig.DEBUG 因为我不想在调试版本中禁用这些功能。另外,我想避免创建一个新的 buildConfig 以避免创建太多构建变体(我们已经定义了很多口味)。

我正在寻找一种方法来定义 buildConfigField 以进行测试,但我在 Google 上找不到任何参考。

您可以为此使用 SharedPreferences。

设置调试模式:

boolean isDebug = true;

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("DEBUG_MODE", isDebug);
editor.commit();

检查是否为调试模式:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
boolean isDebug = sharedPref.getBoolean("DEBUG_MODE", false);

if(isDebug){
    //Activate debug features
}else{
    //Disable debug features
}

结合CommonsWare的评论。这是我的解决方案:

我定义了一个AtomicBoolean变量和一个函数来检查它是否是运行测试:

private AtomicBoolean isRunningTest;

public synchronized boolean isRunningTest () {
    if (null == isRunningTest) {
        boolean istest;

        try {
            Class.forName ("myApp.package.name.test.class.name");
            istest = true;
        } catch (ClassNotFoundException e) {
            istest = false;
        }

        isRunningTest = new AtomicBoolean (istest);
    }

    return isRunningTest.get ();
}

这避免了每次需要检查值时都进行 try-catch 检查,它只在您第一次调用此函数时运行检查。

结合 Commonsware 评论 + Comtaler 的解决方案,这是使用 Espresso 框架进行任何测试的方法class。

private static AtomicBoolean isRunningTest;

public static synchronized boolean isRunningTest () {
    if (null == isRunningTest) {
        boolean istest;

        try {
            Class.forName ("android.support.test.espresso.Espresso");
            istest = true;
        } catch (ClassNotFoundException e) {
            istest = false;
        }

        isRunningTest = new AtomicBoolean (istest);
    }

    return isRunningTest.get();
}

基于以上答案,以下 Kotlin 代码是等效的:

val isRunningTest : Boolean by lazy {
    try {
        Class.forName("android.support.test.espresso.Espresso")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

然后您可以检查 属性 的值:

if (isRunningTest) {
  // Espresso only code
}

我不喜欢使用在 android 上很慢的反射。我们大多数人都为依赖注入设置了 dagger2。我有一个用于测试的测试组件。以下是获取应用程序模式(测试或正常)的简要方法:

创建枚举:

public enum ApplicationMode {
    NORMAL,TESTING;
}

和一个普通的 AppModule:

@Module
public class AppModule {

    @Provides
    public ApplicationMode provideApplicationMode(){
        return ApplicationMode.NORMAL;
    }
}

创建一个像我这样的测试运行器:

public class PomeloTestRunner extends AndroidJUnitRunner {

    @Override
    public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            return super.newApplication(cl, MyTestApplication.class.getName(), context);
    }
}

不要忘记像这样在 gradle 中声明它:

defaultConfig {
testInstrumentationRunner "com.mobile.pomelo.base.PomeloTestRunner"
}

现在用覆盖方法创建 AppModule 的子class,看起来完全像这样,不要将其标记为 class 定义之上的模块:

public class TestAppModule extends AppModule{

    public TestAppModule(Application application) {
        super(application);
    }

    @Override
    public ApplicationMode provideApplicationMode(){
        return ApplicationMode.TESTING; //notice we are testing here
    }
}

现在在您在自定义测试运行程序中声明的 MyTestApplication class 中声明了以下内容:

public class PomeloTestApplication extends PomeloApplication {

    @Singleton
    @Component(modules = {AppModule.class})
    public interface TestAppComponent extends AppComponent {
        }

    @Override
    protected AppComponent initDagger(Application application) {
        return DaggerPomeloTestApplication_TestAppComponent.builder()
                .appModule(new TestAppModule(application)) //notice we pass in our Test appModule here that we subclassed which has a ApplicationMode set to testing
                .build();
    }
}

现在要使用它,只需将它注入生产代码中,就像这样:

@Inject
    ApplicationMode appMode;

因此,当您的 运行 espresso 测试时,它将测试枚举,但在生产代码中,它将是普通枚举。

ps 不是必需的,但如果您需要查看我的生产匕首如何构建图形,它是这样的并在应用程序中声明 subclass:

 protected AppComponent initDagger(Application application) {
        return DaggerAppComponent.builder()
                .appModule(new AppModule(application))
                .build();
    }

我将创建如下两个文件

src/main/.../Injection.java

src/androidTest/.../Injection.java

而在 Injection.java 中,我将使用不同的实现,或者只是一个静态变量 int。

由于 androidTest 是源集,而不是构建类型的一部分,我认为你想做的事情很难。

BuildConfig class 中的标志怎么样?

android {
    defaultConfig {
        // No automatic import :(
        buildConfigField "java.util.concurrent.atomic.AtomicBoolean", "IS_TESTING", "new java.util.concurrent.atomic.AtomicBoolean(false)"
    }
}

在你的测试中的某处添加这个 classes.

static {
    BuildConfig.IS_TESTING.set(true);
}

如果您将 JitPack 与 kotlin 一起使用。您需要更改 Espresso 的 包名称。

val isRunningTest : Boolean by lazy {
    try {
        Class.forName("androidx.test.espresso.Espresso")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

用于检查

if (isRunningTest) {
  // Espresso only code
}

这是一种为 react-native Android 应用调整公认解决方案的方法。

// MainActivity.java

// ...

  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {

      // ...

      @Override
      protected Bundle getLaunchOptions() {
        Bundle initialProperties = new Bundle();
        boolean testingInProgress;

        try {
          Class.forName ("androidx.test.espresso.Espresso");
          testingInProgress = true;
        } catch (ClassNotFoundException e) {
          testingInProgress = false;
        }

        initialProperties.putBoolean("testingInProgress", testingInProgress);

        return initialProperties;
      }
    };
  }
}

然后您将能够访问 testingInProgress 作为给予最顶层组件(通常是 App.js)的道具。从那里你可以使用 componentDidMount 或等价物来访问它并将它放入你的 Redux 商店(或你正在使用的任何东西),以便你的应用程序的其余部分可以访问它。

我们用它来触发我们应用程序中的一些逻辑,以帮助我们使用 fastlane 截取屏幕截图。

我建议在另一个 class 调用中使用初始化为 false 的布尔变量,例如 Settings.java:

private static boolean isRunningAndroidTest = false;

此布尔变量将具有以下 setter 和 getter 也在 Settings.java 中定义:

public static void setIsRunningAndroidTest(boolean isRunningAndroidTest) {
    Settings.isRunningAndroidTest = isRunningAndroidTest;
}

public static boolean getIsRunningAndroidTest() {
    return isRunningAndroidTest;
}

然后可以通过调用 Settings.java 中定义的 setter 在 androidTest 文件的开头将此 isRunningAndroidTest 变量切换为 true,如下所示:

Settings.setIsRunningAndroidTest(true);

最后,此布尔变量的实际值稍后可以通过调用其在 Settings.java 中定义的相应 getter 来在任何其他文件中检查,如下所示:

if (Settings.getIsRunningAndroidTest()) {
    // Do something in case an androidTest is currently running
} else {
    // Do something else in case NO androidTest is currently running
}