单元测试之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.快速入门
(1)mockito干什么的?
获取数据库连接并且读写数据
连接网络和下载文件
发送邮件
调用某个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
(1)mock方式
mock一个对象,就是用mock的东西去替代真实依赖的外部资源(比如db、文件等)
mock方式:
-1)@RunWith(MockitoJUnitRunner.class):在类上
-2)@Mock:
在类的属性上(要mock的对象)标注@Mock注解
写init()方法, ---->不写这个就会报NPE
方法上标注@Before;
在方法中要初始化mock:MockitoAnnotations.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。
2】stub
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有人也称为测试桩。可以不用翻译。
stub和mock的理解:
stub是对方法的stub,调用mock对象的某个方法,进而返回某个值
mock是模拟的意思,mock一个对象
easyMock中:
when().thenReturn() ---->称为录制,约定当调用什么,其中传什么参数,就返回什么内容
后面的实际调用,就按照约定的形式返回-----》播放
上面的when().thenReturn() 类似这种操作就是stub。
使用Stub,我们只关注方法的调用和返回的结果。一个方法被stub后,该方法就只返回该结果。
注意:
static、final方法,无法进行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出来一个对象相同。但作用不同。
spy和mock都是代理对象。
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是参数匹配器。
比如stub中mock对象的方法会传一些参数,然后返回给定的值
当后面执行该方法,传入该参数时,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
一般不用自定义扩展,因为本身已经提供了很丰富的内容。
步骤:
-1)GreaterThan类继承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)); } }