我们都希望写出没bug的代码,那么测试就是必不可少的一个环节。在CI持续集成并发布我们的代码的过程中,有很多测试方法,可以提高我们代码的覆盖率,查缺补漏。
单元测试用于测试最小的功能单元,这是各种测试中范围最小的一种。在单元测试中,我们一般会测试一个方法以其为单位,检测是否能返回我们期望的结果。理论上,单元测试应该在内存中进行,被测试的代码在理论上不应该和其他外部信息进行交互,这些外部信息包括:访问网络、访问数据库、读写数据、访问其他线程。
除了让整个应用运行起来之外,我们希望有一种比较方便的方式来测试我们的功能,JUnit就是这样的一种框架,设想如果没有单元测试框架,我们用main函数来测试过程实在是复杂而且难以想象。一般的,项目工程都会有一个test文件夹,我们将所有的测试方法都写在这个文件夹下,测试方法的名字是无关的,但是我们一般最好起一个易懂的名字。
@Test注解:表示当前方法为测试方法,我们可以运行该方法
@Before注解:表示当前类中,所有的方法在执行前都会执行被Before注解修饰的方法
@After注解:类似于Before,在每个方法执行完后都会执行此方法
Assert:测试方法一般返回值都是void,我们用Assert.assertTrue()等方法做断言
@Ignore注解:如果测试方法还没完成时,可以用这个注解,让集成的时候忽略此测试方法
@Test(expected = Exception.class) : 如果测试后没有抛出期望的异常,则测试失败
在测试的时候,有时我们要达到一些覆盖率指标,或者即使不是因为覆盖率指标我们也会遇到这种问题:测试方法A中调用了其他方法B,而B方法因某些原因不能成功调用或我们不想调用B方法。我们需要一套机制,让测试增加可控,我们能比较自由的测试想要测试的部分,mockito就是帮助我们完成这件工作的工具。
mockito的主要作用有两个:
验证某实例的某方法在测试中是否被调用、被调用了多少次
跳过某实例的某方法的实际执行内容,直接返回我们设置的结果
mockito是单元测试的利器,我们开始说过,单元测试是专注于方法内部,最小单元的测试,在其中我们或许会难免涉及其他服务的方法调用等,我们可以利用mockito来跳过这些步骤。mock这个单词的意义就是虚假、愚弄,我们用一个自己创建的假对象替换真实的对象达到我们的效果,并不会对原类、原对象产生改变性的影响。
@Test
public void testLogin() throws Exception {
UserManager mockUserManager = Mockito.mock(UserManager.class);
//用上面的方法,我们创建了一个mock的UserManager对象
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.setUserManager(mockUserManager);
//我们将这个对象注入loginPresenter中,再后面我们使用注解时,有更便捷的方法
loginPresenter.login("xiaochuang", "xiaochuang password");
//在测试方法中调用一次我们要测试的方法
Assert.assertTrue(Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("xiaochuang", "xiaochuang password"));
//如果在第11行调用loginPresenter.login的方法内部,调用了performLogin方法1次,则这个断言为true
//Mockito.verify(mockUserManager, Mockito.times(1)) 等价于 Mockito.verify(mockUserManager) 一次可以省略
Mockito.when(mockUserManager.verifyPassword("xiaochuang_is_handsome")).thenReturn(true);
//当调用mockUserManager的verifyPassword方法时,直接返回true
}
上面的代码与注释基本解释了mockito的工作原理,值得注意的是:mock生成的对象的所有的方法都不会真正执行,int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null。只要用mock生成(被打上@mock注解)这个对象的所有方法都空了。而when方法只是为其中的某个方法指定一个返回结果。
有的时候我们不希望所有方法都空,而是希望自己指定,那么可以使用spy方法:
UserManager mockUserManager = Mockito.spy(UserManager.class);
//所有的方法都正常执行,只有被when调用的方法会直接返回
现在我们整理一下使用mockito的思路:
我们创建一个mock的对象a,这个对象方法都空
在测试方法中,我们要测试的方法是对象b的方法C,也就是b.C
b对象中组合了对象a,我们需要把mock的对象a传到b中替换掉真正的a,让我们在b.C执行的内部,在遇到a的方法a.D、a.E执行时变成空
上述的三个步骤涉及一个绑定的过程,我们需要把a和b绑定在一起,在不使用注解的时候我们用setter解决这个问题,在有了注解之后,我们可以方便的使用注解。
@InjectMocks
@Resource(name = "loginPresenter")
LoginPresenter loginPresenter;
@Mock
UserManager mockUserManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
//这一步的作用是将Mocks注解和InjectMocks注解的对象自动装配
}
@Test
public void testLogin() throws Exception {
loginPresenter.login("xiaochuang", "xiaochuang password");
//在测试方法中调用一次我们要测试的方法
Assert.assertTrue();
}
上述代码和我们开始的代码作用完全相同,但是因为使用了注解,就清晰了很多,其中我们initMocks方法进行了两个注解的装配。使用注解后,我们有两点需要注意:
如果我们需要装配的对象有三层,那么肯定有一次层需要手动配置而不能使用两个@InjectMocks注解
一般我们的方法都会返回对象,在@Mock后,返回对象的方法都返回null,如果有需要,我们要自己用when方法设定返回值