Так с некоторая комбинация ответа @ 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 в его текстовом оформлении, и на данный момент у меня нет запасной полосы пропускания.
Jon, вы можете объяснить, почему вы хотите использовать другой R-файл? –
Мы в основном перестраиваем тесты на существующий код. Иногда некоторые вещи, такие как наша пользовательская анимационная среда, фактически потребляют ресурсы. Эта структура не была построена с учетом тестовых возможностей, и мы не хотим ее модифицировать (пока), потому что тестов нет. Самый простой подход к тестированию этого устаревшего кода заключается в том, чтобы кормить его ограниченными ресурсами только для тестирования. –
Я вижу, что вам нужно создать пользовательские тесты, но я не большой экспорт здесь –