Напомню, что тест написанный с использованием EasyMock приблизительно выглядит так
package test.com.somepackage;
import static org.junit.Assert.*;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import com.easymockrunner.Injector;
import com.somepackage.Dependency;
import com.somepackage.SomeObject;
public class SomeObjectTest {
public Dependency dependency;
public SomeObject someObject;
@Before
public void setUp() throws Exception {
someObject = new SomeObject();
dependency = EasyMock.createMock(Dependency.class);
Injector.injectAll(someObject, dependency);
}
@Test
public void testThatItMocksDependency() {
EasyMock.expect(dependency.callFromSomeObject()).andReturn("Hello");
EasyMock.replay(dependency);
assertEquals("Hello", someObject.callToDependency());
EasyMock.verify(dependency);
}
}Много писанины и дублирования, а потому хотелось бы как-то так
package test.com.somepackage;
import static org.junit.Assert.assertEquals;
import org.easymock.EasyMock;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.easymockrunner.CurrentMocks;
import com.easymockrunner.EasyMockRunner;
import com.easymockrunner.Mock;
import com.easymockrunner.TestObject;
import com.somepackage.Dependency;
import com.somepackage.SomeObject;
@RunWith(EasyMockRunner.class)
public class SomeObjectMockTest {
@Mock
private Dependency dependency;
@TestObject
private SomeObject someObject;
@Test
public void testThatItMocksDependency() {
EasyMock.expect(dependency.callFromSomeObject()).andReturn("Hello");
CurrentMocks.replay();
assertEquals("Hello", someObject.callToDependency());
}
}
Согласитесь, красивее. Ну тогда вперед!
Для начала две аннотации, которыми мы будем помечать моки и тестируемый класс
package com.easymockrunner;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestObject {
}
package com.easymockrunner;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mock {
}Наш старый добрый Injector, о котором я впервые упоминал в посте "Spring IoC для бедных". Класс слегка модифицирован.
package com.easymockrunner;
import java.lang.reflect.Field;
public class Injector {
public static <T> T injectAll(T object, Object injectible) throws Exception {
final Field[] declaredFields = object.getClass().getDeclaredFields();
final Class<?> injectibleClass = injectible.getClass();
boolean injected = false;
for (Field field : declaredFields) {
if (Object.class.equals(field.getType())) {
continue;
}
if (!field.getType().isAssignableFrom(injectibleClass)) {
continue;
}
injectToPrivate(object, field, injectible);
injected = true;
}
if (!injected) {
throw new IllegalArgumentException("Field not found.");
}
return object;
}
public static <T> T injectToPrivate(T object, Field field, Object injectible) throws IllegalAccessException {
field.setAccessible(true);
field.set(object, injectible);
field.setAccessible(false);
return object;
}
}Хранилище рабочих моков
package com.easymockrunner;
import java.util.Collection;
import java.util.LinkedList;
import org.easymock.EasyMock;
public class CurrentMocks {
static Collection<Object> mocks = new LinkedList<Object>();
public static void replay() {
EasyMock.replay(mocks.toArray());
}
public static void verify() {
EasyMock.verify(mocks.toArray());
}
public static void add(Object mock) {
mocks.add(mock);
}
public static void clear() {
mocks.clear();
}
}Ну и самое главное - сам раннер
package com.easymockrunner;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import org.easymock.EasyMock;
import org.junit.internal.runners.statements.RunAfters;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
public class EasyMockRunner extends BlockJUnit4ClassRunner {
private Collection<Field> mocks = new LinkedList<Field>();
private Collection<Field> testObjects = new LinkedList<Field>();
public EasyMockRunner(Class<?> clazz) throws InitializationError {
super(clazz);
for (Field field : clazz.getDeclaredFields()) {
if (isMock(field)) {
mocks.add(field);
} else if (isTestObject(field)) {
testObjects.add(field);
}
}
}
private boolean isTestObject(Field field) {
return field.isAnnotationPresent(TestObject.class);
}
private boolean isMock(Field field) {
return field.isAnnotationPresent(Mock.class);
}
public void verifyAfterTest() {
CurrentMocks.verify();
}
@Override
protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
Statement result = super.withAfters(method, target, statement);
FrameworkMethod methodToRun = new FrameworkMethod(getMyMethod("verifyAfterTest"));
return new RunAfters(result, Arrays.asList(methodToRun), this);
}
private Method getMyMethod(String methodName) {
try {
return this.getClass().getDeclaredMethod(methodName);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Override
protected Object createTest() throws Exception {
CurrentMocks.clear();
Object test = super.createTest();
for (Field mockField : mocks) {
Object mockObject = EasyMock.createMock(mockField.getType());
CurrentMocks.add(mockObject);
Injector.injectToPrivate(test, mockField, mockObject);
}
for (Field testObjectField : testObjects) {
Object testObject = testObjectField.getType().newInstance();
for (Object mock : CurrentMocks.mocks) {
Injector.injectAll(testObject, mock);
}
Injector.injectToPrivate(test, testObjectField, testObject);
}
return test;
}
}
Мне не нравится статика в CurrentMocks и я ее уже успешно удалил (как именно - покажу позже). Так же интересной задачкой будет избавиться от вызова CurrentMocks.replay() в каждом тесте.
Продолжение следует...

Комментариев нет:
Отправить комментарий