Android Instrumentation测试:如何在主机而非设备上运行代码?
Android Instrumentation测试中使用Testcontainers的解决方案
问题背景
在Android Instrumentation测试中尝试用Testcontainers启动Web服务器时,因测试代码运行在设备/模拟器上,无法访问主机的Docker守护进程,抛出以下异常:
java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
可行解决方案
方案1:利用Testcontainers Desktop的Android适配
Testcontainers Desktop针对Android测试场景做了优化,允许容器在主机运行,同时让设备通过专用网络访问容器。
- 确保已安装Testcontainers Desktop并启用Android支持
- 修改测试代码,调整容器配置与访问路径:
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertTrue; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.test.ext.junit.runners.AndroidJUnit4; @RunWith(AndroidJUnit4.class) public class MyIntegrationTest { private static GenericContainer<?> webServer; private static String containerUrl; @BeforeClass public static void startServer() { webServer = new GenericContainer<>(DockerImageName.parse("nginx:latest")) .withExposedPorts(80) .waitingFor(Wait.forHttp("/")) // 绑定主机端口,让设备可访问 .withPublishPort(80, 8080); webServer.start(); // 配置主机局域网IP,确保设备与主机在同一网络 String hostIp = "192.168.1.100"; containerUrl = "http://" + hostIp + ":8080/"; } @Test public void testMyWebApp() { getInstrumentation().runOnMainSync(() -> { WebView webView = new WebView(getInstrumentation().getContext()); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { // 页面加载完成后再执行断言 assertTrue(view.getTitle().contains("Welcome to nginx!")); } }); webView.loadUrl(containerUrl); }); } @AfterClass public static void stopServer() { if (webServer != null) { webServer.stop(); } } }
方案2:将容器启动逻辑移至主机端
通过自定义Gradle任务,在测试开始前于主机启动容器,并将容器地址传递给测试设备:
- 编写Gradle任务管理容器生命周期:
tasks.register("startTestContainer") { doLast { def container = new org.testcontainers.containers.GenericContainer( org.testcontainers.utility.DockerImageName.parse("nginx:latest") ) .withExposedPorts(80) .waitingFor(org.testcontainers.containers.wait.strategy.Wait.forHttp("/")) .withPublishPort(80, 8080) container.start() // 将容器地址写入测试assets目录 def assetsDir = file("src/androidTest/assets") assetsDir.mkdirs() new File(assetsDir, "container_url.txt").text = "http://192.168.1.100:8080/" new File(assetsDir, "container_id.txt").text = container.containerId } } tasks.register("stopTestContainer") { doLast { def containerIdFile = file("src/androidTest/assets/container_id.txt") if (containerIdFile.exists()) { def container = new org.testcontainers.containers.GenericContainer(containerIdFile.text) container.stop() containerIdFile.delete() file("src/androidTest/assets/container_url.txt").delete() } } } // 让Instrumentation测试依赖容器启停任务 android.testVariants.all { variant -> variant.instrumentationTestProvider.dependsOn("startTestContainer") variant.instrumentationTestProvider.finalizedBy("stopTestContainer") } - 测试代码中读取容器地址:
private String getContainerUrl() throws IOException { InputStream is = getInstrumentation().getContext().getAssets().open("container_url.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); return reader.readLine(); }
方案3:使用Testcontainers Android专用模块
Testcontainers提供了testcontainers-android模块,自动处理主机与设备的网络映射:
- 添加依赖到
build.gradle:androidTestImplementation "org.testcontainers:testcontainers-android:1.19.0" androidTestImplementation "org.testcontainers:nginx:1.19.0" - 修改测试代码:
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertTrue; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.testcontainers.android.AndroidTestContainer; import org.testcontainers.nginx.NginxContainer; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.test.ext.junit.runners.AndroidJUnit4; @RunWith(AndroidJUnit4.class) public class MyIntegrationTest { private static AndroidTestContainer<NginxContainer> nginxContainer; @BeforeClass public static void setup() { nginxContainer = new AndroidTestContainer<>(new NginxContainer<>("nginx:latest")); nginxContainer.start(); } @Test public void testWebView() { getInstrumentation().runOnMainSync(() -> { WebView webView = new WebView(getInstrumentation().getContext()); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { assertTrue(view.getTitle().contains("Welcome to nginx!")); } }); webView.loadUrl(nginxContainer.getContainerUrl()); }); } @AfterClass public static void teardown() { nginxContainer.stop(); } }
内容的提问来源于stack exchange,提问作者fauess
相关产品推荐
相关产品推荐

