4

У меня есть проект библиотеки с использованием robolectric 3.0 на API21, с com.android.tools.build:gradle:1.3.1.Robolectric with test.R.java

Я хочу использовать тестовые ресурсы (как будто под src/androidTest/res/...), а именно com.mypackage.test.R.java (в отличие от com.mypackage.R.java для производства) в robolectric испытаний.

То, что я до сих пор:

структура каталогов

src/ 
    main/ 
    java 
    res 
    test/ 
    java 
    // no res here because it's not picked up 
    androidTest/ 
    res // picked up by androidTest build variant to generate test.R.java 

Тогда в build.gradle:

android { 
    compileSdkVersion 21 
    buildToolsVersion = "22.0.1" 

    defaultConfig { 
    minSdkVersion 15 
    targetSdkVersion 21 
    } 

    sourceSets { 
    test { 
     java { 
      srcDir getTestRJavaDir() 
     } 
    } 
    } 
} 

def private String getTestRJavaDir(){ 
    def myManifestRoot = (new XmlParser()).parse("${project.projectDir}/src/main/AndroidManifest.xml") 
    def myPackageNamespace = [email protected] 
    def myPackagePath = myPackageNamespace.replaceAll("\\.", "/") 

    return "${project.buildDir}/generated/source/r/androidTest/debug/${myPackagePath}/test" 
} 

afterEvaluate { project -> 
    [tasks.compileDebugUnitTestSources, tasks.compileReleaseUnitTestSources]*.dependsOn("compileDebugAndroidTestSources") 
} 

Мои тесты успешно компилировать с test.R.java ,

Однако, во время выполнения они терпят неудачу, потому что robolectric теперь не находит мои файлы активов, поскольку они теперь находятся в ${project.buildDir}/intermediates/assets/androidTest/debug, тогда как раньше они находились в ${project.buildDir}/intermediates/assets/debug. Мое подозрение в том, что robo также не сможет найти файлы ресурсов, потому что они также были перемещены в каталог androidTest (вариант сборки?).

Итак, два вопроса: 1) есть ли лучший способ сделать это? 2) если нет, как я могу сказать robolectric, где искать файлы активов?

Я пробовал @Config(assetDir="build/intermediates/assets/androidTest/debug") и @Config(assetDir="../build/intermediates/assets/androidTest/debug") безрезультатно.

+0

Jon, вы можете объяснить, почему вы хотите использовать другой R-файл? –

+0

Мы в основном перестраиваем тесты на существующий код. Иногда некоторые вещи, такие как наша пользовательская анимационная среда, фактически потребляют ресурсы. Эта структура не была построена с учетом тестовых возможностей, и мы не хотим ее модифицировать (пока), потому что тестов нет. Самый простой подход к тестированию этого устаревшего кода заключается в том, чтобы кормить его ограниченными ресурсами только для тестирования. –

+0

Я вижу, что вам нужно создать пользовательские тесты, но я не большой экспорт здесь –

ответ

4

Вы можете создать собственный тест roboletric бегуна, как:

public class CustomRobolectricGradleTestRunner extends RobolectricGradleTestRunner { 

    public CustomRobolectricGradleTestRunner(Class<?> klass) throws InitializationError { 
     super(klass); 
    } 

    // Fix for the NotFound error with openRawResource() 
    @Override 
    protected AndroidManifest getAppManifest(Config config) { 
     String manifest = "src/main/AndroidManifest.xml"; 
     String res = String.format("../app/build/intermediates/res/merged/%1$s/%2$s", BuildConfig.FLAVOR, BuildConfig.BUILD_TYPE); 
     String asset = "src/test/assets"; 
     return new AndroidManifest(Fs.fileFromPath(manifest), Fs.fileFromPath(res), Fs.fileFromPath(asset)); 
    } 
} 

В переменной актив можно определить что-то вроде «../build/intermediates/assets/androidTest/debug»

+0

Это почти сработало. Вы дали мне идею, поэтому я даю вам кредит, но я тоже отправлю свой собственный ответ. –

2

Так с некоторая комбинация ответа @ motou и сообщение this, я разработал решение, которое просто сорт сломанный, а не полностью сломанный.

Вот мой набор тестовых бегунов.

Этот первый испытательный бегун был необходим только для того, чтобы исправить мои ранее работающие тесты после сбрасывания с моей градирникой.

import com.google.common.base.Joiner; 

import org.junit.runners.model.InitializationError; 
import org.robolectric.RobolectricGradleTestRunner; 
import org.robolectric.annotation.Config; 
import org.robolectric.manifest.AndroidManifest; 
import org.robolectric.res.FileFsFile; 
import org.robolectric.res.FsFile; 
import org.robolectric.util.Logger; 
import org.robolectric.util.ReflectionHelpers; 

import java.io.File; 

/** 
* Extension of RobolectricGradleTestRunner to provide more robust path support since 
* we're kind of hacking androidTest resources into our build which moves stuff around. 
* 
* Most stuff is copy/pasted from the superclass and modified to suit our needs (with 
* an internal class to capture file info and a builder for clarity's sake). 
*/ 
public class AssetLoadingRobolectricGradleTestRunner extends RobolectricGradleTestRunner { 

    public AssetLoadingRobolectricGradleTestRunner(Class<?> klass) throws InitializationError { 
    super(klass); 
    } 

    private static final String BUILD_OUTPUT = "build/intermediates"; 

    @Override 
    protected AndroidManifest getAppManifest(Config config) { 
    if (config.constants() == Void.class) { 
     Logger.error("Field 'constants' not specified in @Config annotation"); 
     Logger.error("This is required when using RobolectricGradleTestRunner!"); 
     throw new RuntimeException("No 'constants' field in @Config annotation!"); 
    } 

    final String type = getType(config); 
    final String flavor = getFlavor(config); 
    final String packageName = getPackageName(config); 

    final FileFsFile res; 
    final FileFsFile assets; 
    final FileFsFile manifest; 

    FileInfo.Builder builder = new FileInfo.Builder() 
     .withFlavor(flavor) 
     .withType(type); 

    // res/merged added in Android Gradle plugin 1.3-beta1 
    if (FileFsFile.from(BUILD_OUTPUT, "res", "merged").exists()) { 
     res = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "res", "merged") 
          .build())); 
    } else if (FileFsFile.from(BUILD_OUTPUT, "res").exists()) { 
     res = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "res") 
          .build())); 
    } else { 
     res = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "bundles") 
          .build()), 
      "res"); 
    } 

    FileFsFile tmpAssets = null; 
    if (FileFsFile.from(BUILD_OUTPUT, "assets").exists()) { 
     tmpAssets = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "assets") 
          .build())); 
    } 

    if (tmpAssets == null || !tmpAssets.exists()) { 
     assets = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "bundles") 
          .build()), 
      "assets"); 
    } else { 
     assets = tmpAssets; 
    } 

    if (FileFsFile.from(BUILD_OUTPUT, "manifests").exists()) { 
     manifest = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "manifests", "full") 
          .build()), 
      "AndroidManifest.xml"); 
    } else { 
     manifest = FileFsFile.from(
      getPathWithFlavorAndType(
        builder.withPrefix(BUILD_OUTPUT, "bundles") 
          .build()), 
      "AndroidManifest.xml"); 
    } 

    Logger.debug("Robolectric assets directory: " + assets.getPath()); 
    Logger.debug(" Robolectric res directory: " + res.getPath()); 
    Logger.debug(" Robolectric manifest path: " + manifest.getPath()); 
    Logger.debug(" Robolectric package name: " + packageName); 
    return getAndroidManifest(manifest, res, assets, packageName); 
    } 

    protected String getType(Config config) { 
    try { 
     return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE"); 
    } catch (Throwable e) { 
     return null; 
    } 
    } 

    private String getPathWithFlavorAndType(FileInfo info) { 
    FileFsFile typeDir = FileFsFile.from(info.prefix, info.flavor, info.type); 
    if (typeDir.exists()) { 
     return typeDir.getPath(); 
    } else { 
     // Try to find it without the flavor in the path 
     return Joiner.on(File.separator).join(info.prefix, info.type); 
    } 
    } 

    protected String getFlavor(Config config) { 

    // TODO HACK! Enormous, terrible hack! Odds are this will barf our testing 
    // if we ever want multiple flavors. 
    return "androidTest"; 
    } 

    protected String getPackageName(Config config) { 
    try { 
     final String packageName = config.packageName(); 
     if (packageName != null && !packageName.isEmpty()) { 
     return packageName; 
     } else { 
     return ReflectionHelpers.getStaticField(config.constants(), "APPLICATION_ID"); 
     } 
    } catch (Throwable e) { 
     return null; 
    } 
    } 

    // We want to be able to override this to load test resources in a child test runner 
    protected AndroidManifest getAndroidManifest(FsFile manifest, FsFile res, FsFile asset, String packageName) { 
    return new AndroidManifest(manifest, res, asset, packageName); 
    } 

    public static class FileInfo { 

    public String prefix; 
    public String flavor; 
    public String type; 

    public static class Builder { 

     private String prefix; 
     private String flavor; 
     private String type; 

     public Builder withPrefix(String... strings) { 
     prefix = Joiner.on(File.separator).join(strings); 
     return this; 
     } 

     public Builder withFlavor(String flavor) { 
     this.flavor = flavor; 
     return this; 
     } 

     public Builder withType(String type) { 
     this.type = type; 
     return this; 
     } 

     public FileInfo build() { 
     FileInfo info = new FileInfo(); 
     info.prefix = prefix; 
     info.flavor = flavor; 
     info.type = type; 
     return info; 
     } 
    } 
    } 


} 

Оперативник вещь в этом классе является то, что я отменяю аромат с «androidTest», чтобы заставить глядя внутрь androidTest построить каталог выходов, который где все получает положенное в тот момент, что я заставляю compileDebugAndroidTestSources как обязательное условие задачи так Я могу включить test.R.java на мой путь компиляции.

Конечно, это разрывает кучу других вещей (особенно потому, что у версии buildtype нет ничего внутри каталога выходов androidTest), поэтому я добавил некоторую резервную логику в тестировании путей здесь. Если существует список вариантов сборки/типа, используйте его; другие возвращаются к типу в одиночку. Я также должен был добавить второй уровень логики вокруг активов, потому что он находил каталог build/intermediates/assets и пытался использовать его, в то время как тот, который работал бы, находился в папке пакетов.

Дело в том, что на самом деле позволило мне использовать тестовые ресурсы был этот другой, полученный бегун:

import com.mypackage.BuildConfig; 

import org.junit.runners.model.InitializationError; 
import org.robolectric.RobolectricGradleTestRunner; 
import org.robolectric.annotation.Config; 
import org.robolectric.manifest.AndroidManifest; 
import org.robolectric.res.Fs; 
import org.robolectric.res.FsFile; 
import org.robolectric.res.ResourcePath; 

import java.util.List; 

public class TestResLoadingRobolectricGradleTestRunner extends AssetLoadingRobolectricGradleTestRunner { 

    public TestResLoadingRobolectricGradleTestRunner(Class<?> klass) throws InitializationError { 
    super(klass); 
    } 

    @Override 
    protected AndroidManifest getAndroidManifest(FsFile manifest, FsFile res, FsFile asset, String packageName) { 
    return new AndroidManifest(manifest, res, asset, packageName) { 

     public String getTestRClassName() throws Exception { 
     getRClassName(); // forces manifest parsing to be called 
     // discard the value 

     return getPackageName() + ".test.R"; 
     } 

     public Class getTestRClass() { 
     try { 
      String rClassName = getTestRClassName(); 
      return Class.forName(rClassName); 
     } catch (Exception e) { 
      return null; 
     } 
     } 

     @Override 
     public List<ResourcePath> getIncludedResourcePaths() { 
     List<ResourcePath> paths = super.getIncludedResourcePaths(); 

     paths.add(new ResourcePath(getTestRClass(), getPackageName(), Fs.fileFromPath("src/androidTest/res"), getAssetsDirectory())); 
     return paths; 
     } 
    }; 
    } 
} 

Конечно, вы спрашиваете, «почему бы не просто всегда использовать полученный тест бегун? " и ответ «потому что он каким-то образом нарушает загрузку ресурсов Android по умолчанию». Пример этого, с которым я столкнулся, заключался в том, что шрифт TextView по умолчанию возвращается обратно, потому что он не может найти значение по умолчанию для семейства шрифтов во время инициализации из темы.

Я чувствую, что я действительно близко к чему-то, что работает полностью, здесь, но мне нужно больше узнать, почему Android внезапно не может найти значение в com.android.internal.R.whatever в его текстовом оформлении, и на данный момент у меня нет запасной полосы пропускания.