• 单元测试之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));
        }
    
    }
  • 相关阅读:
    [Go] 理解(*interface{})(nil) 赋值的变量是否为nil
    [Linux] 理解CPU缓存的伪共享问题
    [MySQL] 理解InnoDB并发高的原因
    [Go] 理解计算机负数的表示以及整数范围
    [Go]理解golang项目性能分析工具trace
    [Go]理解golang项目性能分析工具PProf
    [Go] 理解切片slice扩容
    [javascript]解决多个版本的jquery库或者$冲突
    [Linux] 理解Reactor 模型
    [Linux] 理解I/O多路复用
  • 原文地址:https://www.cnblogs.com/yq055783/p/14496804.html
Copyright © 2020-2023  润新知