java 单元测试

一、目前应用比较普遍的java单元测试工具 junit4+Mock(Mockito /jmock / powermock)Stub(用得较少,一般不推荐)由于junit3目前用得不多,基本升级到junit4了,所以就直接简单说下junit4。

问题一:为什么需要mock或stub?它与junit什么关系?

在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,记录Log, 文件系统 之类的)。 而我们没法控制这些外部依赖的对象。  为了解决这个问题,我们需要用到Stub和Mock来模拟这些外部依赖的对象,从而控制它们。

JUnit是单元测试框架,可以轻松的完成关联依赖关系少或者比较简单的类的单元测试,但是对于关联到其它比较复杂的类或对运行环境有要求的类的单元测试,模拟环境或者配置环境会非常耗时,实施单元测试比较困难。而这些“mock框架”Mockito 、jmock 、 powermock、EasyMock),可以通过mock框架模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得测试变得简单。(例如service调用dao,即service依赖dao,我们可以通过mock dao来模拟真实的dao调用,从而能达到测试service的目的。)

模拟对象(Mock Object)可以取代真实对象的位置,用于测试一些与真实对象进行交互或依赖于真实对象的功能,模拟对象的背后目的就是创建一个轻量级的、可控制的对象来代替测试中需要的真实对象,模拟真实对象的行为和功能。

 

问题二:mock与stub什么区别?

Mock和Stub是两种测试代码功能的方法。Mock测重于对功能的模拟,Stub测重于对功能的测试重现。比如对于List接口,Mock会直接对List进行模拟,而Stub会新建一个实现了List的TestList,在其中编写测试的代码。
强烈建议优先选择Mock方式,因为Mock方式下,模拟代码与测试代码放在一起,易读性好,而且扩展性、灵活性都比Stub好。

其中EasyMock和Mockito对于Java接口使用接口代理的方式来模拟,对于Java类使用继承的方式来模拟(也即会创建一个新的Class类)。Mockito支持spy方式,可以对实例进行模拟。但它们都不能对静态方法和final类进行模拟,powermock通过修改字节码来支持了此功能。

有篇文章介绍:http://blog.csdn.net/devhubs/article/details/8018084

 

二、junit4相关介绍

这里有篇文章介绍了junit4的一些,包括怎么引入,使用,蛮详细。---》 http://blog.csdn.net/happylee6688/article/details/38069761

这边就记录一些常用注解,当做学习方便。

常用注解

@Before:初始化方法,在任何一个测试方法执行之前,必须执行的代码。对比 JUnit 3 ,和 setUp()方法具有相同的功能。在该注解的方法中,可以进行一些准备工作,比如初始化对象,打开网络连接等。

@After:释放资源,在任何一个测试方法执行之后,需要进行的收尾工作。对比 JUnit 3 ,和 tearDown()方法具有相同的功能。
 
@Test:测试方法,表明这是一个测试方法。在 JUnit 中将会自动被执行。对与方法的声明也有如下要求:名字可以随便取,没有任何限制,但是返回值必须为 void ,而且不能有任何参数。如果违反这些规定,会在运行时抛出一个异常。不过,为了培养一个好的编程习惯,我们一般在测试的方法名上加 test ,比如:testAdd()。

同时,该 Annotation(@Test) 还可以测试期望异常和超时时间,如 @Test(timeout=100),我们给测试函数设定一个执行时间,超过这个时间(100毫秒),他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些 bug 了。而且,它还可以测试期望的异常,例如,我们刚刚的那个空指针异常就可以这样:@Test(expected=NullPointerException.class)。

 @Ignore:忽略的测试方法,标注的含义就是“某些方法尚未完成,咱不参与此次测试”;这样的话测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应的函数,只需要把 @Ignore 注解删除即可,就可以进行正常测试了。当然,这个 @Ignore 注解对于像我这样有“强迫症”的人还是大有意义的。每当看到红色条(测试失败)的时候就会全身不舒服,感觉无法忍受(除非要测试的目的就是让它失败)。当然,对代码也是一样,无法忍受那些杂乱不堪的代码。

@BeforeClass:针对所有测试,也就是整个测试类中,在所有测试方法执行前,都会先执行由它注解的方法,而且只执行一次。当然,需要注意的是,修饰符必须是 public static void xxxx ;此 Annotation 是 JUnit 4 新增的功能。

@AfterClass:针对所有测试,也就是整个测试类中,在所有测试方法都执行完之后,才会执行由它注解的方法,而且只执行一次。当然,需要注意的是,修饰符也必须是 public static void xxxx ;此 Annotation 也是 JUnit 4 新增的功能,与 @BeforeClass 是一对。

 

执行顺序

所以,在 JUnit 4 中,单元测试用例的执行顺序为:
 
 

三、Mock的几种比较(Mockito 、jmock 、 powermock)

介绍文章一:http://blog.csdn.net/luvinahlc/article/details/10442743

介绍文章二:http://blog.csdn.net/zhangxin09/article/details/42422643

介绍文章三(Mockito 文档):https://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html

Spring提供了对Junit支持,可以使用注解的方式(注解加在需要测试的类上):

@RunWIth(SpringJunit4ClassRunner.class) ---->为了让测试在Spring容器环境下执行

@ContextConfiguration(locations = {"classpath:applicationContext.xml"} --->用来指明Spring的配置文件位置

 

Mockito简单运用说明

①    when(mock.someMethod()).thenReturn(value):设定mock对象某个方法调用时的返回值。可以连续设定返回值,即when(mock.someMethod()).thenReturn(value1).then
Return(value2),第一次调用时返回value1,第二次返回value2。也可以表示为如下:
when(mock.someMethod()).thenReturn(value1,value2)。
②     调用以上方法时抛出异常: when(mock.someMethod()).thenThrow(new Runtime
Exception());
③    另一种stubbing语法:
doReturn(value).when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
④    对void方法进行方法预期设定只能用如下语法:
doNothing().when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
doNothing().doThrow(new RuntimeException()).when(mock.someMethod())
⑤     方法的参数可以使用参数模拟器,可以将anyInt()传入任何参数为int的方法,即anyInt匹配任何int类型的参数,anyString()匹配任何字符串,anySet()匹配任何Set。
⑥    Mock对象只能调用stubbed方法,调用不了它真实的方法,但是Mockito可以用spy来监控一个真实对象,这样既可以stubbing这个对象的方法让它返回我们的期望值,又可以使得对其他方法调用时将会调用它的真实方法。
⑦    Mockito会自动记录自己的交互行为,可以用verify(…).methodXxx(…)语法来验证方法Xxx是否按照预期进行了调用。
(1)    验证调用次数:verify(mock,times(n)).someMethod(argument),n为被调用的次数,如果超过或少于n都算失败。除了times(n),还有never(),atLease(n),atMost(n)。
(2)    验证超时:verify(mock, timeout(100)).someMethod();
(3)    同时验证:verify(mock, timeout(100).times(1)).someMethod();

相关注解:

MockitoAnnotations.initMocks(this);

initializes fields annotated with Mockito annotations.

  • Allows shorthand creation of objects required for testing.
  • Minimizes repetitive mock creation code.
  • Makes the test class more readable.
  • Makes the verification error easier to read because field name is used to identify the mock.

 

ReflectionTestUtils.setField(AopTargetUtils.getTarget(appInfoService), "openAppInfoMapper",openAppInfoMapperMock);

但是由于Spring可以使用@Autoware类似的注解方式,对私有的成员进行赋值,此时无法直接对私有的依赖设置mock对象。可以通过引入ReflectionTestUtils,解决依赖注入的问题。

 

 

@InjectMocks --- injects mock or spy fields into tested object automatically.

这个注解不会把一个类变成mock或是spy,但是会把当前对象下面的Mock/Spy类注入进去,按类型注入。

@Mock 生成的类,所有方法都不是真实的方法,而且返回值都是NULL。---> when(dao.getOrder()).thenReturn("returened by mock ");

@Spy ---Creates a spy of the real object. The spy calls real methods unless they are stubbed.

生成的类,所有方法都是真实方法,返回值都是和真实方法一样的。---> doReturn("twotwo").when(ps).getPriceTwo(); 

Mockito可以完成对一般对象方法的模拟,但是对于静态函数、构造函数、私有函数等还是无能为力.

代码展示mock和spy的区别:

@Test
public void test() {
    //test for Mock
    List mockList = Mockito.mock(List.class);
    mockList.add("1");
    System.out.println(mockList.get(0));//返回null,说明并没有调用真正的方法
    Mockito.when(mockList.size()).thenReturn(100);//stub
    System.out.println(mockList.size());//size() method was stubbed,返回100

    //test for Spy
    List list = new LinkedList();
    List spy = Mockito.spy(list);

    //optionally, you can stub out some methods:
    Mockito.when(spy.size()).thenReturn(100);

    //using the spy calls real methods
    spy.add("one");
    spy.add("two");

    //prints "one" - the first element of a list
    System.out.println(spy.get(0));

    //size() method was stubbed - 100 is printed
    System.out.println(spy.size());
}

 

import org.junit.Test;
import org.mockito.Mockito;

public class PasswordValidatorTest {
    private static final String PASSWORD_TRUE = "ck_is_handsome";
    private static final String PASSWORD_FALSE = "ck_is_not_handsome";

    // Test spy
    @Test
    public void testVerifyPassword() {
        //跟创建mock类似,只不过调用的是spy方法,而不是mock方法。spy的用法
        PasswordValidator spyValidator = Mockito.spy(PasswordValidator.class);

        //在默认情况下,spy对象会调用这个类的真实逻辑,并返回相应的返回值,这可以对照上面的真实逻辑
        System.out.println("[Real]" + PASSWORD_TRUE + " : " + spyValidator.verifyPassword(PASSWORD_TRUE));     //true
        System.out.println("[Real]" + PASSWORD_FALSE + " : " + spyValidator.verifyPassword(PASSWORD_FALSE));   //false

        //spy对象的方法也可以指定特定的行为
        Mockito.when(spyValidator.verifyPassword(Mockito.anyString())).thenReturn(true);

        //同样的,可以验证spy对象的方法调用情况
        System.out.println("[Mock]" + PASSWORD_TRUE + " : " + spyValidator.verifyPassword(PASSWORD_TRUE));     //true
        System.out.println("[Mock]" + PASSWORD_FALSE + " : " + spyValidator.verifyPassword(PASSWORD_FALSE));   //true

        Mockito.verify(spyValidator, Mockito.times(2)).verifyPassword(PASSWORD_TRUE); //pass
    }
}

class PasswordValidator {
    public boolean verifyPassword(String password) {
        return "ck_is_handsome".equals(password);
    }
}

 总之,spy与mock的唯一区别就是默认行为不一样:spy对象的方法默认调用真实的逻辑mock对象的方法默认什么都不做,或直接返回默认值

 

转自: https://www.cnblogs.com/wuyun-blog/p/7081548.html

posted @ 2022-01-11 19:29  ck_2016  Views(2914)  Comments(0Edit  收藏  举报