Android之单元测试

2023-03-20  本文已影响0人  南风知我咦

申明

单元测试的目的以及测试内容

  1. 列出想要测试覆盖的正常、异常情况,进行测试验证;
  2. 性能测试,例如某个算法的耗时等等。
  1. 本地测试(Local tests): 只在本地机器JVM上运行,以最小化执行时间,这种单元测试不依赖于Android框架,或者即使有依赖,也很方便使用模拟框架来模拟依赖,以达到隔离Android依赖的目的,模拟框架如google推荐的[Mockito][1];
  2. 仪器化测试(Instrumented tests): 在真机或模拟器上运行的单元测试,由于需要跑到设备上,比较慢,这些测试可以访问仪器(Android系统)信息,比如被测应用程序的上下文,一般地,依赖不太方便通过模拟框架模拟时采用这种方式。

JUnit 注解

本地测试

添加依赖
 //单元测试的依赖库
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    // Optional -- Mockito framework(可选,用于模拟一些依赖对象,以达到隔离依赖的效果)
    androidTestImplementation 'org.mockito:mockito-core:2.19.0'
单元测试代码位置
创建测试类
- 其中勾选setUp/tearDown是会自动创建一个setUp()/ tearDown() 的空方法
public class TimeUtilsTest extends TestCase {

    public void setUp() throws Exception {
        super.setUp();
    }

    public void tearDown() throws Exception {
    }
}
简单例子
public class PhoneUtils {

    /**
     * 判断是否是手机号
     */
    public static boolean assertPhone(String phoneNum){
        return phoneNum.matches("^1[3-9][0-9]{9}");
    }

    public static boolean assertPhone2(String phoneNum){
        return phoneNum.matches("^1[3-9]\\d{9}");
    }
}
  1. 正确输入和反馈
   public void testAssertPhone() {
//        assertEquals(PhoneUtils.assertPhone("12871650177"),true);
        assertEquals(PhoneUtils.assertPhone("13871650177"),true);
    }
测试通过
  1. 错误输入和反馈
    public void testAssertPhone() {
        assertEquals(PhoneUtils.assertPhone("12871650177"),true);
//        assertEquals(PhoneUtils.assertPhone("13871650177"),true);
    }
测试失败
Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
java.lang.RuntimeException: Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
    at android.util.Log.d(Log.java)
    at com.southwind.module_common.SouthWindLog.d(SouthWindLog.java:51)
    at com.southwind.module_common.SouthWindLogTest.testTestD1(SouthWindLogTest.java:39)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        ...

//上述错误的原因是方法里面调用了android.util.Log包里面的静态方法
//解决办法就是在你自动生成的before方法里面mock一下这个静态类
   @Override
    protected void setUp() throws Exception {
        super.setUp();
        mockStatic(Log.class);
    }
//再次运行就能正常了,这个就是接下来要说的隔离依赖;

通过模拟框架模拟依赖,隔离依赖

Mockito的使用
    // required if you want to use Mockito for unit tests
    testCompile 'org.mockito:mockito-core:2.7.22'
    // required if you want to use Mockito for Android tests
    androidTestCompile 'org.mockito:mockito-android:2.7.22'
  1. 使用静态方法mock()
  2. 使用注解@Mock
使用注解
    @Mock
    Activity activityMock;
    @Rule
    public MockitoRule rule = MockitoJUnit.rule();

    @Test
    public void testD() {
        SouthWindLog.d(activityMock,"测试");
    }
使用静态方法哈
   @Mock
    Activity activityMock;
    @Test
    public void testD() {
        activityMock = Mockito.mock(Activity.class);
        when(activityMock.getString(R.string.log_d)).thenReturn("单元测试日志");
        SouthWindLog.d(activityMock);
        //verify是判断方法是否被调用过
        verify(activityMock).getString(R.string.log_d);
    }

配置模拟对象

"when thenReturn"和"when thenThrow"
//强行指定1和2是相等的。
 public void testMockObject(){
        PhoneUtils phoneUtils = mock(PhoneUtils.class);
        when(phoneUtils.isEqules(1,2)).thenReturn(true);
        assertEquals(phoneUtils.isEqules(1,2),false);
    }
//结果就是不通过哈哈
//返回多个值
        Iterator<Integer> i = mock(Iterator.class);
        when(i.next()).thenReturn(11).thenReturn(12);
        String res = i.next() + "/" + i.next();
        assertEquals(res,"11/12");
  PhoneUtils phoneUtils = mock(PhoneUtils.class);
        when(phoneUtils.divideInput(0)).thenThrow(new Exception("0不能做除数"));
        try {
            phoneUtils.divideInput(0);
            fail("0做除数啦,夭寿啦");
        }catch (Exception e){
        }
"doReturn when" 和 "doThrow when"
使用Spy包装实例
 public void testSpy(){
        ArrayList<String> list = new ArrayList<>();
        ArrayList<String> spy = Mockito.spy(list);
//        when(spy.get(0)).thenReturn("第一个元素");
//        assertEquals("第一个元素",spy.get(0));

        doReturn("第一个元素").when(spy).get(0);
        assertEquals("第一个元素",spy.get(0));
    }
验证模拟对象的调用
    public void testVerify(){
        PhoneUtils phoneUtils = mock(PhoneUtils.class);
        when(phoneUtils.divideInput(5)).thenReturn(2);

        phoneUtils.divideInput(10);
        phoneUtils.isEqules(1,2);
        phoneUtils.isEqules(2,2);
        phoneUtils.isEqules(2,2);


        verify(phoneUtils).divideInput(ArgumentMatchers.eq(10));//期望入参是10的调用
        //verify(phoneUtils).divideInput(ArgumentMatchers.eq(5));//报错了,没有调用入参是5的情况

        //判断方法跑了多少次
        verify(phoneUtils,times(2)).isEqules(2,2);
        //verify(phoneUtils,times(3)).isEqules(2,2);//这里报错,说跑了两次,但是期望的是三次

        //判断是否从没出现后面的调用情况
        verify(phoneUtils,never()).isEqules(0,1);
        verify(phoneUtils, atLeastOnce()).isEqules(1,2);
        verify(phoneUtils, atLeast(1)).isEqules(1,2);//至少被调用1次
        verify(phoneUtils, atMost(3)).isEqules(1,2);//至多调用了3次

        //下面的方法用来检查是否所有的用例都涵盖了,如果没有将测试失败
        //放在所有的测试后面
        verifyNoMoreInteractions(phoneUtils);
    }
Answer
public void testAnswer(){
        TestAnswer testAnswer = mock(TestAnswer.class);
        doAnswer(returnsFirstArg()).when(testAnswer).add(anyString(),anyString());
//        doReturn(returnsFirstArg()).when(testAnswer).add(anyString(),anyString());
        when(testAnswer.add(anyString(),anyString())).thenAnswer(returnsSecondArg());
        when(testAnswer.add(anyString(),anyString())).then(returnsFirstArg());
        System.out.println(testAnswer.add("first","second"));
    }
   public void testAnswer() {
        TestAnswer testAnswer = mock(TestAnswer.class);
//        doAnswer(returnsFirstArg()).when(testAnswer).add(anyString(),anyString());
////        doReturn(returnsFirstArg()).when(testAnswer).add(anyString(),anyString());
//        when(testAnswer.add(anyString(),anyString())).thenAnswer(returnsSecondArg());
//        when(testAnswer.add(anyString(),anyString())).then(returnsFirstArg());
//        System.out.println(testAnswer.add("first","second"));

        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                //invocation这个是存放你参数的容易,我第三个参数是回调函数,所以下面下标要写2
                CallBack callBack = invocation.getArgument(2);
                callBack.back("callback");
                return "return";
            }
        }).when(testAnswer).add(anyString(), anyString(), any(CallBack.class));

        String result = testAnswer.add("1", "2", new CallBack() {
            @Override
            public void back(String back) {
                System.out.println(back);
            }
        });
        System.out.println(result);
    }

    class TestAnswer {
        public String add(String first, String second, CallBack callBack) {
            callBack.back("");
            return "";
        }

        public String add(String first, String second) {
            return "";
        }

    }

    interface CallBack {
        void back(String back);
    }
//最后打印的是 符合我再模拟中提供的数据
...
...
> Task :module_common:testDebugUnitTest
callback
return
BUILD SUCCESSFUL in 2s
16 actionable tasks: 3 executed, 13 up-to-date

总结

上一篇 下一篇

猜你喜欢

热点阅读