Напомню, что тест написанный с использованием 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() в каждом тесте.
Продолжение следует...
Комментариев нет:
Отправить комментарий