Итак, с некоторой комбинацией ответа @motou и этого сообщения я разработал решение, которое просто немного сломано, а не полностью сломано.
Вот мой набор тестовых бегунов.
Этот первый запуск тестов был необходим только для того, чтобы исправить мои ранее работающие тесты после того, как я испортил сборку gradle.
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
в свою компиляцию дорожка.
Конечно, это затем ломает кучу других вещей (особенно потому, что тип сборки релиза не имеет ничего внутри каталога androidTest
outputs), поэтому я добавил здесь некоторую резервную логику в тестировании пути. Если каталог комбинации варианта/типа сборки существует, используйте его; иначе вернуться к типу в одиночку. Мне также пришлось добавить второй уровень логики вокруг ресурсов, потому что он находил каталог 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 по умолчанию возвращает значение null, потому что он не может найти значение по умолчанию для семейства шрифтов во время инициализации из темы.
Я чувствую, что я действительно близок к чему-то, что работает полностью, здесь, но мне нужно подробнее разобраться, почему Android внезапно не может найти значение на com.android.internal.R. что угодно в его текстовой тематике, и у меня сейчас нет свободной пропускной способности для этого.
person
Jon O
schedule
09.11.2015