美文网首页Android单元测试测试
Android单元测试(二)-实战

Android单元测试(二)-实战

作者: Stan_Z | 来源:发表于2021-05-06 21:38 被阅读0次

    一、项目单元测试环境配置

    gradle配置:

    dependencies {
        // junit4
        testImplementation 'junit:junit:4.12'
        // mokito
        testImplementation "org.mockito:mockito-core:2.8.9"
        testImplementation "org.mockito:mockito-android:2.8.9”
      
        // powermokito
        testImplementation "org.powermock:powermock-module-junit4:1.7.1"
        testImplementation "org.powermock:powermock-api-mockito2:1.7.1"
        testImplementation 'org.powermock:powermock-core:1.7.1'
        testImplementation "org.powermock:powermock-module-junit4-rule:1.7.1"
        testImplementation "org.powermock:powermock-classloading-xstream:1.7.1"
       
        //robolectric
        testImplementation "org.robolectric:robolectric:3.3.1"
    }
    

    抽取单测基类:

    1)纯java单测基类

    @RunWith(RobolectricTestRunner.class)
    @Config(manifest = "AndroidManifest.xml", sdk = 21, application = ApplicationStub.class)
    public abstract class BaseJavaTest {
    
        private int androidSdkVersion;
    
        protected static void log(String msg) {
            System.out.println(msg);
        }
    
        @Before
        public void init() {
            // 输出日志
            MockitoAnnotations.initMocks(this);
            androidSdkVersion = VERSION.SDK_INT;
        }
    
        /**
         * hook执行前,测试log
         */
        @Before
        public void startLog() {
            log("======= start =======");
        }
    
        /**
         * hook执行后,测试log
         */
        @After
        public void endLog() throws NoSuchFieldException, IllegalAccessException {
            log("======= end =======");
            mockVersionSdkIntReturn(androidSdkVersion);
        }
    
        //基类可以封装一些通用的单测方法
    
        /**
         * mock sdk 版本
         */
        protected void mockVersionSdkIntReturn(int apiVersion) throws NoSuchFieldException, IllegalAccessException {
            Field sdkInt = VERSION.class.getField("SDK_INT");
            sdkInt.setAccessible(true);
            sdkInt.set(null, apiVersion);
        }
      ...
    }
    

    2)PowerMock基类

    @RunWith(RobolectricTestRunner.class)
    @Config(manifest = "AndroidManifest.xml", sdk = 21, application = ApplicationStub.class)
    @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.powermock.*"})
    public abstract class BasePowerMockTest {
    
        private int androidSdkVersion;
    
             //解决powermock和robolectric兼容性
        @Rule
        public PowerMockRule rule = new PowerMockRule();
    
        protected static void log(String msg) {
            System.out.println(msg);
        }
    
        @Before
        public void init() {
            // 输出日志
            MockitoAnnotations.initMocks(this);
            androidSdkVersion = VERSION.SDK_INT;
        }
    
        /**
         * hook执行前,测试log
         */
        @Before
        public void startLog() {
            log("======= start =======");
        }
    
        /**
         * hook执行后,测试log
         */
        @After
        public void endLog() {
            log("======= end =======");
            try {
                mockVersionSdkIntReturn(androidSdkVersion);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        protected void mockVersionSdkIntReturn(int apiVersion) throws NoSuchFieldException, IllegalAccessException {
            Field sdkInt = VERSION.class.getField("SDK_INT");
            sdkInt.setAccessible(true);
            sdkInt.set(null, apiVersion);
        }
      ...
    }
    

    二、可测代码设计原则

    • 充分利用mvp模式;
    • 一个函数只做一件事情,不要把几件事揉在一起;
    • 通过将查询和行为分离,可以方便对查询做测试;
    • 将纯逻辑部分拆分为独立函数,方便测试;
    • 函数的依赖尽量通过参数来传递;
    • 尽量不要把复杂结构当做参数传递给函数。

    好的代码,能极大的避免mock,降低单测书写难度,因此单测某种程度上也能反过来倒逼程序员写出更优秀的代码。

    三、Android单测实战场景

    mock场景的单测主要包括三大步:构建对象 + 打桩 + 验证行为

    3.1 构建对象
    • new 常规初始化对象
    • mock 构建空实现对象
    • spy 构建具体实现对象

    注:
    类没有依赖且对象好构建,那可以选择new来初始化对象,否则使用mock/spy。前者类对外部依赖较多,只关新少数函数的具体实现;后者类对外依赖较少,关心大部分函数的具体实现。

    3.2 打桩

    mock对象之后的后续函数操作,doCallRealMethod()、doReturn()、thenReturn()、doNothing()等是比较常用的打桩方法。

    1)public/protected/default方法:

    mock类执行真实方法

    AppManagerModule appManagerModule = Mockito.mock(AppManagerModule.class);
    Mockito.doCallRealMethod().when(appManagerModule).isIgnoreApp("22");
    boolean res = appManagerModule.isIgnoreApp("22");
    Assert.assertFalse(res);
    

    公有方法返回修改值

    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    
    Context context = Mockito.mock(Context.class);
    ActivityManager activityManager = Mockito.mock(ActivityManager.class);
    Mockito.doReturn(activityManager).when(context).getSystemService(Mockito.eq(Context.ACTIVITY_SERVICE));
    
    2)private/static/final方法

    mock类执行真实方法

    AppManagerModule mockAppManagerModule = PowerMockito.mock(AppManagerModule.class);
    doCallRealMethod().when(mockAppManagerModule).getDownloadingApkCount();
    mockAppManagerModule.getDownloadingApkCount();
    

    静态方法返回修改值

    List<DownloadInfo> downloadInfoList = DownloadProxy.getInstance().getDownloadInfoList(DownloadType.APK, true);
    
    DownloadProxy downloadProxy = PowerMockito.mock(DownloadProxy.class);
    //类的@PrepareForTest需要添加DownloadProxy.class
    PowerMockito.mockStatic(DownloadProxy.class);
    //DownloadProxy.getInstance()默认返回mock的downloadProxy实例
    PowerMockito.when(DownloadProxy.getInstance()).thenReturn(downloadProxy);
    //构建List<DownloadInfo>
    ArrayList<DownloadInfo> downloadInfos = new ArrayList<>();
    DownloadInfo downloadInfo = new DownloadInfo();
    downloadInfos.add(downloadInfo);
    //返回设置为构建的list
    PowerMockito.when(downloadProxy.getDownloadInfoList(Mockito.any(SimpleDownloadInfo.DownloadType.class),Mockito.anyBoolean())).thenReturn(downloadInfos);
    

    私有方法返回修改值

    public class MockPrivateObjectClass {
        public String stepName;
        public MockPrivateObjectClass() {
        }
    
        private void setStepName() {
            System.out.print("enter setStepName");
            stepName = "has set name";
        }
    
        public void testStepName() {
            setStepName();
            System.out.print(stepName);
        }
    }
    
      @Test
        public void replacePrivateMethodTest() {
            MockPrivateObjectClass objectClass = new MockPrivateObjectClass();
            PowerMockito.replace(PowerMockito.method(MockPrivateObjectClass.class, "setStepName")).with(new InvocationHandler() {
                @Override
                public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                    Whitebox.setInternalState(o, "stepName", "modify step name");
                    return null;
                }
            });
            objectClass.testStepName();
        }
    

    invoke对象私有方法

    ObjectWhiteBoxClass objectWhiteBoxClass = new ObjectWhiteBoxClass();
    Whitebox.invokeMethod(objectWhiteBoxClass, “addObject", "test1");
    Assert.assertEquals(1, objectWhiteBoxClass.getObjectList().size());
    
    3.3 验证行为
    验证返回值:

    Assert.assertFalse、Assert.assertEquals等

    Assert.assertEquals("0", mockAppManagerModule.getPkgScanStatus());
    Assert.assertFalse(appManagerModule.isIgnoreApp("22"););
    
    验证方法被调用及其频率:(要求对象是mock对象)

    Mockito.verify、PowerMotiko.verifyStatic、Mockito.verifyPrivate等

    public/protected/default方法:

    NormalClassB b = new MockNormalClassB();
     NormalClassB mockB = Mockito.spy(b);
    Mockito.verify(mockB).getName();
    
    Mockito.verify(triggerManager, Mockito.times(1)).showDesktopWindowLocked(
            Mockito.any(Context.class), Mockito.any(DesktopWinTrigger.class),
            Mockito.any(DesktopWinCardInfo.class),
            Mockito.anyLong(), Mockito.anyInt());
    

    static方法:

    PowerMockito.verifyStatic(Mockito.times(1));
    ToastUtils.show(null, 0x7632343, Toast.LENGTH_SHORT);
    

    private方法:

    NormalClassA classA = new NormalClassA(); 
    NormalClassA mockClassA = PowerMockito.spy(classA);
    PowerMockito.verifyPrivate(mockClassA, times(1)).invoke("privateAdd", Mockito.anyInt(), Mockito.anyInt());
    

    四、常见报错问题处理

    持续更新中…..

    相关文章

      网友评论

        本文标题:Android单元测试(二)-实战

        本文链接:https://www.haomeiwen.com/subject/fzcjdltx.html