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

如何在JUnit4参数化测试中适配不同Android SDK版本测试分支逻辑?

Android SDK版本判断逻辑单元测试方案

单测试方法指定SDK版本的注解

存在可直接指定单测试方法运行SDK版本的注解,根据测试类型不同分为两类:

  • 本地JVM单元测试:使用Robolectric测试框架提供的@Config(sdk = 目标版本号)注解,可标注在测试类或单个测试方法上,方法级注解会覆盖类级配置。
  • 真机/模拟器仪器化测试:使用AndroidX Test提供的@SdkSuppress(minSdkVersion = xx, maxSdkVersion = xx)注解,可指定测试方法仅在符合版本范围的设备上运行。

整合参数化测试的最佳实践

推荐优先使用Robolectric方案,无需手动修改系统静态变量,测试隔离性更好,可完美兼容Parameterized、JUnitParams等参数化测试方案,甚至可以将SDK版本也作为参数化维度,一套用例覆盖所有分支。

示例1:Robolectric + 参数化测试(SDK版本作为参数)

import android.os.Build;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;

@RunWith(Parameterized.class)
public class SdkConditionTest {
    // 参数化入参:测试SDK版本、方法入参、预期返回值
    @Parameterized.Parameter
    public int testSdkVersion;
    @Parameterized.Parameter(1)
    public String input;
    @Parameterized.Parameter(2)
    public String expectedResult;

    // 构造测试参数集,覆盖高低版本不同场景
    @Parameterized.Parameters
    public static Collection<Object[]> getTestData() {
        return Arrays.asList(new Object[][]{
                // 低于Android O(SDK 26)的场景
                {Build.VERSION_CODES.N, "input1", "result_below_o_input1"},
                {Build.VERSION_CODES.N_MR1, "input2", "result_below_o_input2"},
                // 等于及高于Android O的场景
                {Build.VERSION_CODES.O, "input1", "result_above_o_input1"},
                {Build.VERSION_CODES.TIRAMISU, "input2", "result_above_o_input2"}
        });
    }

    @Test
    @Config(sdk = "{testSdkVersion}") // 直接引用参数化的SDK版本作为运行配置
    public void testSdkConditionLogic() {
        // 调用待测试的目标方法
        String actualResult = yourTargetMethod(input);
        assertEquals(expectedResult, actualResult);
    }

    // 待测试的目标方法示例
    private String yourTargetMethod(String input) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return "result_below_o_" + input;
        } else {
            return "result_above_o_" + input;
        }
    }
}

示例2:无Robolectric时反射修改SDK_INT整合参数化

如果不使用Robolectric,也可以将SDK版本加入参数化项,通过反射统一修改SDK_INT字段值,无需为每个版本单独编写测试方法:

import android.os.Build;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;

@RunWith(Parameterized.class)
public class SdkConditionTest {
    @Parameterized.Parameter
    public int mockSdkInt;
    @Parameterized.Parameter(1)
    public String input;
    @Parameterized.Parameter(2)
    public String expectedResult;

    private static Object originalSdkIntValue;

    // 测试开始前备份原SDK_INT值
    @BeforeClass
    public static void backupOriginalSdkInt() throws NoSuchFieldException, IllegalAccessException {
        Field sdkIntField = Build.VERSION.class.getField("SDK_INT");
        sdkIntField.setAccessible(true);
        originalSdkIntValue = sdkIntField.get(null);
    }

    // 每个测试方法运行前设置对应模拟SDK版本
    @Before
    public void setMockSdkInt() throws NoSuchFieldException, IllegalAccessException {
        Field sdkIntField = Build.VERSION.class.getField("SDK_INT");
        sdkIntField.setAccessible(true);
        sdkIntField.set(null, mockSdkInt);
    }

    // 所有测试结束后恢复原SDK_INT值,避免影响其他测试
    @AfterClass
    public static void restoreOriginalSdkInt() throws NoSuchFieldException, IllegalAccessException {
        Field sdkIntField = Build.VERSION.class.getField("SDK_INT");
        sdkIntField.setAccessible(true);
        sdkIntField.set(null, originalSdkIntValue);
    }

    @Parameterized.Parameters
    public static Collection<Object[]> getTestData() {
        return Arrays.asList(new Object[][]{
                {25, "input1", "result_below_o_input1"},
                {26, "input1", "result_above_o_input1"},
                {33, "input2", "result_above_o_input2"}
        });
    }

    @Test
    public void testSdkConditionLogic() {
        assertEquals(expectedResult, yourTargetMethod(input));
    }

    private String yourTargetMethod(String input) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return "result_below_o_" + input;
        } else {
            return "result_above_o_" + input;
        }
    }
}

注意事项

  • 反射修改SDK_INT的方案需要严格做好值的备份和恢复,避免静态变量修改污染其他测试类的运行环境。
  • Robolectric方案无需处理反射逻辑,内部已实现测试环境隔离,是优先推荐的方案。

内容的提问来源于stack exchange,提问作者Mathias Walter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 10:24:06