1、定义


从名字上看,单元测试就是参与项目开发的工程师在项目中为了测试某一个代码单元而写的测试代码,用于执行项目中的目标函数并验证其逻辑状态或者结果。这里提到的“一个代码单元”指的是测试的最小模块,通常指函数。这些代码是白盒测试,能够检测目标代码的准确性和可靠性,在打包时单元测试的代码并不会被编译进入release apk中。


单元测试不是集成测试,单元测试只是测试一个方法单元,不是测试一整个流程。集成测试是一种end to end的系统测试,测试相关模块集成在一起是否能够按照预期工作,一般都是接口或者功能层面的测试,可能会依赖很多系统因素,测试的代码逻辑一般比较复杂,运行时间会比较长,出错之后的修复成本高。单元测试的目标函数主要有三种:


有明确的返回值,做单元测试时,只需调用这个函数,验证其返回值是否符合预期结果。


这个函数只改变其对象内部的一些属性或者状态,函数本身没有返回值,就验证它所改变的属性和状态。


一些函数没有返回值,也没有直接改变哪个值的状态,这就需要验证其行为,比如点击事件。


2、


我们也可以通过Android Studio的快捷方式来创建单元测试类,选中目标类或者目标方法点击右键,选择GoTo--->Test来快速创建单元测试方法。打开测试类,鼠标右键点击测试方法,选择Run测试,就执行了单元测试。



测试一些数据性的功能,比如加载网络数据;测试SharedPerferences,测试数据库,测试函数等;工具类的测试,比如验证时间,转化格式,正则验证等等。测试用例里面为我们提供了测试过程中可能需要的系统环境对象,比如:application,context等等。


我们所写的测试用例方法需要添加名称为Test的注解;测试函数需要为public。



3、一些测试的框架


JUnit4(在JVM运行环境下的单元测试)


JUnit4是一个Java语言的单元测试框架,由Kent Beck和Erich Gamma编写的一个回归测试框架。多数Java的开发环境都已经集成了JUnit作为单元测试的工具,也是用的最多的一个测试框架,Android Studio创建的工程中就已经集成了JUnit4,默认就加了这个dependencies。初始化的方法为:MockitoAnnotations.initMocks(testClass)参数testClass是你所写的测试类。一般情况下在Junit4的@Before定义的方法中执行初始化工作,如下:


@Beforepublic void setUp() { MockitoAnnotations.initMocks(this);}


JUnit4使用Java5中的注解(annotation),以下是JUnit4常用的几个annotation:


@Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)


@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)


@Test:测试方法,在这里可以测试期望异常和超时时间


@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常


@Ignore:忽略的测试方法


@BeforeClass:针对所有测试,只执行一次,且必须为static void


@AfterClass:针对所有测试,只执行一次,且必须为static void


注:一个JUnit4的单元测试用例执行顺序为:@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;


每一个测试方法的调用顺序为:@Before -> @Test -> @After;



Mockito(在JVM运行环境下的单元测试)


在写单元测试的过程中,一个很普遍的问题是,要测试的目标类会有很多依赖,这些依赖的类/对象/资源又会有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。针对这个问题有一个应对的办法:Mock。简单地说就是对测试的类所依赖的其他类和对象,进行mock - 构建它们的一个假的对象,定义这些假对象上的行为,然后提供给被测试对象使用。被测试对象像使用真的对象一样使用它们。用这种方式,我们可以把测试的目标限定于被测试对象本身,就如同在被测试对象周围做了一个划断,形成了一个尽量小的被测试目标。


在测试环境中,通过Mockito来mock出其他的依赖对象,用来替换真实的对象,使得待测的目标方法被隔离起来,避免一些外界因素的影响和依赖,能在我们预设的环境中执行,以达到两个目的:验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等;指定这个对象的某些方法的行为,返回特定的值,或是执行特定的动作。Mockito提供的Junit Runner:MockitoJUnitRunner,来进行初始化。


@RunWith(MockitoJUnit44Runner.class)public class ComplaintPresenterTest {}


在Mockito中,同样支持对变量进行注解,例如将mock对象设为测试类的属性,然后通过注解的方式@Mock来定义它,这样有利于减少重复代码,增强可读性,易于排查错误等。除了支持@Mock,Mockito支持的注解还有@Spy(监视真实的对象),@Captor(参数捕获器),@InjectMocks(mock对象自动注入)。


@Mock注解


使用@Mock注解来定义mock对象有如下的优点:方便mock对象的创建;减少mock对象创建的重复代码;提高测试代码可读性;变量名字作为mock对象的标示,所以易于排错。


@Spy注解


使用@Spy生成的类,所有方法都是真实方法,返回值和真实方法一样的,是使用Mockito.spy()的快捷方式。


@Captor注解


@Captor是参数捕获器的注解,通过注解的方式可以更便捷的对ArgumentCaptor进行定义。还可以通过ArgumentCaptor对象的forClass(Class<T> clazz)方法来构建ArgumentCaptor对象,然后便可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。ArgumentCaptor的Api:argument.capture() 捕获方法参数;argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值;argument.getAllValues() 方法进行多次调用后,返回多个参数值;


@InjectMocks注解


通过这个注解,可实现自动注入mock对象。当前版本只支持setter的方式进行注入,Mockito首先尝试类型注入,如果有多个类型相同的mock对象,那么它会根据名称进行注入。当注入失败的时候Mockito不会抛出任何异常,所以你可能需要手动去验证它的安全性。


any参数匹配


很多时候你并不关心被调用方法的参数具体是什么,或者是你也不知道,你只关心这个方法得到调用了就行。这种情况下,Mockito提供了一系列的any方法,来表示任何的参数都行,如上面的例子:Mockito.verify(complaintManager, Mockito.times(1)).getComplaintReasons(any(Callback.class));


any(Callback.class)表示任何一个Callback对象都可以。null?也可以的!类似any,还有anyInt, anyLong, anyDouble等。anyObject表示任何对象,any(clazz)表示任何属于clazz的对象。其他anyCollection,anyCollectionOf(clazz), anyList(Map, set), anyListOf(clazz)等。


注:Mockito.when(mockedList.get(0)).thenReturn("one");(来指定这个mock对象具体的行为)//当mockedList的get方法被调用,并且参数的值是0的时候,返回”one”。



PowerMockito(在JVM运行环境下的单元测试)


PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。通过使用自定义的类加载器,简化采用的IDE或持续集成服务器不需要做任何改变。它有两个重要的依赖:javassist和objenesis,javassist是一个修改java字节码的工具包,objenesis是一个绕过构造方法来实例化一个对象的工具包。由此看来,PowerMock的本质是通过修改字节码来实现对静态和final等方法的mock的。


PowerMock初始化,如下:


@RunWith(PowerMockRunner.class)@PrepareForTest({YourClassWithEgStaticMethod.class})


要么使用注解加代码的方式


@PrepareForTest({YourClassWithEgStaticMethod.class})MockitoAnnotations.initMocks(this);


PowerMock有三个重要的注解:


@RunWith(PowerMockRunner.class) @PrepareForTest({YourClassWithEgStaticMethod.class}) ,是声明需要进行mock的静态类,如果你需要声明多个静态类,使用


@PrepareForTest({Example1.class, Example2.class, ...})这种方式声明。 @PowerMockIgnore("javax.management.*") ,声明package路径,表示不使用PowerMockito来加载所声明的package路径的类。


注:如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。


PowerMock的使用:


a. 普通Mock:Mock参数传递的对象:执行步骤:


通过PowerMockito.mock(File.class)创建出一个mock对象,然后再通过PowerMockito.when(file.exists()).thenReturn(true);来指定这个mock对象具体的行为,再将mock对象作为参数传递个测试方法,执行测试方法。


b. Mock方法内部new出来的对象:


当使用PowerMockito.whenNew().thenReturn()方法时,必须加上注解@PrepareForTest和@RunWith,注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。需要mock的对象是在方法内部new出来的,这是一种比较常见的mock方式。执行步骤:


之前步骤同上,之后通过PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file);来指定当以参数为"aaa"创建File对象的时候,返回已经mock的File对象。在测试方法之上加注解@PrepareForTest(CommonExample.class),注解里写的类是需要mock的new对象代码所在的类。


c. Mock普通对象的final方法:执行步骤:


Mock的步骤和之前的一样,只是需要在测试方法之上加注解@PrepareForTest(ClassDependency.class),注解里写的类是需要mock的final方法所在的类。


d. Mock普通类的静态方法:


当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。


e. Mock 私有方法:


和Mock普通方法一样,只是需要加注解@PrepareForTest(CommonExample.class),注解里写的类是私有方法所在的类。


f. Mock系统类的静态和final方法:


和Mock普通对象的静态方法、final方法一样,只不过注解@PrepareForTest里写的类不一样 ,注解里写的类是需要调用系统方法所在的类。


g. Mock普通类的私有变量:


当需要mock私有变量mState的时候,不需要加注解@PrepareForTest和@RunWith,而是使用Whitebox来mock私有变量mState并注入你预设的变量值。



Robolectric(实现一套JVM能运行的Android代码)


Robolectric它的设计思路便是通过实现一套JVM能运行的Android代码,然后在unit test运行的时候去截取android相关的代码调用,然后转到自己实现的代码去执行这个调用的过程。除此之外,他们给每个Shadow类额外增加了很多接口(Shadows.shadowOf()可以获取很多Android对象的Shadow对象),方便我们读取对应Android类的一些状态。Shadow是Robolectric的立足之本,如其名,作为影子,一定是变幻莫测,时有时无,且依存于本尊。因此,框架针对Android SDK中的对象,提供了很多影子对象(如Activity和ShadowActivity、TextView和ShadowTextView等),这些影子对象,丰富了本尊的行为,能更方便的对Android相关的对象进行测试。


注解配置:


@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)