结合@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;
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 );
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 {
return Joiner.on( File.separator ).join( info.prefix, info.type );
}
}
protected String getFlavor( Config config ) {
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;
}
}
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
输出目录中),因此我在此添加了一些备用逻辑测试路径。如果存在构建变体/类型组合目录,则使用它;否则回退到只有类型。我还必须在资产周围添加第二层逻辑,因为它发现了一个构建/中间资产目录,并尝试使用它,而能够起作用的目录位于bundles文件夹中。
实际上使我能够使用测试资源的是这个派生运行程序:
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();
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.whatever的值,而我现在没有多余的带宽来做这件事。