Integration with TestNG

PowermockをTestNGと一緒に使う為に必要な設定をメモとして残しておく。
基本的に、testプロジェクトはMavenプロジェクトとして作成することを前提している。

Mavenプロジェクトのproperties設定を以下に示す。

keyvaluecomment
project.build.sourceEncodingUTF-8
java.version1.8
powermock.version2.0.9
log4j.version2.17.1

Mavenプロジェクトのplugin設定を以下に示す。

  	<plugin>
  		<groupId>ora.apache.maven.plugins</groupId>
  		<artifactId>maven-compiler-plugin</artifactId>
  		<version>3.8.1</version>
  		<configuration>
  	  		<source>${java.version}</source>
  			<target>${java.version}</target>
  		</configuration>
  	</plugin>

TestNGと一緒に使う場合は、下記モジュールの導入が必要だ。
必要モジュールを下記に示す。

groupIdartifactIdversionscope
org.powermockpowermock-module-testng${powermock.version}test
org.powermockpowermock-api-mockito2${powermock.version}test

その他、必要モジュールを導入するが、基本的に入れるモジュールを以下に示す。

groupIdartifactIdversionscope
org.dbunitdbunit2.7.2
org.apache.commonscommons-lang33.12
org.apache.poipoi5.1.0
org.apache.poipoi-ooxml5.1.0
commons-iocommons-io2.11.0
commons-beanutilscommons-beanutils1.9.3
com.google.injectguice5.0.1
org.apache.logging.log4jlog4j-1.2-api${log4j.version}test
org.apache.logging.log4jlog4j-api${log4j.version}test
org.apache.logging.log4jlog4j-core${log4j.version}test

Common Parent Test Class

Testクラスが共通で継承する親クラスについて、サンプルとして残しておく。

@Listeners({CustomTestMethodListener.class})
@PowerMockIgnore({"javax.management.*", "javax.security.auth.x500.*", "javax.net.ssl.*",
	"oracle.jdbc.*", "javax.sql.*", "com.microsoft.sqlserver.*"})
public abstract class AbstractCustomTestCase extends PowerMockTestCase {
    /** DBUnitのテスター */
    protected IDatabaseTester databaseTester;
 
    //overwrite createWorkbook for Xlsx data set
    //DBUnitでXlsxをITableとして使えるよう、override
    protected final XlsDataSetWriter xlsxWriter = new XlsDataSetWriter() {
        /* (non-Javadoc)
	 * @see org.dbunit.dataset.excel.XlsDataSetWriter#createWorkbook()
	 */
	@Override
	protected Workbook createWorkbook() {
	    return new XSSFWorkbook();
	}
    };
 
    //***** public method *****
    //***** protected method *****
 
    @BeforeClass
    protected void setUp() throws Exception {
        setUpLog4j();
	setUpDbunitTester();
	prepareSpecific();
    }
 
    @AfterClass
    protected void tearDown() throws Exception {
	databaseTester.setTearDownOperation(DatabaseOperation.NONE);
	databaseTester.onTearDown();
	tearDownHook();
    }
 
    protected abstract void prepareSpecific() throws Exception;
 
    protected void tearDownHook() throws Exception {
	//NOP
    }
 
    //***** private method *****
 
    // Creates a new JdbcDatabaseTester
    private void setUpDbunitTester() throws Exception {
	DataSource _ds = DataSourceLocator.getDataSource(DBConstants.EPADS);
	databaseTester = new DataSourceDatabaseTester(_ds);
 
	// ----------------------------------
	// Database config
	// ----------------------------------
	DatabaseConfig _config = databaseTester.getConnection().getConfig();
	_config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new OracleDataTypeFactory());
    }
 
    //Enable Log4j 1.x bridge
    private void setUpLog4j() {
        System.setProperty("log4j1.compatibility", "true");
    }
 
}

@Listeners

Test実行中のあるフェーズで共通の処理を追加したい場合の為に、TestNGではListener1)というInterfaceを提供している。
通常テストを行う際、メソッド1個のみ実行することはない。
今なんのメソッドが実行されたかをコンソールで確認し易いために、IInvokeMethodListenerを実装しておくと便利だ。
サンプルのParent Classでは、CustomTestMethodListenerを指定しているが、コードを以下に示す。

public class CustomTestMethodListener implements IInvokedMethodListener {
 
    //***** injection field *****
    //***** constructor *****
    //***** public method *****
 
    /**
     * @see org.testng.IInvokedMethodListener#beforeInvocation(org.testng.IInvokedMethod, org.testng.ITestResult)
     */
    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
	if (method.isTestMethod()) {
            System.out.println("on test method " +  method.getTestMethod().getMethodName() + " start");
	    //parameters
	    System.out.println("parameters:" + ToStringBuilder.reflectionToString(testResult.getParameters(), ToStringStyle.SIMPLE_STYLE));
        }
    }
 
    /**
     * @see org.testng.IInvokedMethodListener#afterInvocation(org.testng.IInvokedMethod, org.testng.ITestResult)
     */
    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
        //NOP
    }
 
}

@MockPolicy

Powermockをlog4j又はslf4jと一緒に使う場合、次のエラーが発生することがある。

log4j:ERROR A "org.apache.log4j.xml.DOMConfigurator" object is not assignable to a "org.apache.log4j.spi.Configurator" variable.
log4j:ERROR The class "org.apache.log4j.spi.Configurator" was loaded by
log4j:ERROR [org.powermock.core.classloader.MockClassLoader@14a55f2] whereas object of type
log4j:ERROR "org.apache.log4j.xml.DOMConfigurator" was loaded by [sun.misc.Launcher$AppClassLoader@92e78c].
log4j:ERROR Could not instantiate configurator [org.apache.log4j.xml.DOMConfigurator].

解決策はいくつか2)あるが、簡単なのは、MockPolicy annotationを利用してlog実装体を指定する方法だ。
Log4jのみ利用する場合は、@MockPolicy(Log4jMockPolicy.class)
Slf4jとLog4jを併用する場合は、@MockPolicy(Slf4jMockPolicy.class)を指定する。

@PowerMockIgnore

TestNGとPowerMockを同時に使うと、TestNGのClassLoaderによりloadingされたクラスが、PowerMockerのClassLoaderにloadingされエラーになることがある。
それを回避するために、用意されているのが@PowerMockIgnore annotationになる。

サンプルのParentクラスで指定した設定の意味を次に示す。

package or class目的
javax.management.*DBUnitと統合
javax.net.ssl.*DBUnitと統合
javax.security.auth.x500.*DBUnitと統合
oracle.jdbc.*Oracle接続
javax.sql.*SQL Server接続
com.microsoft.sqlserver.*SQL Server接続

log4j設定

log4j-2.xのlogging設定ファイルとしてlog4j2-test.xmlをsrc/test/resourcesフォルダーに配置しておく。
設定例を以下に示す。

<Configuration>
    <Properties>
        <Property name="LOG_DIR">C:/workspaces/logs/appl</Property>
    </Properties>
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
        </Console>
        <RollingFile name="AccessLog"
    		fileName="${LOG_DIR}/web.log"
    		filePattern="${LOG_DIR}/web.log.%d{yyyy-MM-dd}"
            >
            <PatternLayout pattern="%d %-5p %-42c - %m%n" charset="UTF-8" />
            <Policies>
                <TimeBasedTriggeringPolicy />
            </Policies>
        </RollingFile>
        <RollingFile name="ErrorLog"
    		fileName="${LOG_DIR}/error.log"
    		filePattern="${LOG_DIR}/error.log.%d{yyyy-MM-dd}"
            >
            <PatternLayout pattern="%d %-5p - %m%n" charset="UTF-8" />
            <Policies>
                <TimeBasedTriggeringPolicy />
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!-- rootLoggerへ伝播を防ぐため、additivity=false指定  -->
        <Logger name="com.consoto.bizlogic" level="debug" additivity="false">
    	    <AppenderRef ref="CONSOLE" />
    	    <AppenderRef ref="ErrorLog" level="error" />
        </Logger>
        <!-- 基本loggingレベル -->
        <Root level="debug">
            <AppenderRef ref="CONSOLE" />
        </Root>
    </Loggers>
</Configuration>

logback.xml設定

logback-classicをloggingとして使う場合の設定例を以下に示します。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE logback>
<configuration>
	<property name="LOG_DIR" value="C:/workspaces/logs/appl" />
	<!-- コンソール出力 -->
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
	    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
		<pattern>
		    %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] -   %class{40}.%method:%msg %n
	        </pattern>
	    </encoder>
	</appender>

	<!--
	    ファイルへ出力 
	-->
	<appender name="FILE_E" class="ch.qos.logback.core.rolling.RollingFileAppender">
	    <file>${LOG_DIR}/ecabinet.log</file>
	    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		<fileNamePattern>${LOG_DIR}/ecabinet_%d{yyyy-MM-dd}.log</fileNamePattern>
		<maxHistory>30</maxHistory>
	    </rollingPolicy>
	    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
		    <pattern>
		    %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] -   %class{40}.%method:%msg %n
	        </pattern>
	    </encoder>
	</appender>

        <logger name="com.nsk.sample.demo" level="DEBUG" additivity="false">
    	    <appender-ref ref="STDOUT" />
        </logger>

	<!-- spring web client logging -->
	<logger name="org.springframework.web.client" level="DEBUG" additivity="false">
	    <appender-ref ref="STDOUT" />
	</logger>

	<!-- spring http client logging -->
	<logger name="org.springframework.http.client" level="DEBUG" additivity="false">
	    <appender-ref ref="STDOUT" />
	</logger>

        <!-- suppress full wire logging -->
	<logger name="org.apache.http.wire" level="DEBUG" additivity="false">
	    <appender-ref ref="FILE_E" />
	</logger>

	<!-- header and context logging -->
	<logger name="org.apache.http" level="DEBUG" additivity="false">
	    <appender-ref ref="STDOUT" />
	</logger>

	<!-- turn OFF all logging (children can override) -->
	<root level="INFO">
    	    <appender-ref ref="STDOUT" />
	</root>

</configuration>

QR Code
QR Code study:java:powermock:integation (generated for current page)