添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • 1.1. Mockito / PowerMockito
  • 1.2. OpenPojo : Auto test Pojo classes for coverage
  • 1.3. SLF4J : Abstract logging
  • 1.4. Log4j2
  • 1.5. Aspect4log : Logging functions starts/stops with inputs/outputs
  • 1.6. Log methods duration
  • 1.7. JUnit
  • 2. Best practices
  • 2.1. Java packages & classes naming
  • 2.2. Java 7 try with closable objects
  • 2.3. Static Java Maps
  • 2.4. Init on demand
  • 2.5. Enums and Strings
  • 2.6. Single Implementation Interfaces Are Evil
  • @RunWith(PowerMockRunner.class)
    @PrepareForTest({ TypeUtils.class })
    @PowerMockIgnore("javax.management.*")
    public class OpenPojoWebTest {
    	@Before
    	public void before() throws Exception {
    		PowerMockito.mockStatic(TypeUtils.class);
    		PowerMockito.when(TypeUtils.setterDate((Date) Mockito.any(), (Date) Mockito.any()))
    				.thenAnswer(invocation -> invocation.getArgumentAt(1, Date.class));
    

    OpenPojo au tests Pojo classes, especially getters and setters. Very handy for large beans / auto generated classes for whom testing is boring.

    Usage
    import com.openpojo.reflection.filters.FilterNonConcrete;
    import com.openpojo.validation.Validator;
    import com.openpojo.validation.ValidatorBuilder;
    import com.openpojo.validation.test.impl.GetterTester;
    import com.openpojo.validation.test.impl.SetterTester;
    public class OpenPojoTest {
    	public static void validateBeans(String javaPackage) {
    		Validator validator = ValidatorBuilder.create().with(new SetterTester()).with(new GetterTester()).build();
    		//exclude enums, abstracts, interfaces
    		validator.validateRecursively(javaPackage, new FilterNonConcrete());
    	@Test (1)
    	public void testPojoRecursiv() {
    		// recursive
    		validateBeans("my.full.java.package.with.sub.packages");
    	@Test (2)
    	public void testExludingSomeClasses() {
    		List<PojoClass> listOfPojoClassInDto = PojoClassFactory.getPojoClasses("my.full.java.package.with.sub.packages", null);
    		listOfPojoClassInDto.remove(PojoClassFactory.getPojoClass(SomeSpecialClassNotToTest.class));
    		validator.validate(listOfPojoClassInDto);
    

    1.3. SLF4J : Abstract logging

    SLF4J helps abstract logging from implementation. Even for libraries using log4j explicitely with the concept of bridge.

    Maven dependencies
    	<!-- for SLF4J implementing log4j2 -->
    	<dependency>
    		<groupId>org.slf4j</groupId>
    		<artifactId>slf4j-api</artifactId>
    		<version>1.7.25</version>
    	</dependency>
    	<dependency>
    		<groupId>org.slf4j</groupId>
    		<artifactId>jcl-over-slf4j</artifactId>
    		<version>1.7.25</version>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.logging.log4j</groupId>
    		<artifactId>log4j-api</artifactId>
    		<version>2.11.1</version>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.logging.log4j</groupId>
    		<artifactId>log4j-core</artifactId>
    		<version>2.11.1</version>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.logging.log4j</groupId>
    		<artifactId>log4j-slf4j-impl</artifactId>
    		<version>2.11.1</version>
    	</dependency>
    	<!-- to bridge any LOG4J1 usage to SLF4J -->
    	<dependency>
    		<groupId>org.slf4j</groupId>
    		<artifactId>log4j-over-slf4j</artifactId>
    		<version>1.7.25</version>
    	</dependency>

    Since slf4j does not accept multiple boot jars, you may have to exclude log4j from dependencies :

    Exclude log4j JAR
    	<dependency>
    		<groupId>my.bad.group</groupId>
    		<artifactId>my-bad-artifact</artifactId>
    		<version>1.0.0</version>
    		<exclusions>
    			<exclusion>
    				<groupId>log4j</groupId>
    				<artifactId>log4j</artifactId>
    			</exclusion>
    		</exclusions>
    	</dependency>
    17:53:33.372 DEBUG main  .wm.utils.wmcall.WmHelper:176  + setTestMode()
    17:53:33.372 DEBUG main  cg.wm.utils.wmcall.WmCall:176  +   WmCall()
    17:53:33.372 DEBUG main  cg.wm.utils.wmcall.WmCall:176  .   WmCall() -> (null)
    17:53:33.373 DEBUG main  tils.wmcall.WmCallEclipse:176  +   WmCallEclipse()
    17:53:33.373 DEBUG main  tils.wmcall.WmCallEclipse:176  .   WmCallEclipse() -> (null)
    17:53:33.373 DEBUG main  .wm.utils.wmcall.WmHelper:176  . setTestMode()
    17:53:33.374 DEBUG main  cg.wm.utils.ConfigUtils  :176  + healthCheck()
    log4j2.xml example
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="INFO">
    	<Appenders>
    		<Console name="Console" target="SYSTEM_OUT">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} [%t] %-5level %-30logger{2.} %msg%n" />
    		</Console>
    		<RollingFile name="cg-wm" fileName="target/log4j2/cg-wm.log"
    			filePattern="target/log4j2/old/cg-wm.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    				<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    		<RollingFile name="cg-utils" fileName="target/log4j2/cg-utils.log"
    			filePattern="target/log4j2/old/cg-utils.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    				<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    		<RollingFile name="ALL.error" fileName="target/log4j2/ALL.error.log"
    			filePattern="target/log4j2/old/ALL.error.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    		<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    		<RollingFile name="ALL.warn" fileName="target/log4j2/ALL.warn.log"
    			filePattern="target/log4j2/old/ALL.warn.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    		<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    				<RollingFile name="ALL.info" fileName="target/log4j2/ALL.info.log"
    			filePattern="target/log4j2/old/ALL.info.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    		<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    		<RollingFile name="ALL.debug" fileName="target/log4j2/ALL.debug.log"
    			filePattern="target/log4j2/old/ALL.debug.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    				<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    		<RollingFile name="ALL.trace" fileName="target/log4j2/ALL.trace.log"
    			filePattern="target/log4j2/old/ALL.trace.%i.log" immediateFlush="true"
    			append="true">
    			<PatternLayout
    				pattern="%d{HH:mm:ss.SSS} %-5level %-5.5thread %-25.25logger:%-4line %msg%n" charset="UTF-8"/>
    			<Policies>
    				<SizeBasedTriggeringPolicy size="10 MB" />
    			</Policies>
    			<DefaultRolloverStrategy max="5" />
    		</RollingFile>
    	</Appenders>
    	<Loggers>
    		<!-- additivity is true by default : so we enter in all matching loggers -->
    		<Root level="warn">
    			<AppenderRef ref="Console" level="warn" />
    			<AppenderRef level="error" ref="ALL.error" />
    			<AppenderRef level="warn" ref="ALL.warn" />
    			<!-- every jars will enter here, so nothing below WARN -->
    		</Root>
    		<Logger name="cg.wm">
    			<!-- here we are in our project, we can go down to TRACE -->
    			<AppenderRef level="info" ref="ALL.info" />
    			<AppenderRef level="debug" ref="ALL.debug" />
    			<AppenderRef level="trace" ref="ALL.trace" />
    		</Logger>
    		<!-- specifically for each module -->
    		<Logger name="cg.wm.utils" level="debug">
    			<AppenderRef ref="Console" level="info" />
    			<AppenderRef ref="cg-utils" />
    		</Logger>
    	</Loggers>
    </Configuration>
    Result log example
    07-31_14:13:48.491 DEBUG org.a.utils.ConfigUtils        - + getParameter(test)
    07-31_14:13:48.491 DEBUG org.a.utils.wmcall.WmHelper    - + 	getPackageName(true)
    07-31_14:13:48.492 DEBUG g.a.utils.wmcall.WmCallEclipse - + 		getPackageName(true)
    07-31_14:13:48.492 DEBUG g.a.utils.wmcall.WmCallEclipse - . 		getPackageName(true) -> DEFAULT
    07-31_14:13:48.492 DEBUG org.a.utils.wmcall.WmHelper    - . 	getPackageName(true) -> DEFAULT
    07-31_14:13:48.492 DEBUG org.a.utils.ConfigUtils        - + 	getParameter(DEFAULT, test)
    07-31_14:13:48.505 DEBUG org.a.utils.ConfigUtils        - . 	getParameter(DEFAULT, test) -> (null)
    07-31_14:13:48.506 DEBUG org.a.utils.ConfigUtils        - . getParameter(test) -> (null)
    LOGGER declaration
    import net.sf.aspect4log.Log;
    import static net.sf.aspect4log.Log.Level.TRACE;
    @Log (1)
    public class FooDao {
        public void tooLowLevelFunction(){ (2)
            //[...]
        @Log(enterLevel = Level.TRACE, exitLevel = Level.TRACE) (3)
    	public void delete(String foo) {
            //[...]
    	@Log(argumentsTemplate = "[...skipped...]", resultTemplate = "[...skipped...]") (4)
    	public void find(String bigXML) {
            //[...]
    	@Log(on = { @Exceptions(exceptions = { CgException.class }, level = Level.INFO) }) (5)
    	public void saveOrUpdate(String foo) {
            //[...]
    

    For runtime, have log4j & aspect4log configuration files in the classpath, examples : link:log4j2.xml & link:aspect4log.xml.

    Dependencies
    <dependencies>
        <!-- for @Log -->
        <dependency>
        	<groupId>net.sf.aspect4log</groupId>
        	<artifactId>aspect4log</artifactId>
        	<version>1.0.7</version>
        </dependency>
        <!-- AspectJ for instrumentation -->
        <dependency>
        	<groupId>org.aspectj</groupId>
        	<artifactId>aspectjrt</artifactId>
        	<version>1.8.9</version>
        </dependency>
        <dependency>
        	<groupId>org.aspectj</groupId>
        	<artifactId>aspectjtools</artifactId>
        	<version>1.8.9</version>
        </dependency>
    </dependencies>
    <plugins>
        <plugin>
        	<groupId>org.codehaus.mojo</groupId>
        	<artifactId>aspectj-maven-plugin</artifactId>
        	<version>1.7</version>
        	<executions>
        		<execution>
        			<goals>
        				<goal>compile</goal>
        			</goals>
        		</execution>
        	</executions>
        	<configuration>
        		<showWeaveInfo>false</showWeaveInfo>
        		<Xlint>adviceDidNotMatch=ignore,noGuardForLazyTjp=ignore</Xlint>
        		<aspectLibraries>
        			<aspectLibrary>
        				<groupId>net.sf.aspect4log</groupId>
        				<artifactId>aspect4log</artifactId>
        			</aspectLibrary>
        		</aspectLibraries>
        	</configuration>
        	<dependencies>
        		<dependency>
        			<groupId>org.aspectj</groupId>
        			<artifactId>aspectjtools</artifactId>
        			<version>1.8.9</version>
        		</dependency>
        	</dependencies>
        </plugin>
    </plugins>
    @Loggable(skipArgs = true, skipResult = true, name = "PERFORMANCES")
    public static void topLevelJarFunction(IData pipeline) throws ServiceException {
        //[...]
    
    Running cg.msg.tracker.ui.MainFrameIT
    11:23:37.814 [main] DEBUG TEST - Running aboutTest...
    11:23:38.503 [main] DEBUG TEST - ...OK in 0.689S
    11:25:17.561 [main] DEBUG TEST - Running updateLAFTryNextTest...
    11:25:20.865 [main] DEBUG TEST - ...OK in 3.304S
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 103.443 sec - in cg.msg.tracker.ui.MainFrameIT

    And here is how you do it.

    Inner Class in your test class
    * The Class LogTestName. public static class LogTestRule extends TestWatcher { Instant startingDate = Instant.now(); @Override protected void failed(Throwable e, Description description) { LOGGER.debug("...KO [{}]", e.getMessage()); @Override protected void starting(Description description) { LOGGER.debug("Running {}...", description.getMethodName()); startingDate = Instant.now(); @Override protected void succeeded(Description description) { LOGGER.debug("...OK in {}", Duration.between(startingDate, Instant.now()).toString().substring(2));
    Declare it’s usage in the class with @Rule
    @Rule
    public LogTestRule logTestRule = new LogTestRule());
    16:48:35.176 [main] DEBUG TEST - Running buttonsTest...
    [WARNING] buttonsTest(cg.msg.tracker.ui.ConnectionIT): Run 1 failed [Condition with alias 'broker radio button should be selected' didn't complete within 30 seconds because condition with lambda expression in cg.msg.tracker.ui.utils.ParentAssertJTestCase that uses org.assertj.swing.fixture.JRadioButtonFixture was not fulfilled.]
    16:49:21.574 [main] DEBUG TEST - ...OK in 46.394S
    16:49:21.665 [main] DEBUG TEST - Running connectionInfoZoneTextTest...
    16:49:55.874 [main] DEBUG TEST - ...OK in 0.714S
    Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 85.826 sec - in cg.msg.tracker.ui.ConnectionIT

    And here is how you do it.

    Inner Class in your test class
    * The Class RetryRule. public static class RetryRule implements TestRule { private final static int TRY_COUNT = 3; @Override public Statement apply(Statement base, Description description) { return statement(base, description); private Statement statement(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { Throwable caughtThrowable = null; // implement retry logic here for (int i = 0; i < TRY_COUNT; i++) { try { base.evaluate(); return; } catch (Throwable t) { caughtThrowable = t; // [WARNING] for a colorful Jenkins build System.out.println("[WARNING] " + description.getDisplayName() + ": Run " + (i + 1) + " failed [" + t.getMessage() + "]"); LOGGER.warn(CgException.prettyPrint(t)); commonAfterClass(); commonBeforeClass(); // [ERROR] for a colorful Jenkins build System.out.println("[ERROR] " + description.getDisplayName() + ": Giving up after " + TRY_COUNT + " failures."); throw caughtThrowable;
    Double rules usage
    @Rule
    public RuleChain chain = RuleChain.outerRule(new LogTestName()).around(new RetryRule());
    @Test(expected = IllegalArgumentException.class)
    	public void testFromStringUnknown() {
    		CgPackage.fromString("Unknown");
    
    Wait until a background task is done
    Awaitility.await("Broker radio button should be visible").until(() -> mCEditor.getBrokerRadioButton().isShowing());

    Best package organization is by fonctionnality first, and then technically when many classes of the same type

    Always put classes in subpackage of the project

    If a java project is bar-a-b, all packages are mycorp.bar.a.b.*

    Don’t use different packages for a few classes, regroup them (if below or equal 3 classes by package)

    Don’t put in the class name what is already in the package name, except for too generic file name

    Some naming conventions

    http://stackoverflow.com/questions/3226282/are-there-best-practices-for-java-package-organisation http://www.javapractices.com/topic/TopicAction.do?Id=205

    Some widely used examples

    http://commons.apache.org/proper/commons-lang/javadocs/api-2.6/overview-tree.html https://commons.apache.org/proper/commons-lang/apidocs/overview-tree.html

    2.2. Java 7 try with closable objects

    Before Java 7, you had to close() streams and other closable objects in a try/catch/finally. Now Java handles everything if you use the right pattern :

    try-with-resource
    try (
    	ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstDirectory + "/" + fileName + ".zip"));
    	FileInputStream in = new FileInputStream(foundFile.getAbsolutePath())
    	ZipEntry ze = new ZipEntry(fileName);
    	zos.putNextEntry(ze);
    	int len;
    	while ((len = in.read(buffer)) > 0) {
    		zos.write(buffer, 0, len);
    	if (delete)
    		foundFile.delete();
    } catch (IOException e) {
    	LOGGER.error("Unable to zip or delete the file=" + srcDirectory + "/" + fileName + ", dest=" + dstDirectory, e);
    	throw e;
    

    2.3. Static Java Maps

    When a Map is static (and then accessed by multiple threads), declare it Map and instantiate it ConcurrentHashMap :

    Thread-safe Map
    Map<a,b> myMap == new ConcurrentHashMap<>();

    Idem for a Set but this is a bit tricky :

    Thread-safe Set
    Set<String>
    mySet = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>());

    2.4. Init on demand

    For objects used by static functions, try to initialize them only once and do it in thread safe mode.

    Init on demand pattern
    public class Something {
        private Something() {}
        private static class LazyHolder {
            private static final Something INSTANCE = new Something();
        public static Something getInstance() {
            return LazyHolder.INSTANCE;
    	 * @param input the input
    	 * @return the package
    	 * @throws IllegalArgumentException the illegal argument exception
    	public static CgPackage fromString(String input) throws IllegalArgumentException {
    		for (CgPackage p : CgPackage.values()) {
    			if (p.str().equals(input)) {
    				return p;
    		throw new IllegalArgumentException("Unknown package=" + input);
    	 * Custom, short-named toString().
    	 * Don't use defaults name() or toString(), they'll give the strict enum name
    	 * @return the string
    	public String str() {
    		return this.str;