Skip to content

Requirements 12x

rawagner edited this page Jul 19, 2017 · 1 revision

A requirement represents one prerequisite required by a test (e.g. created user or running server). Each requirement consists of three parts:

  • requirement declaration (in a form of annotation)
  • code for fulfilling the requirement (java class)
  • code for cleaning up after requirement (java class)

The biggest difference between a Red Deer requirement and the the @BeforeClass annotation from the JUnit framework is that if a requirement cannot be fulfilled the test is not executed. This is implemented via RedDeerSuite class therefore all tests requiring requirement functionality has to be run with RedDeerSuite suite i. e. has to be annotated with annotation @RunWith(RedDeerSuite.class)

There are 4 ways how to define requirements:

Requirements without XML configuration file

Requirements with XML configuration file

Complete source code of used code snippets could be found here RedDeer Examples. To use it just clone RedDeer repo and import org.jboss.reddeer.examples plugin to Eclipse workspace.


Requirements without XML configuration file

Simple Requirement 

A simple requirement consists of just an annotation without any parameters and the fulfilling class.

Example

Let's say you have a test that requires a database admin user account to be defined. You do not care if the user account already exists when the test is run. If the account does not exist, you want it to be created before the test methods are executed.

Requirement usage

This is how you declare the requirement that admin user has to be present in the database:

package org.jboss.reddeer.snippet.requirements.simple;

import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirements.simple.AdminUserRequirement.AdminUser;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(RedDeerSuite.class)
@AdminUser
public class AdminUserTest {
       
	@Test
    public void test(){
		// test admin user
	}
}

source code

Requirement Fulfilling Class

This is how you create a class for fulfilling the declared requirement (the annotation needs to be an inner member of the class implementing Requirement interface)

package org.jboss.reddeer.snippet.requirements.simple;

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

import org.jboss.reddeer.junit.requirement.Requirement;
import org.jboss.reddeer.snippet.requirements.simple.AdminUserRequirement.AdminUser;

public class AdminUserRequirement implements Requirement<AdminUser> {

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AdminUser {
    }

	public void setDeclaration(AdminUser declaration) {
		// no need to access the annotation
	}

	public boolean canFulfill() {
		 // return true if it is possible to connect to database, false otherwise
        return true;
	}

	public void fulfill() {
		// create an admin user in the database if admin user does not exists
		
	}

	public void cleanUp() {
		// perform clean up
	}
}

source code


Requirement with Parameters 

A requirement with parameters consists of an annotation with one or more parameters and a fulfilling class that can access those parameters.

Example

If you want to specify the user name you can create an annotation with a user name parameter. The requirement fulfilling class code will have access to that parameter and will create appropriate user in the database if needed.

This is how you declare the requirement that a user with name ''admin'' has to be present in the database:

package org.jboss.reddeer.snippet.requirements.withparameters;

import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirements.withparameters.UserRequirement.User;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(RedDeerSuite.class)
@User(name = "admin")
public class UserTest {
	
	@Test
	public void test() {
	}
}

source code

Requirement Fulfilling Class

This is how you access the parameters of the declared annotation.

package org.jboss.reddeer.snippet.requirements.withparameters;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jboss.reddeer.junit.requirement.Requirement;

public class UserRequirement implements Requirement<UserRequirement.User> {
	
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface User {
		String name();
	}

	private User user;

	public boolean canFulfill() {
		// return true if you can connect to the database
		return true;
	}

	public void fulfill() {
		System.out.println("Fulfilling reuirement User with name: "
				+ user.name());
		// create an user in the database if user does not exist
	}

	public void cleanUp() {
		// perform clean up
	}

	public void setDeclaration(User user) {
		this.user = user;
	}
}

source code


Requirements with XML configuration file

The Red Deer JUnit component provides support for easy test run configuration by providing its own XML schema (http://www.jboss.org/schema/reddeer/RedDeerSchema.xsd).

To create a test run configuration you only need to create the XML file and point Red Deer either to that file or to the folder containing several XML configuration files by specifying the following property on the command line when you run your test:

-Drd.config=/home/path/to/file/or/directory

You can make use of this configuration file to configure your own requirements in two ways:

  • Use a simple property based (key-value) configuration. To do this, you will need to provide setter methods in your requirement fulfilling class for each configuration property, or
  • Create your own XML schema that can be hooked into the XML configuration file. To do this, you will need to create a special configuration object that will be injected into your requirement.

The following sections describe each of these two approaches.


Requirements with Property Based Configuration 

This approach makes use of a simple, property based, configuration in a Red Deer configuration file.

Example

You probably do not want to hard-code some database information into your Java code. Instead, you can put that information into a Red Deer configuration file:

Configuration file

<?xml version="1.0" encoding="UTF-8"?>
<reddeer 
	xmlns="http://www.jboss.org/NS/Req" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:server="http://www.jboss.org/NS/ServerReq"
	xsi:schemaLocation="http://www.jboss.org/NS/Req http://www.jboss.org/schema/reddeer/v1/RedDeerSchema.xsd">

	<requirements>
		<requirement class="org.jboss.reddeer.snippet.requirement.simple.UserRequirement"
			name="userRequirement">
			<property key="name" value="USERS_ADMINISTRATION" />
			<property key="ip" value="127.0.0.1" />
		</requirement>
	</requirements>
</reddeer>

source code

Requirement usage

This is how you declare the requirement that user with name ''admin'' has to be present in the database:

package org.jboss.reddeer.snippet.requirements.property;

import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirements.property.UserRequirement.User;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(RedDeerSuite.class)
@User
public class UserTest {
	
	@Test
	public void test(){
		// put test logic here
	}
}

source code

Requirement Fulfilling Code

Since it is possible to use requirements without any configuration, you need to let Red Deer know that you'd like to use property based config by implementing the PropertyConfiguration interface.

package org.jboss.reddeer.snippet.requirements.property;

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

import org.jboss.reddeer.junit.requirement.PropertyConfiguration;
import org.jboss.reddeer.junit.requirement.Requirement;
import org.jboss.reddeer.snippet.requirements.property.UserRequirement.User;
/**
 * User requirement using configuration from simple xml file
 * @author lucia jelinkova
 *
 */
public class UserRequirement implements Requirement<User>, PropertyConfiguration{

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface User {
	}
	private String name;
	
	private String ip;
	
	public boolean canFulfill() {
		// return true if you can connect to the database
		return true;
	}

	public void fulfill() {
		System.out.println("Fulfilling User requirement with\nName: " + name + "\nIP: " + ip);
	}
	
	public void setDeclaration(User user) {
		// annotation has no parameters so no need to store it
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setIp(String ip) {
		this.ip = ip;
	}

	@Override
	public void cleanUp() {
		// perform clenaup if needed
	}
}

source code


Requirements with a Custom Schema 

This approach makes use of a Red Deer configuration file but also provides its own XML schema. This is more complex than a property based configuration but has the advantages of:

  • Protects you from making typo errors
  • Allows you to refactor your code without a need for changes in configuration files, and
  • Enables you to mark some properties as required so that XML schema validation will prevent you from forgetting some parameters

This approach is intended to be used mainly if you provide your requirements for other teams or components.

Example

Let's say you have many configuration options or you want to use the same user requirements in several configuration files. You can create a simple XSD schema and read it using JAXB annotations all within a Red Deer configuration file:

Configuration File

<?xml version="1.0" encoding="UTF-8"?>
<reddeer 
	xmlns="http://www.jboss.org/NS/Req" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:user="http://www.jboss.org/NS/user-schema"
	xsi:schemaLocation="http://www.jboss.org/NS/Req http://www.jboss.org/schema/reddeer/v1/RedDeerSchema.xsd http://www.jboss.org/NS/user-schema user-schema.xsd">

	<requirements>
		<user:user-requirement name="user-requirement">
			<user:db-name>USERS_ADMINISTRATION</user:db-name>
			<user:ip>127.0.0.1</user:ip>
			<user:port>1111</user:port>
		</user:user-requirement>
	</requirements>
</reddeer>

source code

XSD schema (user-schema.xsd)

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	elementFormDefault="qualified" 
	targetNamespace="http://www.jboss.org/NS/user-schema"
	xmlns:user="http://www.jboss.org/NS/user-schema" 
	xmlns:rd="http://www.jboss.org/NS/Req">
	
	<!-- Import basic RedDeer requirements -->
	<xs:import namespace="http://www.jboss.org/NS/Req" schemaLocation="RedDeerRequirements.xsd" />

	<!-- Specify user-requirement -->
	<xs:element name="user-requirement" type="user:userRequirementType" substitutionGroup="rd:abstractRequirement">
		<xs:annotation>
			<xs:documentation>Specifies all data needed to create a user in the database</xs:documentation>
		</xs:annotation>
	</xs:element>

	<!-- type for user-requirement -->
	<xs:complexType name="userRequirementType">
		<xs:complexContent>
			<xs:extension base="rd:abstractRequirementType">
				<xs:sequence>
					<xs:element name="db-name" type="xs:string" minOccurs="1" maxOccurs="1"/>
					<xs:element name="ip" type="xs:string" minOccurs="1" maxOccurs="1"/>
					<xs:element name="port" type="xs:string" minOccurs="1" maxOccurs="1"/>
				</xs:sequence>
			</xs:extension>
		</xs:complexContent>
	</xs:complexType>
</xs:schema>

source code

Requirement Usage

This is how you declare the requirement that user with name ''admin'' has to be present in the database:

package org.jboss.reddeer.snippet.requirements.custom;

import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirements.custom.UserRequirement.User;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(RedDeerSuite.class)
@User(name="admin")
public class UserTest {
	@Test
	public void test(){
		// put your test logic here
	}
}

source code

Requirement Configuration

To read the configuration from the Red Deer configuration file using your own schema you need to create a configuration object with JAXB annotations:

package org.jboss.reddeer.snippet.requirements.custom;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
 * Stores user requirement configuration loaded from custom xml file
 * @author lucia jelinkova
 *
 */
@XmlRootElement(name="user-requirement", namespace="http://www.jboss.org/NS/user-schema")
public class UserConfiguration {

	private String dbName;
	
	private String ip;
	
	private String port;

	public String getIp() {
		return ip;
	}

	@XmlElement(namespace="http://www.jboss.org/NS/user-schema")
	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getPort() {
		return port;
	}

	@XmlElement(namespace="http://www.jboss.org/NS/user-schema")
	public void setPort(String port) {
		this.port = port;
	}

	public String getDbName() {
		return dbName;
	}

	@XmlElement(name="db-name", namespace="http://www.jboss.org/NS/user-schema")
	public void setDbName(String dbName) {
		this.dbName = dbName;
	}
}

source code

Requirement

Since it is possible to use requirements without any configuration or with a property based configuration, you need to let Red Deer know that you'd like to use a custom configuration by implementing CustomConfiguration. You'll also need to indicate the class of JAXB annotated object (called Userconfiguration):

package org.jboss.reddeer.snippet.requirements.custom;

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

import org.jboss.reddeer.junit.requirement.CustomConfiguration;
import org.jboss.reddeer.junit.requirement.Requirement;
import org.jboss.reddeer.snippet.requirements.custom.UserRequirement.User;

/**
 * User requirement using configuration from custom xml file
 * @author lucia jelinkova
 *
 */
public class UserRequirement implements Requirement<User>, CustomConfiguration<UserConfiguration> {

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface User {
		String name();
	}
	
	private User user;
	
	private UserConfiguration userConfiguration;
	
	public boolean canFulfill() {
		// return true if you can connect to the database
		return true;
	}

	public void fulfill() {
		System.out.println("fulfiling requirement User with\nName: " + user.name() +
			"\nDB name: " +	userConfiguration.getDbName() +
			"\nPort: " + userConfiguration.getPort() +
		    "\nIP: " + userConfiguration.getIp());
	}
	
	public void setDeclaration(User user) {
		this.user = user;
	}
	
	public Class<UserConfiguration> getConfigurationClass() {
		return UserConfiguration.class;
	}
	
	public void setConfiguration(UserConfiguration config) {
		this.userConfiguration = config;
	}

	@Override
	public void cleanUp() {
	}
}

source code

Accessing XML configuration from within test class

It is also possible to inject the whole Requirement instance into the test class so that you are able to access the configuration defined in the XML file.

Example

You need to access user's password (defined in the XML configuration file) in your test. You can inject the whole UserRequirement instance into the test's property and access all data that the requirement exposes.

Configuration file

<?xml version="1.0" encoding="UTF-8"?>
  <reddeer
        xmlns="http://www.jboss.org/NS/Req"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.jboss.org/NS/Req http://www.jboss.org/schema/reddeer/v1/RedDeerSchema.xsd">

	<requirements>
		<requirement class="org.jboss.reddeer.junit.injection.UserRequirement" name="userRequirement">
			<property key="name" value="USERS_ADMINISTRATION" />
			<property key="ip" value="127.0.0.1" />
			<property key="password" value="abc123" />
		</requirement>
	</requirements>
</reddeer>

source code

Requirement Usage

This is how you inject the UserRequirement instance into the test and access the password:

package org.jboss.reddeer.snippet.requirements.inject;

import org.jboss.reddeer.junit.requirement.inject.InjectRequirement;
import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirements.inject.UserRequirement.User;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
 * Test injecting user requirement
 * 
 * Set VM parameter -Drd.config to point to directory with requirements.xml file 
 * -Drd.config=${project_loc}/src/org/jboss/reddeer/junit/injection
 * 
 * @author lucia jelinkova
 *
 */
@RunWith(RedDeerSuite.class)
@User
public class UserTest {
	
	@InjectRequirement
	private UserRequirement userRequirement;
	
	@Test
	public void test(){
		System.out.println(userRequirement.getPassword());
	}
}

source code

Requirement

The UserRequirement needs to provide methods for accessing data (in this case getPassword()):

package org.jboss.reddeer.snippet.requirements.inject;

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

import org.jboss.reddeer.junit.requirement.PropertyConfiguration;
import org.jboss.reddeer.junit.requirement.Requirement;
import org.jboss.reddeer.snippet.requirements.inject.UserRequirement.User;

public class UserRequirement implements Requirement<User>, PropertyConfiguration{

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface User {
	}
	
	private String name;
	
	private String ip;
	
	private String password;
	
	public boolean canFulfill() {
		// return true if you can connect to the database
		return true;
	}

	public void fulfill() {
		System.out.println("Fulfilling User requirement with\nName: " + name + "\nIP: " + ip);
	}
	
	@Override
	public void setDeclaration(User user) {
		// annotation has no parameters no need to store reference to it
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setIp(String ip) {
		this.ip = ip;
	}
	
	public void setPassword(String password) {
		this.password = password;
	}
	
	public String getPassword() {
		return password;
	}

	@Override
	public void cleanUp() {
		
	}
}

source code

Clone this wiki locally