单元测试之Mockito学习笔记

之前用的单元测试用的都是Junit,看有的单元测试会用到Mockito,学习记录下笔记。

主要看的是b站视频:https://www.bilibili.com/video/BV1jJ411A7Sv

好的类似笔记有:https://blog.csdn.net/yangshengwei230612/category_9737103.html

Mockito中文文档:https://gitee.com/libearys/mockito-doc-zh   (很有用)

1.单元测试简介

和单元测试相关的:

junit

testNg

powermock

easymock

mockito

jmock

 

作者认为上面最好的是powermock和mockito

powermock是对mockito的补充,可以mock静态方法、final类型的方法、final类型的class和局部变量。mockito不能做这几个mock。

绝佳:用mockito+powermock+junit的组合。

从功能测试角度看的工具:

     concordion

    cucumber:DDD主流,功能强大

比如关注数据的input和output的报表,就会用到concordion,关注到某一个功能相关的,就会用到cucumber。

 

集成工具:

    jekins

    git/github/git workflow

好的单元测试的特征:

自动化

执行要够快

每个test不能依赖其他test(独立、任意顺序执行)

test不能依赖外部资源(数据库、文件、网络资源、第三方API接口等)

任何时间和任何环境执行结果一样

test必须有意义(没必要测试get、set、toString和代码自动生成的这些方法)

test要短

test和业务代码一样对待

配置、源代码、测试代码,都要有版本控制。【上线后,哪怕改动配置文件,也要做充分的测试、公告、版本管理】

 

实际上,测试时是需要依赖很多外部资源的,比如数据库、调用第三方接口的入参、文件等。并且对这些资源修改后,还有对齐还原,就会又很耗费资源---》解决方法:mock数据。

mock就是替身。

2.快速入门

1mockito干什么的?

获取数据库连接并且读写数据

连接网络和下载文件

发送邮件

调用某个web的服务

调用打印机、出报表等IO操作

上面这些场景就不需要真正连接外部资源了,可以直接用mockito去模拟。

 

2)快速开始

步骤:

-1)引入依赖

引入mockito和junit

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>

引入servlet-api:假设用户在页面的登陆行为

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

servlet要有tomcat容器,用户登陆的username、password放到数据库中,就用mockito。

 

-2)写Accout类

public class Account {
}

-3)写AccountLoginController.java

public class AccountLoginController {

    private final AccountDao accountDao;

    /**
     * 通过构造器的方式注入
     * @param accountDao
     */
    public AccountLoginController(AccountDao accountDao) {
        this.accountDao = accountDao;
    }


    public String login(HttpServletRequest request) {
        final String username = request.getParameter("username");
        final String password = request.getParameter("password");
        //数据库也有不可用的情况,加上try
        try {
            Account account = accountDao.findAccount(username, password);
            if(account == null) {
                return "/login";
            }else {
                return "/index";
            }
        }catch (Exception e) {
            return "/505";
        }
    }
}

-4)写AccountDao的findAccount方法

public class AccountDao {

    public Account findAccount(String username, String password) {
        //模拟不可调,一调就抛异常
        throw new UnsupportedOperationException("db可用");
    }
}

-5)写单元测试;AccountLoginControllerTest

@RunWith(MockitoJUnitRunner.class)
public class AccountLoginControllerTest {

    private AccountDao accountDao;

    private HttpServletRequest request;

    private AccountLoginController accountLoginController;

    @Before
    public void setUp(){
        this.accountDao = Mockito.mock(AccountDao.class);
        this.request = Mockito.mock(HttpServletRequest.class);
        this.accountLoginController = new AccountLoginController(accountDao);
    }

    @Test
    public void testLoginSuccess() {
        Account account = new Account();
        //when是静态导入,实际是Mockito.when()
        when(request.getParameter("username")).thenReturn("alex");
        when(request.getParameter("password")).thenReturn("123456");
        when(accountDao.findAccount(anyString(), anyString())).thenReturn(account);
        assertThat(accountLoginController.login(request), equalTo("/index"));
    }

    @Test
    public void testLoginFailure() {
        when(request.getParameter("username")).thenReturn("alex");
        when(request.getParameter("password")).thenReturn("123451");
        when(accountDao.findAccount(anyString(), anyString())).thenReturn(null);
        assertThat(accountLoginController.login(request), equalTo("/login"));
    }

    @Test
    public void testLogin505() {
        when(request.getParameter("username")).thenReturn("alex");
        when(request.getParameter("password")).thenReturn("123451");
        when(accountDao.findAccount(anyString(), anyString())).thenThrow(UnsupportedOperationException.class);
        assertThat(accountLoginController.login(request), equalTo("/505"));
    }

}

 

解释:

-1)初始化

controller中的HttpServletRequest(前端请求)和AccountDao(数据库查询的数据)是外部资源,因此将这两个变量作为类的属性,并在setUp中通过Mock方式赋值。

AccountController是要测试的类,也作为类的属性,并在setUp中直接new来赋值。

 

-2)测试:

login方法有三种不同的结果:登陆成功、登陆失败、数据库异常返回505,针对每种不同的结果mock然后断言测试。

when(...).thenReturn(...):当调用...,就返回... (就模拟调用外部资源[mock对象]的返回值),这个模拟过程会替换调实际执行结果中 的调用并且作为返回值给实际执行中。

when(...).thenThrow(...):当调用...,就抛出异常....

assertThat(...., equalTo(....)):断言(....等于....)。调用测试的目标方法,得到实际执行结果,断言实际执行结果等于期望结果。

 

anyString():Macthers.anyString() 返回任意的字符串,可以作为参数模拟。相应的还有anyInt、anyList等

 

 // 静态导入会使代码更简洁

 import static org.mockito.Mockito.*;

 

3.几种不同的mock方式以及深度mock

1mock方式

mock一个对象,就是用mock的东西去替代真实依赖的外部资源(比如db、文件等)

mock方式:

    -1@RunWith(MockitoJUnitRunner.class):在类上

    -2@Mock

       在类的属性上(mock的对象)标注@Mock注解

       写init()方法,   ---->不写这个就会报NPE

       方法上标注@Before

       在方法中要初始化mockMockitoAnnotations.initMock(this);

      (这个初始化mock什么意思?)

    -3@Rule

                    public MockitoRule mockitoRule = MockitoJUnit.rule();

方式1:@RunWith

@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest {

    @Test
    public void testMock() {
        AccountDao accountDao = mock(AccountDao.class);
        //AccountDao accountDao 
        = mock(AccountDao.class, Answers.RETURNS_SMART_NULLS)

        //调用这个方法不会抛异常,得到null(没有写when、return这些stubbing)。
        Account account = accountDao.findAccount(“x”, “x”);

    }
}

mock(classToMock):传入要mock的类

mock(classToMock, defaultAnswer):传入要mock的类和默认的answer。当没有给mock的对象进行stubbing(when...thenReturn...),就会返回默认的answer。如果没有指定answer,也有全局的answer(根据类型来定)。

 

方式2:@Mock + MockitoAnnotations.initMocks(this);

public class MockByAnnotationTest {

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }


     @Mock
    //@Mock(answer=Answers.RETURNS.SMART_NULLS)
    AccountDao accountDao;

    @Test
    public void testMock() {
        Account account = accountDao.findAccount("x", "x");
    }
}

 

给mock对象设置Answer

@Mock

@Mock(answer=Answer.RETURNS_SMART_NULLLS)

 

方式3:@Rule + MockitoRule

public class MockitoByRuleTest {

    //必须是public的属性
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testMock() {
        AccountDao accountDao = mock(AccountDao.class);
        //也可以将accountDao作为属性,用@Mock标注,在test方法中直接用,代替上面这行

        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }

}

 

2)深度mock

-1)写个Lesson03Service类和Lesson03

 

-2)测试

1】空指针异常

public class DeepMockTest {

    @Mock
    private Lesson03Service lesson03Service;
    
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testNPEMock() {
        Lesson03 lesson03 = lesson03Service.get();
        lesson03.foo();
    }

}

上面会报空指针异常。因为mock的lesson03Service调用的get方法,返回的Lesson03对象是null,再调用foo()就会报NPE。

2stub

 

public class DeepMockTest {

    @Mock
    private Lesson03Service lesson03Service;

    @Mock
    private Lesson03 lesson03;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testStubMock() {
        //调用lesson03Service.get()就返回mock的lesson03对象(sutb)
        when(lesson03Service.get()).thenReturn(lesson03);
        //这个res03就是mock的lesson03
        Lesson03 res03 = lesson03Service.get();
        //此时就可以调foo()方法了,因为此时的res03不是null了
        res03.foo();
    }


}

 

3】深度mock

 

public class DeepMockTest {
    
    //深度mock,mock了lesson03Service,也自动mock了调用它的方法的返回值
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private Lesson03Service lesson03Service;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void testDeepMock() {
        lesson03Service.get().foo();
    }
    
}

深度mock中,自动帮助mock的返回值不一定是我们想要的返回值。

 

4.Stub语法详解

stub有人也称为测试桩。可以不用翻译。

stubmock的理解:

       stub是对方法的stub,调用mock对象的某个方法,进而返回某个值

       mock是模拟的意思,mock一个对象

 

 

easyMock中:

          when().thenReturn() ---->称为录制,约定当调用什么,其中传什么参数,就返回什么内容

   后面的实际调用,就按照约定的形式返回-----播放

 

上面的when().thenReturn() 类似这种操作就是stub

 

 

使用Stub,我们只关注方法的调用和返回的结果。一个方法被stub后,该方法就只返回该结果。

注意:

      staticfinal方法,无法进行stub操作

  当连续为一个方法stub操作,只会调用最近的一次。

 

测试:

@RunWith(MockitoJUnitRunner.class)
public class StubblingTest {

    private List<String> list;

    @Before
    public void inti() {
        this.list = mock(ArrayList.class);
    }

    @Test
    public void howToStubblingReturn() {
        //实例list中没有放数据,这边的stubbling只是mock一种行为:
        // 当从list取出第一个元素list.get(0),就返回"first"
        when(list.get(0)).thenReturn("first");
        assertThat(list.get(0), equalTo("first"));
    }

    @Test
    public void howToStubblingException() {

        //入参为任何数字,抛出异常
        when(list.get(anyInt())).thenThrow(new RuntimeException());
        try {
            list.get(0);
            fail();
        }catch (Exception e) {
            assertThat(e, instanceOf(RuntimeException.class));
        }


    }

    /**
     * 没有返回值的方法的校验执行次数的stubbling
     */
    @Test
    public void howToStubblingVoidVerifyMethod() {
        doNothing().when(list).clear();
        list.clear();
        //verify检验list是否执行了1次的clear方法
        verify(list, times(1)).clear();
    }

    /**
     * 没有返回值的方法的抛出异常的stubbling
     */
    @Test
    public void howToStubblingVoidException() {
        doThrow(RuntimeException.class).when(list).clear();
        try{
            list.clear();
            //这个有什么用
            fail();
        }catch (Exception e) {
            assertThat(e, instanceOf(RuntimeException.class));
        }

    }


    @Test
    public void stubDoReturn() {
        //这两个方法等价
        when(list.get(0)).thenReturn("first");
        doReturn("second").when(list).get(1);

        assertThat(list.get(0), equalTo("first"));
        assertThat(list.get(1), equalTo("second"));
    }

    /**
     * 相同调用,返回不同的值,最后一次调用生效
     */
    @Test
    public void stubOverride() {
        when(list.size()).thenReturn(1);
        when(list.size()).thenReturn(2);
        when(list.size()).thenReturn(3);
        when(list.size()).thenReturn(4);

        //前三个断言都失败
        assertThat(list.size(), equalTo(1));
        assertThat(list.size(), equalTo(2));
        assertThat(list.size(), equalTo(3));
        //断言成功
        assertThat(list.size(), equalTo(4));

    }

    @Test
    public void iterateStub() {
        when(list.size()).thenReturn(1, 2, 3, 4);
        /**和上面等价
        when(list.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);*/

        //4个断言都成功,第几次调用返回第几个值,
        // 当前面的值都用完了,以后的调用都是返回的最后一个值
        assertThat(list.size(), equalTo(1));
        assertThat(list.size(), equalTo(2));
        assertThat(list.size(), equalTo(3));
        assertThat(list.size(), equalTo(4));
        //后面调用都返回最后一个返回的值
        assertThat(list.size(), equalTo(4));
        assertThat(list.size(), equalTo(4));
        assertThat(list.size(), equalTo(4));
    }

    /**
     * 需求:给定一个数字,返回该数字*10的字符串
     * 用thenAnswer,可以灵活写返回值
     */
    @Test
    public void stubAnswer() {
        when(list.get(anyInt())).thenAnswer(invocationOnMock -> {
            Integer index = invocationOnMock.getArgumentAt(0, Integer.class);
            return String.valueOf(index*10);
        });
        assertThat(list.get(0), equalTo("0"));
        assertThat(list.get(999), equalTo("9990"));
    }


    @Test
    public void stubThenCallRealMethod() {
        StubblingService service = mock(StubblingService.class);
        //调用mock对象的方法,不会执行原来对象的方法

        //调用mock对象的方法,实际调用的是代理对象的方法,返回值是默认值或者指定的值(stub)
        service.getS();
        System.out.println(service.getClass());
        //class com.yang.mockito.lessson04.StubblingService
        //$$EnhancerByMockitoWithCGLIB$$3264aa13

        //现在希望调用getI()可以执行原来对象的方法 ( getI()不需要外部资源 )
        //thenCallRealMethod

        when(service.getS()).thenReturn("alex");
        assertThat(service.getS(), equalTo("alex"));
        when(service.getI()).thenCallRealMethod();
        assertThat(service.getI(), equalTo(10));

    }




    @After
    public void destroy() {
        //销毁/重置stubbling的动作
        reset(this.list);
    }

}

 

 

StubblingService.java

public class StubblingService {
    public int getI() {
        System.out.println("getI()执行....");
        return 10;
    }

    public String getS() {
        throw new RuntimeException();
    }
}

 

 

5.Spy

mock出来的对象在调用方法时,都不会执行原来对象的方法(除非when(....).thenCallRealMethod())

    Spy可以spy一个对象,和mock出来一个对象相同。但作用不同。

    spymock都是代理对象。

 

Spying作用:

    和mock对象相反,当Spy一个对象后,调用它的方法,如果该方法没有被stub,就会真正执行原来对象的真正方法。如果spy对象的方法被stub,就会返回stub的值。

   【mock对象,调用它的方法,不管有没有stub,执行它的方法,都不会执行原来对象的方法】

 

spy+stub,起到部分方法mock的作用

@RunWith(MockitoJUnitRunner.class)
public class SpyingTest {

    public void testSpy() {
        List<String> realList = new ArrayList<>();
        List<String> list = spy(realList);

        list.add("Mockito");
        list.add("PowerMockito");

        //会执行realList(原对象)的方法,而不是spy的list的方法
        //如果是mock出来的对象,只会执行mock对象的方法
        assertThat(list.get(0), equalTo("Mockito"));
        assertThat(list.get(1), equalTo("PowerMockito"));
        assertThat(list.isEmpty(), equalTo(false));

    }

    /**
     * spy+stub,起到部分方法的mock
     * spy的对象,对它的某些方法stub,调用这些方法就会返回stub的值;
     * 其他没有stub的方法就会调用原来对象的方法,放回真正的值
     */
    @Test
    public void testSpyStub() {
        List<String> realList = new ArrayList<>();
        List<String> list = spy(realList);

        list.add("Mockito");
        list.add("PowerMockito");

        when(list.isEmpty()).thenReturn(true);
        when(list.size()).thenReturn(0);

        //该方法没有stub,就会调用原来对象的方法返回值
        assertThat(list.get(0), equalTo("Mockito"));
        //返回stub方法的mock出来的值
        assertThat(list.isEmpty(), equalTo(true));
        assertThat(list.size(), equalTo(0));
    }
    

}

 

采用@Spy注解的方式spy

 

public class SpyingAnnotationTest {

    @Spy
    private List<String> list = new ArrayList<>();

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testSpy() {
        list.add("Mockito");
        list.add("PowerMockito");

        when(list.size()).thenReturn(0);

        assertThat(list.get(0), equalTo("Mockito"));
        assertThat(list.size(), equalTo(0));
    }
}

 

 

 

6.Argument Matcher

很关键。

argument matcher是参数匹配器。

比如stubmock对象的方法会传一些参数,然后返回给定的值

当后面执行该方法,传入该参数时,matcher就会进行匹配方法和参数,相同就返回相应的值;如果一个都没匹配到,就返回返回类型的默认值。

 

 

when().thenReturn()  都是由matcher保证不同的参数返回不同的返回值。

 

isA(class<T> clazz)

any(class<T> clazz)

eq(primitive value)

public class ArgumentMatcherTest {

    @Test
    public void basicTest() {
        List list = mock(List.class);


        //stub动作
        when(list.get(0)).thenReturn(100);

        //stub后,当执行mock的方法时(即list.get(0)),Matcher就起作用,判断这个方法的参数是否和stub中写的参数
        // 是否一样,判断的方法就是java中的"equals";如果参数相同,会返回stub的返回值100
        list.get(0);
        assertThat(list.get(0), equalTo(100));

        //参数1,就没有匹配到stub中的参数,就返回null
        list.get(1);
        assertThat(list.get(1), equalTo(null));
        assertThat(list.get(1), nullValue());

        //注意:写单元测试时,入参很重要,要设计好Matcher
    }

    /**
     * 匹配该类或者子类型
     */
    @Test
    public void testIsA() {
        Foo foo = mock(Foo.class);
        when(foo.function(isA(Parent.class))).thenReturn(100);
        int result1 = foo.function(new Child1());
        int result2 = foo.function(new Child2());
        //isA()匹配器可以匹配到Parent.class和它的子类/实现类
        assertThat(result1, equalTo(100));
        assertThat(result2, equalTo(100));

        //注意要重置mock(不要之前的stub),
        // 否则会对下面的有影响(一般不用,因为下面的要新建一个test)
        reset(foo);

        when(foo.function(isA(Child1.class))).thenReturn(100);
        int res1 = foo.function(new Child1());
        int res2 = foo.function(new Child2());
        assertThat(res1, equalTo(100));
        //断言失败,child2不是child1或它的子类。没有指定就默认返回0
        assertThat(res2, equalTo(100));
        //断言成功
        assertThat(res2, equalTo(0));

    }


    /**
     * 匹配任何,只要满足类型检查即可
     */
    @Test
    public void testAny() {
        Foo foo = mock(Foo.class);
        when(foo.function(any(Child1.class))).thenReturn(100);
        int result = foo.function(new Child2());
        assertThat(result, equalTo(100));
        
    }


    static class Foo {
        int function(Parent parent) {
            return parent.work();
        }
    }

    interface Parent {
        int work();
    }

    class Child1 implements Parent {

        @Override
        public int work() {
            throw new RuntimeException();
        }
    }

    class Child2 implements Parent {

        @Override
        public int work() {
            throw new RuntimeException();
        }
    }

}

 

7.WildCard Argument Matcher

通配的参数匹配器。

stub中方法的参数可以是任意数字(anyInt())/任意字符串(anyString())...,或者是任意类型的子类(isA()),或者直接是任意(any())....,就是WildCard Argument Matcher处理

 

anyXXX()

any()

isA()

eq()

 

 

代码:

 

好的习惯,在单元测试最后一个destory方法(@Before注解)reset下这个mock(mock中的这些stub行为都消除。)

可以reset(),也可以reset某个具体的对象reset(mock对象)

 

 

@RunWith(MockitoJUnitRunner.class)
public class WildcardArgumentMatcherTest {

    @Mock
    private SimpleService simpleService;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void wildcardMethod1() {
        when(simpleService.method1(anyInt(),
                anyString(),
                anyCollection(),
                isA(Serializable.class))
        ).thenReturn(100);
        int result = simpleService
                .method1(1, "Alex", Collections.EMPTY_LIST, "Mockito");
        assertThat(result, equalTo(100));
    }

    /**
     * 用法报错,如果参数中有matcher,其他参数都要是matcher
     */
    @Test
    public void testWildcardMethod1WithSpecFalse() {
        //如果参数中有一个是matcher,其他参数都要是matcher,而不是具体的值
        when(simpleService.method1(anyInt(),
                "Alex",
                anyCollection(),
                isA(Serializable.class))
        ).thenReturn(100);
        when(simpleService.method1(anyInt(),
                "Mockito",
                anyCollection(),
                isA(Serializable.class))
        ).thenReturn(200);
        int result = simpleService
                .method1(1, "Alex", Collections.EMPTY_LIST, "Mockito");
        assertThat(result, equalTo(100));
        int result2 = simpleService
                .method1(1, "Mockito", Collections.EMPTY_LIST, "Mockito");
        assertThat(result2, equalTo(200));

    }

    /**
     * 将具体的值变为eq(具体的值)---》matcher,就和其他matcher一起作为参数
     */
    @Test
    public void testWildcardMethod1WithSpecSuccess() {
        when(simpleService.method1(anyInt(),
                eq("Alex"),
                anyCollection(),
                isA(Serializable.class))
        ).thenReturn(100);
        when(simpleService.method1(anyInt(),
                eq("Mockito"),
                anyCollection(),
                isA(Serializable.class))
        ).thenReturn(200);
        int result = simpleService
                .method1(1, "Alex", Collections.EMPTY_LIST, "Mockito");
        assertThat(result, equalTo(100));
        int result2 = simpleService
                .method1(1, "Mockito", Collections.EMPTY_LIST, "Mockito");
        assertThat(result2, equalTo(200));
        int result3 = simpleService
                .method1(1, "Other", Collections.EMPTY_LIST, "Mockito");
        assertThat(result3, equalTo(0));

    }

    @Test
    public void testMethod2() {
        List list = Collections.EMPTY_LIST;
        doNothing()
                .when(simpleService)
                .method2(anyInt(), anyString(), anyCollection(), isA(Serializable.class));
        simpleService
                .method2(1, "Alex", list, "Mockito");
        //verify验证后面的给定参数的方法 在前面执行的次数
        verify(simpleService, times(1))
                .method2(1, "Alex", list, "Mockito");
    }

    /**
     * 注意matcher匹配范围和顺序的关系:越在后面,优先级越高
     * 成功
     */
    @Test
    public void testOrderSuccess() {
        when(
                simpleService.method1(
anyInt(), anyString(), anyCollection(), isA(Serializable.class))
        ).thenReturn(-1);
        when(
                simpleService.method1(
anyInt(), eq("Alex"), anyCollection(), isA(Serializable.class))
        ).thenReturn(100);
        when(
                simpleService.method1(
anyInt(), eq("Mockito"), anyCollection(), isA(Serializable.class))
        ).thenReturn(100);

        //anyString()在最前面,除了"Alex","Mockito"返回100,其他string都返回-1

        int result1 = simpleService
                .method1(1, "Alex", Collections.EMPTY_LIST, "Mockito");
        assertThat(result1, equalTo(100));

        int result2 = simpleService
                .method1(1, "Mockito", Collections.EMPTY_LIST, "Mockito");
        assertThat(result2, equalTo(100));

        int result3 = simpleService
                .method1(1, "Other", Collections.EMPTY_LIST, "Mockito");
        assertThat(result3, equalTo(-1));

    }

    /**
     * 注意matcher匹配范围和顺序的关系:越在后面,优先级越高
     * 失败
     */
    @Test
    public void testOrderFail() {

        when(
                simpleService.method1(
anyInt(), eq("Alex"), anyCollection(), isA(Serializable.class))
        ).thenReturn(100);
        when(
                simpleService.method1(
anyInt(), eq("Mockito"), anyCollection(), isA(Serializable.class))
        ).thenReturn(100);
        when(
                simpleService.method1(
anyInt(), anyString(), anyCollection(), isA(Serializable.class))
        ).thenReturn(-1);

        //anyString() 在最后,所以string都是返回-1,包括前面的


        int result1 = simpleService
                .method1(1, "Alex", Collections.EMPTY_LIST, "Mockito");
        assertThat(result1, equalTo(100));

        int result2 = simpleService
                .method1(1, "Mockito", Collections.EMPTY_LIST, "Mockito");
        assertThat(result2, equalTo(100));

        int result3 = simpleService
                .method1(1, "Other", Collections.EMPTY_LIST, "Mockito");
        assertThat(result3, equalTo(-1));

    }





    /**
     * 好的习惯,写个after,reset下mock
     */
    @After
    public void destroy() {
        reset(simpleService);
    }


}

 

8.Hamcrest Matcher

assertThat(String reason, T actual, Matcher<? super T> matcher)

断言都可以调用上面的方法,不同的匹配模式就实现不同的matcher就行,这个接口不用变。

如果是AssertEquals(Double actual, Double expected),那么要增加类型时,就需要再修改这个类,增加相应的方法。

 

public class AssertMatcherTest {


    @Test
    public void test() {
        int i = 10;
        //hamcrest的matcher方式
        assertThat(i, equalTo(10));
        //not(matcher)
        assertThat(i, not(equalTo(20)));
        assertThat(i, is(10));
        assertThat(i, not(is(20)));
        //两个中一个通过就行:either(matcher).or(matcher)
        assertThat(i, either(equalTo(20)).or(equalTo(10)));
        //两个都要满足:both(matcher).and(matcher)
        assertThat(i, both(equalTo(10)).and(not(equalTo(20))));
        //任何一个满足:anyOf(matcher1, matcher2, matcher3)
        assertThat(i, anyOf(equalTo(10), is(20), not(equalTo(30))));
        //都要满足:allOf(matcher1, matcher2, matcher3)
        assertThat(i, allOf(equalTo(10), is(10), not(equalTo(20)), not(is(20))));

        //传统的junit方式
        Assert.assertEquals(i, 10);
    }

    @Test
    public void testDesc() {
        int i = 10;
        //assertThat(String reason, Object actual, Matcher matcher)
        //失败时,会给出reason的原因
        assertThat("i 要等于10", i, equalTo(20));
    }


}

 

 

9.自定义Matcher

 

一般不用自定义扩展,因为本身已经提供了很丰富的内容。

 

步骤:

-1GreaterThan类继承BaseMatcher

public class GreaterThan<T extends Number> extends BaseMatcher<T> {

    private final T value;
    private GreaterThan(T value) {
        this.value = value;
    }

    /**
     *
     * @param actual 就是assertThat(10, gt(5))中传来的10
     * @return
     */
    @Override
    public boolean matches(Object actual) {
        Class<?> clazz = actual.getClass();
        if(clazz == Integer.class) {
            //value是构造器gt(5)中传来的值5
            return (Integer) actual > (Integer) value;
        }else if(clazz == Short.class){
            return (Short) actual > (Short) value;
        }else if(clazz == Long.class) {
            return (Long) actual > (Long) value;
        }else if(clazz == Byte.class) {
            return (Byte) actual > (Byte) value;
        }else if(clazz == Float.class) {
            return (Float) actual > (Float) value;
        }
        throw new RuntimeException("不支持" + clazz.getName() + "类型");
    }

    /**
     * 调用gt(value),实际就返回GreaterThan对象
     * @Factory标明这个是个工厂返回,没什么用
     */
    @Factory
    public static <T extends Number> GreaterThan<T> gt(T value) {
        return new GreaterThan<>(value);
    }


    @Override
    public void describeTo(Description description) {
        description.appendText("比较数字失败");
    }
}

-2)实际调用

 

public class SimpleTest {

    @Test
    public void test() {
        //自定义lt、gt、
        assertThat(10, GreaterThan.gt(20));
    }
}

 

上面的GreaterThan类有个很大的问题,当有其他类型要判断时(比如以后增加新的类型,或者现在有些类型没有写),就需要修改GreaterThan类。

就是GraterThan类做了不止一件事,又要比较,又要判断类型。

解决方法:把判断类型的逻辑抽离出来。

 

扩展版:

-1)写Compare接口

 

public interface Compare {

    boolean compare(Object expected);
}

 

-2)写默认的Compare实现:DefaultCompare

 

public class DefaultCompare<T extends Number>  implements Compare{

    private T value;

    public DefaultCompare(T value) {
        this.value = value;
    }


    @Override
    public boolean compare(Object actual) {
        Class<?> clazz = actual.getClass();
        if(clazz == Integer.class) {
            //value是构造器gt(5)中传来的值5
            return (Integer) actual > (Integer) value;
        }else if(clazz == Short.class){
            return (Short) actual > (Short) value;
        }else if(clazz == Long.class) {
            return (Long) actual > (Long) value;
        }else if(clazz == Byte.class) {
            return (Byte) actual > (Byte) value;
        }else if(clazz == Float.class) {
            return (Float) actual > (Float) value;
        }
        throw new RuntimeException("不支持" + clazz.getName() + "类型");
    }


}

 

-3)写GreaterThanNew

 

public class GreaterThanNew<T extends Number> extends BaseMatcher<T> {

    private T value;
    private Compare compare;


    private GreaterThanNew(T value, Compare compare) {
        this.value = value;
        this.compare = compare;
    }



    @Override
    public boolean matches(Object o) {
        return compare.compare(o);
    }

    @Override
    public void describeTo(Description description) {

    }

    public static GreaterThanNew gt(Number o) {
        return new GreaterThanNew(o, new DefaultCompare<>(o));
    }

    /**
     *如果有新的类型,就实现新的compare
     */
    public static GreaterThanNew gt(Number o, Compare compare) {
        return new GreaterThanNew(o, compare);
    }

}

 

 

-4)测试

 

public class GreaterThanNewTest {

    @Test
    public void test1() {
        assertThat(10, GreaterThanNew.gt(1));
    }

}

 

posted @ 2021-03-07 22:07  yq055783  阅读(828)  评论(0编辑  收藏  举报