目录
EasyMock
1、概述
EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock 对象的类库。EasyMock提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。
Maven依赖:
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>4.3</version>
<scope>test</scope>
</dependency>
使用 EasyMock包括以下几个步骤:
- 创建目标类的Mock
- 录制(Recording)其预期行为,包括动作、结果、调用次数、异常等
- 录制完成后,我们将其切换到重播(Replay)模式,以便Mock在被使用者调用时,表现得和录制时一样
- 在测试中使用mock对象
- 验证它是否按预期运行
2、简单示例
被Mock的接口:
public interface CalculatorService {
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
}
应用,依赖于CalculatorService接口:
public class MathApplication {
private CalculatorService calculatorService;
public MathApplication(CalculatorService calculatorService) {
this.calculatorService = calculatorService;
}
public double add(double a, double b) {
return calculatorService.add(a, b);
}
public double subtract(double a, double b) {
return calculatorService.subtract(a, b);
}
public double multiply(double a, double b) {
return calculatorService.multiply(a, b);
}
public double divie(double a, double b) {
return calculatorService.divide(a, b);
}
}
测试:
//指定EasyMock运行器
@RunWith(EasyMockRunner.class)
public class MathApplicationTest {
@Mock//指示哪个对象会被Mock创建
private CalculatorService calculatorService;
@TestSubject//指示哪个类会使用Mock对象
private MathApplication mathApplication = new MathApplication(calculatorService);
@Test
public void add() {
//recording,录制mock对象的预期行为,包括动作、结果、异常等
EasyMock.expect(calculatorService.add(10.0, 20.0))
.andReturn(30.0);
EasyMock.expect(calculatorService.add(11.0, 20.0))
.andReturn(31.0);
//Replay,录制完成后,切换到重播模式,以便Mock在被使用者调用时,表现得和录制时一样
EasyMock.replay(calculatorService);
//在测试中使用
Assert.assertEquals(mathApplication.add(10.0, 20.0),30.0,0);
Assert.assertEquals(mathApplication.add(11.0, 20.0),31.0,0);
//验证mock对象是否按预期运行
EasyMock.verify(calculatorService);
}
}
3、使用注解Mock对象
EasyMock注释可用于创建Mock对象。 我们还可以告诉EasyMock框架将这些模拟对象注入到另一个具体的类中。
3.1、常用注解
- @Mock:用于指定EasyMock要模拟的字段。
- @TestSubject:用于指定一个对象,我们希望EasyMock在其中注入使用@Mock注释创建的对象。
3.2、使用方式
当我们使用EasyMock注释时,我们必须通过以下方法之一显式初始化它们:
- @RunWith(EasyMockRunner.class):如果我们使用的是JUnit 4,可以使用它,请注意, JUnit 5仍然不支持此功能。 如果我们将其与JUnit 5一起使用,则它将回退以使用JUnit 4运行器类。
- org.easymock.EasyMockRule:该方式利用了JUnit 4规则,因此同样不能与JUnit 5一起使用。
- EasyMockSupport.injectMocks(this):可以在@Before方法中使用它来告诉EasyMock注入模拟对象。 这是JUnit 5和TestNG框架的首选方法。
被Mock的接口:
public interface IntegerUtils {
int add(int x, int y);
}
public interface StringUtils {
String reverse(String input);
String convert(int i);
}
应用类,依赖接口:
public class MyUtils {
private StringUtils su;
private IntegerUtils iu;
public MyUtils(StringUtils su, IntegerUtils iu) {
this.su = su;
this.iu = iu;
}
public int add(int i, int j) {
return iu.add(i, j);
}
public String reverse(String s) {
return su.reverse(s);
}
public String convert(int i) {
return su.convert(i);
}
}
1)、使用@RunWith
@RunWith(EasyMockRunner.class)
public class EasyMockAnnotationsRunWithExample {
@Mock
StringUtils mockSU;
@Mock
IntegerUtils mockIU;
@TestSubject
MyUtils mu = new MyUtils(mockSU, mockIU);
@Test
public void test() {
EasyMock.expect(mockIU.add(10, 20)).andReturn(30);
EasyMock.expect(mockSU.convert(10)).andReturn("10");
EasyMock.expect(mockSU.reverse("hello")).andReturn("olleh");
EasyMock.replay(mockSU, mockIU);
Assert.assertEquals(30, mu.add(10, 20));
Assert.assertEquals("10", mu.convert(10));
Assert.assertEquals("olleh", mu.reverse("hello"));
}
}
2)、使用EasyMockRule
public class EasyMockAnnotationsEasyMockRuleExample {
@Mock
StringUtils mockSU;
@Mock
IntegerUtils mockIU;
@TestSubject
MyUtils mu = new MyUtils(mockSU, mockIU);
@Rule
public EasyMockRule easyMockRule = new EasyMockRule(this);
@Test
public void test() {
EasyMock.expect(mockIU.add(10, 20)).andReturn(30);
EasyMock.expect(mockSU.convert(10)).andReturn("10");
EasyMock.expect(mockSU.reverse("hello")).andReturn("olleh");
EasyMock.replay(mockSU, mockIU);
Assert.assertEquals(30, mu.add(10, 20));
Assert.assertEquals("10", mu.convert(10));
Assert.assertEquals("olleh", mu.reverse("hello"));
}
}
3)、使用EasyMockSupport
public class EasyMockAnnotationsInjectExample {
@Mock
StringUtils mockSU;
@Mock
IntegerUtils mockIU;
@TestSubject
MyUtils mu = new MyUtils(mockSU, mockIU);
@Before
public void setup() {
EasyMockSupport.injectMocks(this);
}
@Test
public void test() {
EasyMock.expect(mockIU.add(10, 20)).andReturn(30);
EasyMock.expect(mockSU.convert(10)).andReturn("10");
EasyMock.expect(mockSU.reverse("hello")).andReturn("olleh");
EasyMock.replay(mockSU, mockIU);
Assert.assertEquals(30, mu.add(10, 20));
Assert.assertEquals("10", mu.convert(10));
Assert.assertEquals("olleh", mu.reverse("hello"));
}
}
4、EasyMock常用方法
4.1、创建mock对象
- EasyMock.createMock:
CalculatorService calculatorService = EasyMock.createMock(CalculatorService.class);
- EasyMock.mock:
ArrayList<Object> mockList = EasyMock.mock(ArrayList.class);
- EasyMock.createStrictMock:只允许此Mock按expect定义的顺序被方法调用,也就是说,Mock会检查方法调用的顺序。任何expect之外的方法调用都会导致AssertionError
CalculatorService calculatorService = EasyMock.createStrictMock(CalculatorService.class);
- EasyMock.createNiceMock:创建了模拟,并设置模拟的每个方法的默认实现。如果使用EasyMock.createMock(),然后模拟方法调用将抛出断言错误
CalculatorService calculatorService = EasyMock.createNiceMock(CalculatorService.class);
4.2、录制mock对象行为
- EasyMock.expect
- EasyMock.expectLastCall
CalculatorService calculatorService = EasyMock.createMock(CalculatorService.class);
//录制mock对象行为,方法有返回值
EasyMock.expect(calculatorService.add(1.0, 2.0))
.andReturn(3.0)//指定返回值
.times(3)//指定调用次数
.andThrow(new NullPointerException());
//录制mock对象行为,方法没有返回值
calculatorService.someVoidMethod(1.0, 2.0);
EasyMock.expectLastCall()
.times(3)//指定调用次数
.andThrow(new NullPointerException());
4.3、进入replay阶段
- EasyMock.replay
EasyMock.replay(calculatorService);
4.4、验证verfiy
- EasyMock.verify
EasyMock.verify(calculatorService);
4.5、调用次数
EasyMock提供很多的方法来改变预期的调用计数:
- times(int min, int max):最小值和最大值之间的预期调用
- atLeastOnce():预期至少有一个调用
- anyTimes():预期调用的数量不受限制
5、参数匹配器
EasyMock参数匹配器使我们可以在对方法进行存根时提供灵活的参数以进行匹配。 您将在EasyMock中找到许多any*()
方法,这些方法可以与expect()
一起使用,以提供方法调用的参数。
5.1、一般参数匹配器
可以使用anyXxx()方法来将参数指定为任何对象。
- anyBoolean():任意Boolean
- anyByte():任意byte
- anyChar():任意char
- anyInt():任意int
- anyLong():任意long
- anyFloat():任意float
- anyDouble():任意double
- anyShort():任意short
- anyObject():任意Object
- anyObject(Class<T>):任意指定类型的Object
- anyString():任意String
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashSet;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
public class EasyMockAgrumentMatcherExample {
@Test
public void test() {
HashSet<Object> set = mock(HashSet.class);
expect(set.add(anyBoolean())).andReturn(true);
expect(set.add(anyByte())).andReturn(true);
expect(set.add(anyChar())).andReturn(true);
expect(set.add(anyInt())).andReturn(false);
expect(set.add(anyLong())).andReturn(false);
expect(set.add(anyFloat())).andReturn(false);
expect(set.add(anyDouble())).andReturn(false);
expect(set.add(anyShort())).andReturn(false);
expect(set.add(anyObject(ArrayList.class))).andReturn(false);
expect(set.add(anyString())).andReturn(true);
expect(set.add(anyObject())).andReturn(true);
replay(set);
assertTrue(set.add(true));
assertTrue(set.add((byte)1));
assertTrue(set.add('A'));
assertFalse(set.add(Integer.valueOf(1)));
assertFalse(set.add(Long.valueOf(1)));
assertFalse(set.add(Float.valueOf(2.1f)));
assertFalse(set.add(Double.valueOf(2.22)));
assertFalse(set.add(Short.valueOf("1")));
assertFalse(set.add(new ArrayList<>()));
assertTrue(set.add("hi"));
assertTrue(set.add(new Object()));
}
}
5.2、等于参数匹配器
可以使用same()参数匹配器方法对同一个对象的行为进行存根。 如果要执行相等性检查,请使用eq()方法。
- same():相当于比较引用
- eq():相当于equals
- aryEq():比较数组内容
- isA():比较类型
import org.junit.Test;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
public class EasyMockAgrumentMatcherEqualityExample {
@Test
public void test() {
Utils mock = mock(Utils.class);
Object obj = new Object();
//相当于比较引用
expect(mock.convert(same(obj))).andReturn("True");
//相当于equals
expect(mock.convert(eq("ABC"))).andReturn("Awesome");
expect(mock.convert(anyObject())).andReturn("False");
replay(mock);
assertEquals("True", mock.convert(obj));
assertEquals("Awesome", mock.convert("ABC"));
assertEquals("False", mock.convert(new Object()));
}
}
class Utils {
public String convert(Object obj) {
return obj.toString();
}
}
5.3、比较参数匹配器
我们可以使用lt() , gt() , leq()和geq()方法来比较参数并返回特定的输出。 请注意,这些方法被重载以与原始数据类型以及对象一起使用。 如果将它们与任何对象一起使用,则它们应该是Comparable 。
- lt():小于
- gt():大于
- leq():小于等于
- geq():大于等于
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
public class EasyMockAgrumentMatcherComparisonExample {
@Test
public void test() {
List mock = mock(ArrayList.class);
expect(mock.add(lt(10))).andReturn(true);
expect(mock.add(geq(10))).andReturn(false);
replay(mock);
assertTrue(mock.add(5));
assertFalse(mock.add(100));
}
}
5.4、条件参数匹配器
我们可以将and() , or()和not()函数与参数匹配器一起使用,以编写更复杂的行为。
- and():逻辑与
- or():逻辑或
- not():逻辑非
注意,当我们使用参数匹配器对行为进行存根时,每个参数都必须是匹配器,否则,我们将收到一条错误消息。 因此,如果我们必须指定not(100) ,则将其写为not(eq(100)) 。
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
public class EasyMockAgrumentMatcherConditionalExample {
@Test
public void test() {
List<Integer> mock = mock(ArrayList.class);
//大于0且小于10
expect(mock.add(and(gt(0), lt(10)))).andReturn(true);
//等于33或等于37
expect(mock.add(or(eq(33), eq(77)))).andReturn(true);
//不是100
expect(mock.add(not(lt(100)))).andReturn(false);
replay(mock);
assertTrue(mock.add(5));
assertTrue(mock.add(33));
}
}
5.5、空参数匹配器
我们可以使用isNull()和notNull()来匹配null和notNull()参数。 但是,当我们使用其他参数匹配器时, isNull()会派上用场,并且不能直接使用null。
- isNull():为Null
- notNull():不为Null
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
public class EasyMockAgrumentMatcherNullExample {
@Test
public void test() {
List<Object> mock = mock(ArrayList.class);
expect(mock.add(isNull())).andReturn(true);
expect(mock.add(notNull())).andReturn(false);
replay(mock);
assertTrue(mock.add(null));
assertFalse(mock.add("Hi"));
}
}
5.6、字符串参数匹配器
- contains(String):包含子串
- find(String):子字符串之一与正则表达式匹配
- matches(String):数字符串与正则表达式匹配
- startsWith(String):以…开头
- endsWith(String):以…结尾
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
public class EasyMockAgrumentMatcherStringExample {
@Test
public void test() {
List<String> mock = mock(ArrayList.class);
//以Java开头
expect(mock.add(startsWith("Java"))).andReturn(true);
//以Dev结尾
expect(mock.add(endsWith("Dev"))).andReturn(true);
//包含Pyt
expect(mock.add(contains("Pyt"))).andReturn(true);
//正则匹配
expect(mock.add(matches("^[abc]d."))).andReturn(true);
expect(mock.add(find("[9]{3}"))).andReturn(true);
replay(mock);
assertTrue(mock.add("Java World"));
assertTrue(mock.add("JournalDev"));
assertTrue(mock.add("Python"));
assertTrue(mock.add("ads"));
assertTrue(mock.add("ABC999DDD"));
verify(mock);
}
}