Skip to content

Requirements

rawagner edited this page Aug 8, 2017 · 79 revisions

What are requirements?

Requirements are one of the most important part of RedDeer. Usually, when you want to execute tests, you need an environment in proper state, you need something to be set etc. Basically, you require a specific state, specific actions to be taken before your tests execute. And for that there are requirements.

Requirements are annotation driven setup of test environment. It is executed once for each class before @BeforeClass method.

Requirements types

There are 2 basic types of requirements:

Basic Requirement

Basic Requirement implements interface org.eclipse.reddeer.junit.requirement.Requirement<T extends Annotation>. There are several default requirements of such type, e.g. AutoBuildRequirement (to turn on/off auto building), CleanWorkspaceRequirement (remove all projects from workspace), CloseAllEditorsRequirement and many more

Example

We want to make sure that workspace does not contain any projects so we use CleanWorkspace requirement.

@CleanWorkspace
public class MyUltimateTest { 
... 
@Test
public void myTest(){
 //test something
}
...
}

Configurable Requirement

Configurable Requirement implements interface org.eclipse.reddeer.junit.requirement.ConfigurableRequirement<T extends RequirementConfiguration, K extends Annotation>. Such requirements use additional configuration obtained from a configuration file. There are several default configurable requirements such as ServerRequirement, DatabaseRequirement, PropertyRequirement and many others.

Example

We want to make sure that JRE is defined.

@JRE
public class MyUltimateTest { 
... 
@Test
public void myTest(){
 //test something
}
...
}

Since JRE is a complex requirement it requires a configuration file. In case of JRE it will look like this (it is a json file)

{
	"org.eclipse.reddeer.requirements.jre.JRERequirement.JRE": [ //FQN of JRE requirement annotation
		{
                        //requirement properties
			"name": "jre-name",
			"version": "1.7",
			"path": "/path/to/jre/home"
		}
	]
}

When we run our test we have to specify -Drd.config=/path/to/config/json/file

We can also define more JREs in configuration file

{
	"org.eclipse.reddeer.requirements.jre.JRERequirement.JRE": [
		{
			"name": "jre-name1",
			"version": "1.7",
			"path": "/path/to/jre17/home"
		},
                {
			"name": "jre-name2",
			"version": "1.8",
			"path": "/path/to/jre18/home"
		}
	]
}

In this case, our test will run twice - for both JREs defined.

Restricting configurations for test execution

Usually there is a configuration file with plenty of configurations. Some time we don't want to run a specific test class with all of those configuration. In such cases we can restrict which configurations should be used for test execution by implementing a public static method returning a requirement matcher or collection of requirement matchers and annotated with @RequirementRestriction. Then, in the moment of building test suites, only configurations matching a returned matcher will be used for a test class.

Example

Our configuration file

{
	"org.eclipse.reddeer.requirements.jre.JRERequirement.JRE": [
		{
			"name": "jre-name1",
			"version": "1.7",
			"path": "/path/to/jre17/home"
		},
                {
			"name": "jre-name2",
			"version": "1.8",
			"path": "/path/to/jre18/home"
		}
	]
}

Our test class

@JRE
public class MyUltimateTest { 
... 

@RequirementRestriction
public static RequirementMatcher getRestrictionMatcher() {
  return new RequirementMatcher(JRE.class, "version", new VersionMatcher(">1.7"));
}

@Test
public void myTest(){
 //test something
}
...
}

Our test will run only with JREs which version is >1.7.

We can also return Collection of RequirementMatcher-s as follows:

@JRE
public class MyUltimateTest { 
... 

@RequirementRestriction
public static Collection<RequirementMatcher> getRestrictionMatcher() {
  return Arrays.asList(
	new RequirementMatcher(JRE.class, "version", new VersionMatcher(">1.7")),
	new RequirementMatcher(JRE.class, "name", new RegexMatcher("jre.*")));
}

@Test
public void myTest(){
 //test something
}
...
}

Requirement matcher

Requirement matcher takes requirement annotation class, attribute name and a matcher or a string to match attribute. Attribute could be nested. For nested attributes there is used a dot notation - e.g. nesteConfig.anotherNestedConfig.version

One of the two most useful matchers to be used are RegexMatcher and VersionMatcher located in common plugin. But this does not restrict you to use only those 2 matchers. RequirementMatcher takes any matcher matching a string value.

Injecting Requirements

We can inject requirement to specified field as follows:

@JRE
public class MyUltimateTest { 
... 

@InjectRequirement
JRERequirement jre;

@Test
public void myTest(){
 //access JRE requirement configuration
 JREConfiguration configuration = jre.getConfiguration();

 //get JRE version
 configuration.getVersion();
}
...
}

Requirements and test suites

Requirements does not only modify you test environment but they change test suites for execution as well. For every set of configurable requirements there is a special suite constructed. For a class with 2 configurable requirements, each of them having 2 available configurations, there are 4 suites in total. It is possible to restrict a suitable configuration for a test class Let's have a look at example. We have a configuration file with 2 servers and 2 databases configuration in it, and we have following test classes

@Database
@ApacheTomcatServer
public class TestClass1 {...}
@Database
@ApacheTomcatServer
public class TestClass1 {...}

This will result into execution of 4 test suites with combinations of servers and databases (Cartesian product):

DB1-SRV1
  TestClass1
  TestClass2
DB1-SRV2
  TestClass1
  TestClass2
DB2-SRV1
  TestClass1
  TestClass2
DB2-SRV2
  TestClass1
  TestClass2

Have in mind that this is true only for configurable requirements. Other requirement implementing Requirement interface does not affect amount of test suites.

Implementing a new requirement

To successfully implement a new requirement, there is a list of tasks or more like requirement pattern to be done:

  • requirement annotation
    • each requirement implementing a requirement interface must have an annotation for requirement declaration
    • annotation have to be in the same class as a requirement and must be public (due to logic how requirements are obtained and processed)
    • annotation can contain some elements... These elements could be perceived as static set up of a requirement
    • annotation has to be annotated with @Retention(RetentionPolicy.RUNTIME) and @Target(ElementType.TYPE) (due to logic how requirements are obtained and processed)
  • implementation of interface methods - all methods from a requirement interface should be implemented, if possible

There are additional steps for configurable requirements:

  • requirement has to be registered via extension point org.eclipse.reddeer.juni.requirement
<plugin>
   <extension point="org.eclipse.reddeer.junit.requirement">
	   <requirement class="FQN of your requirement"/>
   </extension>
</plugin>
  • RequirementConfiguration has to be a POJO object with setters and getters for successful deserialization from json configuration file.

Requirements under the hood

This section contains several more advanced topics.

Requirement configuration pool and constructing of configurations

Configurations from a configuration file are built for a test run. This happens when a suite is built and it is one time action. Keep this in mind when working on tests/extending functionality of builders, runners, configuration pool and configuration readers in RedDeer JUnit plugin. There is a static method RequirementConfigurationPool#destroyPool which could come handy.

Implementing a new configuration reader

For now, there is only JSON configuration reader. But that's not final. You can implement your own reader by contributing to RedDeer and implementing interface org.eclipse.reddeer.junit.internal.configuration.reader.ConfigurationReader in RedDeer JUnit plugin and adding few lines of code to RequirementConfigurationPool#initRequirementConfigurationPool. If you decide to go for a dynamically constructed requirements configurations from a configuration file, check JSONConfigurationReader#loadClass(String className) method.

Clone this wiki locally