[개발 자동화] Unitils 통합 Feed4junit

19946 단어 unitilsFeed4Junit
간단 한 소개
    Feed4JUnit 은 JUnit 의 매개 변수 화 테스트 를 간편 하 게 만 들 고 이 테스트 에 미리 정의 하거나 무 작위 테스트 데 이 터 를 제공 할 수 있 습 니 다.이 는 업무 분석 원 이 정의 한 CVS 나 Excel 파일 에서 테스트 사례 데 이 터 를 읽 고 구축 / 유닛 테스트 프레임 워 크 에서 테스트 성공 을 보고 할 수 있 습 니 다.Feed4JUnit 을 이용 하여 무 작위 이지 만 검 증 된 데이터 로 연기 테스트 를 실시 하여 코드 코드 보급률 을 높이 고 매우 특수 한 데이터 구조 로 인해 발생 하 는 Bug 를 발견 할 수 있 습 니 다.
   그러나 Feed4junit 는 spring - test 나 unitils 처럼 spring 통합 에 대한 지원 이 없 으 며, 트 랜 잭 션 제어 실현 방법 을 통 해 실 행 된 후 환경 을 되 돌 릴 수 없습니다.어떻게 Feef4junit 와 unitils 의 장점 을 결합 시 켜 둘 을 유기 적 으로 결합 시 키 는 지 는 본 고가 해결 해 야 할 문제 이다.
계획
    UnitilsJUnit 4 는 Class 에서 @ RunWith (UnitilsJUnit 4 TestClassRunner. class) 를 정의 해 야 하고, Feed4junit 는 Class 에서 @ RunWith (Feeder. class) 를 정의 해 야 합 니 다. 기본 적 인 상황 은 둘 이 병존 할 수 없 으 므 로 변경 이 필요 합 니 다.고 치 는 방식 은 두 가지 가 있 습 니 다. 하 나 는 최신 @ Rule 로 고 치 는 것 입 니 다. 하 나 는 두 가지 코드 를 한 종류 에 합 치 는 것 입 니 다.힘 을 절약 하 는 각도 에서 나 는 뒤의 방안 을 채택 하기 로 결정 했다.
개작 UnitilsJUnit 4 TestClassRunner
    UnitilsJUnit 4 TestClassRunner 는 JUnit 4 ClassRunner 에 계승 되 었 으 며, Feeder 는 새로운 BlockJUnit 4 ClassRunner 에 계승 되 었 으 며, 매개 변 수 를 가 진 테스트 방법 을 지원 하려 면 먼저 UnitilsJUnit 4 TestClassRunner 를 BlockJUnit 4 ClassRunner 로 개조 하 는 것 이 좋 습 니 다.
package org.unitils;

import java.util.List;

import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.rules.MethodRule;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.unitils.core.TestListener;
import org.unitils.core.Unitils;

/** 
 * 
 * Unitils  BlockJUnit4ClassRunner   .
 * 
 * <pre><b>  :</b>
 *       BlockJUnit4ClassRunner
 * </pre>
 * 
 * <pre><b>    :</b>
 *    
 * </pre>
 * 
 * @author <a href="mailto:[email protected]">  </a>
 * @since 2013-8-12
 * @version 1.0
 *
 */
public class UnitilsJUnitBlockRunner extends BlockJUnit4ClassRunner
{
	TestListener listener;

    public UnitilsJUnitBlockRunner(Class<?> aClass) throws InitializationError {
        super(aClass);
        listener = Unitils.getInstance().getTestListener();
    }

    @Override
    protected Statement classBlock(RunNotifier runNotifier) {
        listener.beforeTestClass(getTestClass().getClass());
        return super.classBlock(runNotifier);
    }

    @Override
    protected Object createTest() throws Exception {
        Object o = super.createTest();
        listener.afterCreateTestObject(o);
        return o;
    }

    @Override
    protected Statement methodInvoker(final FrameworkMethod frameworkMethod, final Object o) {
        return new InvokeMethod(frameworkMethod, o) {
            @Override
            public void evaluate() throws Throwable {
                listener.beforeTestMethod(o, frameworkMethod.getMethod());
                Throwable threw = null;
                try {
                    super.evaluate();
                } catch (Throwable t) {
                    threw = t;
                } finally {
                    listener.afterTestMethod(o, frameworkMethod.getMethod(), threw);
                }
                if(threw!=null)
                   throw threw;
            }
        };
    }

    @Override
    protected List<MethodRule> rules(Object o) {
        List<MethodRule> list = super.rules(o);
        list.add(new MethodRule() {
            public Statement apply(final Statement nextStatement, final FrameworkMethod frameworkMethod, final Object o) {
                return new Statement() {
                    public void evaluate() throws Throwable {
                        listener.beforeTestSetUp(o, frameworkMethod.getMethod());
                        nextStatement.evaluate();
                        listener.afterTestTearDown(o, frameworkMethod.getMethod());
                    }
                };
            }
        });
        return list;
    }
}

피 더 개조
Feeder 의 원본 코드 를 복사 하여 UnitilsJUnitBlockRunner 를 직접 계승 합 니 다.
package org.databene.feed4junit;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.databene.benerator.Generator;
import org.databene.benerator.anno.AnnotationMapper;
import org.databene.benerator.anno.DefaultPathResolver;
import org.databene.benerator.anno.PathResolver;
import org.databene.benerator.anno.ThreadPoolSize;
import org.databene.benerator.engine.BeneratorContext;
import org.databene.benerator.engine.DefaultBeneratorContext;
import org.databene.benerator.factory.EquivalenceGeneratorFactory;
import org.databene.benerator.wrapper.ProductWrapper;
import org.databene.commons.ConfigurationError;
import org.databene.commons.IOUtil;
import org.databene.commons.Period;
import org.databene.commons.StringUtil;
import org.databene.commons.converter.AnyConverter;
import org.databene.feed4junit.ChildRunner;
import org.databene.feed4junit.FrameworkMethodWithParameters;
import org.databene.feed4junit.Scheduler;
import org.databene.feed4junit.scheduler.DefaultFeedScheduler;
import org.databene.model.data.DataModel;
import org.databene.platform.java.BeanDescriptorProvider;
import org.databene.platform.java.Entity2JavaConverter;
import org.databene.script.DatabeneScriptParser;
import org.databene.script.Expression;
import org.junit.Test;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerScheduler;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import org.unitils.UnitilsJUnitBlockRunner;

/**
 * 
 * 
 * Feeder  Unitils.
 * 
 * <pre><b>  :</b>
 *      Unitils       Feed4JUnit     
 * </pre>
 * 
 * <pre><b>    :</b>
 *    
 * </pre>
 * 
 * @author <a href="mailto:[email protected]">  </a>
 * @since 2013-8-12
 * @version 1.0
 *
 */
public class Feeder extends UnitilsJUnitBlockRunner
{
	public static final String CONFIG_FILENAME_PROPERTY = "feed4junit.properties";
	private static final String DEFAULT_CONFIG_FILENAME = "feed4junit.properties";
	private static final String FEED4JUNIT_BASE_PATH = "feed4junit.basepath";

	private static final long DEFAULT_TIMEOUT = Period.WEEK.getMillis();

	static {
		ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
	}
	
	private BeneratorContext context;
	private PathResolver pathResolver;
	private AnnotationMapper annotationMapper;
	private List<FrameworkMethod> children;
	private RunnerScheduler scheduler;

	public Feeder(Class<?> aClass) throws InitializationError
	{
		super(aClass);
		this.children = null;
	}
	
	@Override
	protected String testName(FrameworkMethod method) {
		return (method instanceof FrameworkMethodWithParameters ? method.toString() : super.testName(method));
	}
	
	@Override
	public void setScheduler(RunnerScheduler scheduler) {
		this.scheduler = scheduler;
		super.setScheduler(scheduler);
	}
	
	/**
	 * Instantiates a test class and initializes attributes 
	 * which have been marked with a @Source annotation.
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	protected Object createTest() throws Exception {
		Object testObject = super.createTest();
		for (FrameworkField attribute : getTestClass().getAnnotatedFields(org.databene.benerator.anno.Source.class)) {
			if ((attribute.getField().getModifiers() & Modifier.PUBLIC) == 0)
				throw new ConfigurationError("Attribute '" + attribute.getField().getName() + "' must be public");
			Generator<?> generator = getAnnotationMapper().createAndInitAttributeGenerator(attribute.getField(), getContext());
			if (generator != null) {
				ProductWrapper wrapper = new ProductWrapper();
				wrapper = generator.generate(wrapper);
				if (wrapper != null)
					attribute.getField().set(testObject, wrapper.unwrap());
			}
		}
		return testObject;
	}
	
	@Override
	protected List<FrameworkMethod> computeTestMethods() {
		if (children == null) {
			children = new ArrayList<FrameworkMethod>();
			TestClass testClass = getTestClass();
			BeneratorContext context = getContext();
			context.setGeneratorFactory(new EquivalenceGeneratorFactory());
			getAnnotationMapper().parseClassAnnotations(testClass.getAnnotations(), context);
			for (FrameworkMethod method : testClass.getAnnotatedMethods(Test.class)) {
				if (method.getMethod().getParameterTypes().length == 0) {
					// standard JUnit test method
					children.add(method);
					continue;
				} else {
					// parameterized Feed4JUnit test method
					List<? extends FrameworkMethod> parameterizedTestMethods;
					parameterizedTestMethods = computeParameterizedTestMethods(method.getMethod(), context);
					children.addAll(parameterizedTestMethods);
				}
			}
		}
		return children;
	}

	@Override
    protected void validateTestMethods(List<Throwable> errors) {
		validatePublicVoidMethods(Test.class, false, errors);
	}

	// test execution --------------------------------------------------------------------------------------------------
	
	protected Statement childrenInvoker(final RunNotifier notifier) {
		return new Statement() {
			@Override
			public void evaluate() {
				runChildren(notifier);
			}
		};
	}

	private void runChildren(final RunNotifier notifier) {
		RunnerScheduler scheduler = getScheduler();
		for (FrameworkMethod method : getChildren())
 			scheduler.schedule(new ChildRunner(this, method, notifier));
		scheduler.finished();
	}

	public RunnerScheduler getScheduler() {
		if (scheduler == null)
			scheduler = createDefaultScheduler();
		return scheduler;
	}
	
	protected RunnerScheduler createDefaultScheduler() {
		TestClass testClass = getTestClass();
		Scheduler annotation = testClass.getJavaClass().getAnnotation(Scheduler.class);
		if (annotation != null) {
			String spec = annotation.value();
			Expression<?> bean = DatabeneScriptParser.parseBeanSpec(spec);
			return (RunnerScheduler) bean.evaluate(null);
		} else {
			return new DefaultFeedScheduler(1, DEFAULT_TIMEOUT);
		}
	}

	@Override
	public void runChild(FrameworkMethod method, RunNotifier notifier) {
		super.runChild(method, notifier);
	}
	
	// helpers ---------------------------------------------------------------------------------------------------------

	private PathResolver configuredPathResolver() {
		if (pathResolver != null)
			return pathResolver;
		String configuredConfigFileName = System.getProperty(CONFIG_FILENAME_PROPERTY);
		String configFileName = configuredConfigFileName;
		if (StringUtil.isEmpty(configFileName))
			configFileName = DEFAULT_CONFIG_FILENAME;
		if (IOUtil.isURIAvailable(configFileName)) {
			// load individual or configured config file
			return configuredPathResolver(configFileName);
		} else if (StringUtil.isEmpty(configuredConfigFileName)) {
			// if no explicit config file was configured, then use defaults...
			return createDefaultResolver();
		} else {
			// ...otherwise raise an exception
			throw new ConfigurationError("Feed4JUnit configuration file not found: " + configuredConfigFileName);
		}
	}

	private PathResolver createDefaultResolver() {
		return applyBasePath(new DefaultPathResolver());
	}

	private PathResolver configuredPathResolver(String configFileName) {
		try {
			Map<String, String> properties = IOUtil.readProperties(configFileName);
			String pathResolverSpec = properties.get("pathResolver");
			if (pathResolverSpec != null) {
				PathResolver resolver;
				resolver =  (PathResolver) DatabeneScriptParser.parseBeanSpec(pathResolverSpec).evaluate(getContext());
				return applyBasePath(resolver);
			} else
				return createDefaultResolver();
		} catch (IOException e) {
			throw new ConfigurationError("Error reading config file '" + configFileName + "'", e);
		}
	}

	private PathResolver applyBasePath(PathResolver resolver) {
		String confdBasePath = System.getProperty(FEED4JUNIT_BASE_PATH);
		if (confdBasePath != null)
			resolver.setBasePath(confdBasePath);
		return resolver;
	}

	private void validatePublicVoidMethods(Class<? extends Annotation> annotation, boolean isStatic, List<Throwable> errors) {
		List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation);
		for (FrameworkMethod eachTestMethod : methods)
			eachTestMethod.validatePublicVoid(isStatic, errors);
	}

	private List<FrameworkMethodWithParameters> computeParameterizedTestMethods(Method method, BeneratorContext context) {
		Integer threads = getThreadCount(method);
		long timeout = getTimeout(method);
		List<FrameworkMethodWithParameters> result = new ArrayList<FrameworkMethodWithParameters>();
		Class<?>[] parameterTypes = method.getParameterTypes();
		Generator<Object[]> paramGenerator = getAnnotationMapper().createAndInitMethodParamsGenerator(method, context);
		Class<?>[] expectedTypes = parameterTypes;
		ProductWrapper<Object[]> wrapper = new ProductWrapper<Object[]>();
		int count = 0;
		while ((wrapper = paramGenerator.generate(wrapper)) != null) {
			Object[] generatedParams = wrapper.unwrap();
			if (generatedParams.length > expectedTypes.length) // imported data may have more columns than the method parameters, ...
				generatedParams = Arrays.copyOfRange(generatedParams, 0, expectedTypes.length); // ...so cut them
			for (int i = 0; i < generatedParams.length; i++) {
				generatedParams[i] = Entity2JavaConverter.convertAny(generatedParams[i]);
				generatedParams[i] = AnyConverter.convert(generatedParams[i], parameterTypes[i]);
			}
			// generated params may be to few, e.g. if an XLS row was imported with trailing nulls, 
			// so create an array of appropriate size
			Object[] usedParams = new Object[parameterTypes.length];
			System.arraycopy(generatedParams, 0, usedParams, 0, Math.min(generatedParams.length, usedParams.length));
			result.add(new FrameworkMethodWithParameters(method, usedParams, threads, timeout));
			count++;
		}
		if (count == 0)
			throw new RuntimeException("No parameter values available for method: " + method);
		return result;
	}

	private Integer getThreadCount(Method method) {
		ThreadPoolSize methodAnnotation = method.getAnnotation(ThreadPoolSize.class);
		if (methodAnnotation != null)
			return methodAnnotation.value();
		Class<?> testClass = method.getDeclaringClass();
		ThreadPoolSize classAnnotation = testClass.getAnnotation(ThreadPoolSize.class);
		if (classAnnotation != null)
			return classAnnotation.value();
		return null;
	}

	private long getTimeout(Method method) {
		return DEFAULT_TIMEOUT;
	}

	private AnnotationMapper getAnnotationMapper() { 
		// lazy initialization is necessary since the constructor is not executed by JUnit
		if (annotationMapper == null) {
			PathResolver pathResolver = configuredPathResolver();
			annotationMapper = new AnnotationMapper(new EquivalenceGeneratorFactory(), getDataModel(), pathResolver);
		}
		return annotationMapper;
	}

	private BeneratorContext getContext() { 
		// lazy initialization is necessary since the constructor is not executed by JUnit
		if (context == null) {
			context = new DefaultBeneratorContext();
			DataModel dataModel = context.getDataModel();
			new BeanDescriptorProvider(dataModel);
		}
		return context;
	}

	private DataModel getDataModel() {
		return getContext().getDataModel();
	}


}

Example
    현재 우 리 는 테스트 클래스 에서 Feeder 의 주해 와 Unitils 의 주 해 를 동시에 사용 합 니 다. 아래 열 은 feed4junit 를 통 해 엑셀 파일 에서 테스트 사례 를 읽 고 모든 사례 에 대해 save 방법 을 테스트 합 니 다. 테스트 클래스 는 Spring 용기 관 리 를 통 해 테스트 가 끝 난 후에 사 무 는 자동 으로 스크롤 백 되 어 테스트 환경 을 복원 합 니 다.
    연기 가 나 는 통합 테스트 방법 이 생 겨 서 엄 마 는 더 이상 나의 코드 품질 을 걱정 할 필요 가 없다.
package com.litt.cidp.system.service;

import java.util.Map;

import org.databene.benerator.anno.Source;
import org.databene.feed4junit.Feeder;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.unitils.spring.annotation.SpringBeanByType;

import com.litt.cidp.system.po.Role;
import com.litt.core.test.BaseServiceTester;

/** 
 * 
 *     .
 * 
 * <pre><b>  :</b>
 *     
 * </pre>
 * 
 * <pre><b>    :</b>
 *    
 * </pre>
 * 
 * @author <a href="mailto:[email protected]">  </a>
 * @since 2013-8-9
 * @version 1.0
 *
 */
@SpringApplicationContext("spring/applicationContext-*.xml")
@RunWith(Feeder.class)
public class RoleServiceSmokeTest
{
	@SpringBeanByType
	private IRoleService roleService;
	
	@SpringBeanByType
	private JdbcTemplate jdbcTemplate;	
	
	@Test
	@Source("com/litt/cidp/system/service/Role-smoke-data.xls")
	public void save(String roleName, String remark) {
		
		Role role = new Role();
		role.setRoleName(roleName);
		role.setRemark(remark);
		
		roleService.save(role);
		
		this.validate(role);
	}
	
	private void validate(Role role)
	{
		Map<String, Object> rsMap = jdbcTemplate.queryForMap("SELECT * FROM ROLE WHERE ROLE_NAME=?", new Object[]{role.getRoleName()});
		
		Assert.assertEquals(role.getRemark(), rsMap.get("REMARK"));
	}

}

좋은 웹페이지 즐겨찾기