Skip to content

Commit

Permalink
Added ContractExclude annotation to fix Issue #12
Browse files Browse the repository at this point in the history
  • Loading branch information
Claudenw committed Dec 28, 2017
1 parent 89dcc04 commit cd8c062
Show file tree
Hide file tree
Showing 22 changed files with 364 additions and 46 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Release version
contracts-cmdline
-----------------

<a href="./cmdLine/">contracts-cmdline</a> is the model that encompasses the command line tools that will generate reports equivalent ot the maven contracts plugin.
<a href="./cmdLine/">contracts-cmdline</a> is the model that encompasses the command line tools that will generate reports equivalent to the maven contracts plugin.

Release version

Expand Down Expand Up @@ -540,12 +540,28 @@ Indicates that the method is a getter that provides the IProducer to inject or a
ContractImpl
------------

Indentifies the implementation class that is being tested. The argument is the class for which an instance will be created. The __ignore__ property may specify contract tests that should not be executed.
Identifies the implementation class that is being tested.

ContractImpl has two attributes that can remove tests.

* skip is an array of interface classes that should not be tested. All contract tests for the interfaces will be skipped.

* ignore list any @Contract annotated tests that should be ignored. This allows removal of broken
tests that are outside the control of the developer of the @ContractImpl test.

ContractExclude
---------------

ContractExclude is intended to be used with @ContractImpl. The annotation has 2 arguments
1. value is the name of the class that contains the test to exclude.
2. methods is a list of method names in the test class to exclude.

This annotation will remove the tests only for the ContractImpl it is associated with.

ContractTest
------------

Like a JUnit Test annotation but requries that the test be run within the ContractSuite runner.
Like a JUnit Test annotation but requires that the test be run within the ContractSuite runner. Contract tests may be annotated with the standard JUnit @Ignore annotation to disable the test for all contract suites.

Dynamic.Inject
--------------
Expand Down
16 changes: 13 additions & 3 deletions junit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ junit-contracts: A contract test suite runner
A suite runner for use with JUnit @RunWith annotation to run contract tests for interfaces. Handles merging multiple tests from individual
contract tests into a single test suite for concrete implementations of one or more interfaces.

Introduces six annotations:
Introduces seven annotations:

* @Contract - To map contract tests to the interfaces they test.
* @Contract.Inject - To identify the producer of the object under test.
Expand All @@ -13,14 +13,15 @@ Introduces six annotations:
* @Dynamic.Inject - To identify the master producer for dynamic suites.
* @NoContractTest - To identify interfaces that should not or do not yet have contract tests. This only applies to interfaces that have methods as pure marker
interfaces are automatically ignored.
* @ContractExclude - To exclude specific tests from being executed.

Introduces one class

* ContractSuite - used in the JUnit @RunWith to identify a contract testing suite.

Introduces two interfaces

* Producer - defines a producer that creates new instances of the object under test and can clean up after the test is run.
* IProducer - defines a producer that creates new instances of the object under test and can clean up after the test is run.
* Dynamic - defines a dynamic test suite. Dynamic test suites produce a list of tests after the suite is instantiated.

Maven Repository Info
Expand Down Expand Up @@ -66,10 +67,19 @@ The @ContractImpl has two attributes that can remove tests.
* ignore list any @Contract annotated tests that should be ignored. This allows removal of broken
tests that are outside the control of the developer of the @ContractImpl test.

@ContractExclude
----------------

The @ContractExclude annotation is intended to be used with @ContractImpl. The annotation has 2 arguments
1. value is the name of the class that contains the test to exclude.
2. methods is a list of method names in the test class to exclude.

This annotation will remove the tests only for the ContractImpl it is associated with.

@Contract tests
---------------

The @Contract tests may be removed by adding the standard junit @Ignore annotation.
The @Contract tests may be removed by adding the standard junit @Ignore annotation. This will remove it from all contract testing.



50 changes: 50 additions & 0 deletions junit/src/main/java/org/xenei/junit/contract/ContractExclude.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.xenei.junit.contract;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to exclude specific test methods from the contract suite.
* May be used multiple times.
* <p>
* For example
*
* <pre>
*
* &#64;RunWith( ContractSuite.class )
* &#64;ContractImpl( FooImpl.class )
* &#64;ContractExclude( ExpertFooTests.class, fns="{barTest,bazTest} )
* public class Foo_Test {...}
* </pre>
* <p>
* Declares that in the <code>ExpertFooTests</code> class the
* <code>barTest</code>, and <code>bazTest</code> methods should not
* be executed.
* </p>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ContractExcludes.class)
public @interface ContractExclude
{
/**
* The Contract test implementation (annotated with
* &#64;Contract) that declares the methods that should not be executed.
*
* @return The Contract test class.
*/
Class<?> value();

/**
* The list of interface classes for which tests should be skipped.
* This list are interfaces that the class under tests implements but that should
* not be tested.
*
* @return The interfaces to skip testing.
*/
String[] methods();

}
22 changes: 22 additions & 0 deletions junit/src/main/java/org/xenei/junit/contract/ContractExcludes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.xenei.junit.contract;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* The container for repeated ContractExclude annotations.
*
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContractExcludes
{
/**
*
* The list of ContractExclude annotations.
*/
ContractExclude[] value();
}
10 changes: 7 additions & 3 deletions junit/src/main/java/org/xenei/junit/contract/ContractImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,18 @@

/**
* The list of interface classes for which tests should be skipped.
* This list are interfaces that the class under tests implements but that should
* not be tested.
*
* @return The interfaces to skip.
* @return The interfaces to skip testing.
*/
Class<?>[] skip() default {};

/**
* The list of implementations to skip. These are implementations of
* interface tests.
* The list of implementations to skip.
* This list is a list of test implementations that apply to the class under test
* but that should not be executed. This is often
* because the tests are not applicable to this implementation.
*
* @return the list of interface test implementations to skip.
*/
Expand Down
53 changes: 45 additions & 8 deletions junit/src/main/java/org/xenei/junit/contract/ContractSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.xenei.junit.contract;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -52,6 +53,7 @@
import org.xenei.junit.contract.info.TestInfo;
import org.xenei.junit.contract.info.TestInfoErrorRunner;


/**
* Class that runs the Contract annotated tests.
*
Expand Down Expand Up @@ -146,8 +148,6 @@ public ContractSuite(final Class<?> contractTest, final RunnerBuilder builder)
*
* @param cls
* The class to look on
* @param errors
* The list of errors to add to if there is an error
* @return ContractImpl or null if not found.
* @throws InitializationError
*/
Expand All @@ -161,6 +161,38 @@ private ContractImpl getContractImpl(final Class<?> cls) throws InitializationEr
return impl;
}

/**
* Get the ContractExclude annotation and extract the list of methods from it.
*
* logs error if method is not found on the class specified in the annotation.
*
* @param cls
* The class on which to for the annotation.
* @param errors
* The list of errors to add to if there is an error
* @return A list of methods. May be empty.
* @throws InitializationError
*/
private List<Method> getExcludedMethods(final Class<?> cls) {

List<Method> lst = new ArrayList<Method>();
for (ContractExclude exclude : cls.getAnnotationsByType(ContractExclude.class))
{
Class<?> clazz = exclude.value();
for (String mthdName : exclude.methods())
{
try
{
lst.add(clazz.getDeclaredMethod(mthdName));
} catch (NoSuchMethodException | SecurityException e)
{
LOG.warn( String.format( "ContractExclude annotation on %s incorrect", cls), e);
}
}
}
return lst;
}

/**
* Add dynamic classes to the suite.
*
Expand Down Expand Up @@ -283,17 +315,20 @@ private List<Runner> addAnnotatedClasses(final Class<?> baseClass, final RunnerB
private void addSpecifiedClasses(final List<Runner> runners, final Class<?> testClass, final RunnerBuilder builder,
final ContractTestMap contractTestMap, final Object baseObj, final TestInfo parentTestInfo)
throws InitializationError {


// this is the list of all the JUnit runners in the suite.

final Set<TestInfo> testClasses = new LinkedHashSet<TestInfo>();

// we have a RunWith annotated class: Klass
// see if it is in the annotatedClasses

final BaseClassRunner bcr = new BaseClassRunner(testClass);
if (bcr.computeTestMethods().size() > 0) {
runners.add(bcr);
}

List<Method> excludeMethods = getExcludedMethods(getTestClass().getJavaClass());

/*
* get all the annotated classes that test the interfaces that
* parentTestInfo implements and iterate over them
Expand All @@ -308,7 +343,7 @@ private void addSpecifiedClasses(final List<Runner> runners, final Class<?> test
runner.logErrors(LOG);
runners.add(runner);
} else {
runners.add(new ContractTestRunner(baseObj, parentTestInfo, testInfo));
runners.add(new ContractTestRunner(baseObj, parentTestInfo, testInfo, excludeMethods));
}
}
}
Expand Down Expand Up @@ -341,9 +376,9 @@ protected void runChild(final Runner child, final RunNotifier notifier) {
private class BaseClassRunner extends BlockJUnit4ClassRunner {

private List<FrameworkMethod> testMethods = null;

public BaseClassRunner(final Class<?> cls) throws InitializationError {
super(cls);
super(cls);
}

@Override
Expand All @@ -367,8 +402,10 @@ protected void validateInstanceMethods(final List<Throwable> errors) {
protected List<FrameworkMethod> computeTestMethods() {
if (testMethods == null) {
testMethods = new ArrayList<FrameworkMethod>();
List<Method> excludeMethods = getExcludedMethods(getTestClass().getJavaClass());
for (final FrameworkMethod mthd : super.getTestClass().getAnnotatedMethods(ContractTest.class)) {
if (mthd.getMethod().getDeclaringClass().getAnnotation(Contract.class) == null) {
if (mthd.getMethod().getDeclaringClass().getAnnotation(Contract.class) == null &&
! excludeMethods.contains(mthd.getMethod())) {
testMethods.add(mthd);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Ignore;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
Expand All @@ -43,6 +47,7 @@ public class ContractTestRunner extends BlockJUnit4ClassRunner {
private final Object getterObj;
// the getter method to call.
private final Method getter;
private final List<Method> excludedMethods;

/**
* Create a test runner within the ContractTestSuite.
Expand All @@ -54,24 +59,29 @@ public class ContractTestRunner extends BlockJUnit4ClassRunner {
* The test info for the parent.
* @param testInfo
* The test info for this test.
* @param excludedMethods
* A list of test methods that should not be executed.
*
* @throws InitializationError
* on error.
*/
public ContractTestRunner(Object getterObj, TestInfo parentTestInfo,
TestInfo testInfo) throws InitializationError {
TestInfo testInfo, List<Method> excludedMethods) throws InitializationError {
super(testInfo.getContractTestClass());
this.parentTestInfo = parentTestInfo;
this.testInfo = testInfo;
this.getterObj = getterObj;
this.getter = parentTestInfo.getMethod();
this.excludedMethods = excludedMethods;
}

/**
* Create a test runner for stand alone test
*
* @param testClass
* The ContractTest annoated class.
* The ContractTest annotated class.
* @param excludedMethods
* A list of test methods that should not be executed.
*
* @throws InitializationError
* on error.
Expand All @@ -82,6 +92,7 @@ public ContractTestRunner(Class<?> testClass) throws InitializationError {
this.testInfo = null;
this.getterObj = null;
this.getter = null;
this.excludedMethods = Collections.emptyList();
}

/**
Expand Down Expand Up @@ -117,6 +128,16 @@ protected Object createTest() throws InstantiationException,
return retval;

}

@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (method.getAnnotation(Ignore.class) != null || excludedMethods.contains( method.getMethod())) {
notifier.fireTestIgnored(description);
} else {
runLeaf(methodBlock(method), description, notifier);
}
}

/**
* Adds to {@code errors} if the test class has more than one constructor,
Expand Down Expand Up @@ -161,8 +182,13 @@ protected Description describeChild(FrameworkMethod method) {
*/
@Override
protected List<FrameworkMethod> computeTestMethods() {
// this is call during construction. testInfo is not yet available.
// this is call during construction. testInfo and excludedMethods is not yet available.
return getTestClass().getAnnotatedMethods(ContractTest.class);
}

@Override
public String toString() {
return "ContractTest"+testInfo==null?"":testInfo.toString();
}

}
Loading

0 comments on commit cd8c062

Please sign in to comment.