You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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任务,在测试开始前于主机启动容器,并将容器地址传递给测试设备:

  1. 编写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")
    }
    
  2. 测试代码中读取容器地址:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 18:35:22