-
Notifications
You must be signed in to change notification settings - Fork 44
Requirements
A requirement represents one prerequisite required by a test (e.g. created user or running server). Each requirement consists of two parts:
- requirement declaration (in a form of annotation)
- code for fulfilling the 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 to define requirements:
- Simple Requirement
- Requirement with Parameters
- Requirement with Property Based Configuration
- Requirement with a Custom Schema
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.
A simple requirement consists of just an annotation without any parameters and the fulfilling class.
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.
This is how you declare the requirement that admin user has to be present in the database:
package org.jboss.reddeer.snippet.test;
import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirement.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
}
}
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.requirement;
import java.lang.annotation.Annotation;
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 AdminUserRequirement implements Requirement {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AdminUser {
}
public void setDeclaration(Annotation 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
}
}
A requirement with parameters consists of an annotation with one or more parameters and a fulfilling class that can access those parameters.
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.test;
import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.snippet.requirement.UserRequirement.User;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(RedDeerSuite.class)
@User(name = "admin")
public class UserTest {
@Test
public void test() {
}
}
This is how you access the parameters of the declared annotation.
package org.jboss.reddeer.snippet.requirement;
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;
}
}
The Red Deer JUnit component provides support for easy test run configuration by providing its own XML schema.
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:
-Dreddeer.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.
This approach makes use of a simple, property based, configuration in a Red Deer configuration file. This approach requires a mandatory XSD schema. In the future, this schema will be available on public site. Until then you need to have it locally available in the configuration validation phase (you can download it here: https://github.com/jboss-reddeer/reddeer/blob/master/plugins/org.jboss.reddeer.junit/resources/RedDeerRequirements.xsd).
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:
<?xml version="1.0" encoding="UTF-8"?>
<testrun
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/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>
</testrun>
This is how you declare the requirement that user with name ''admin'' has to be present in the database:
@RunWith(RedDeerSuite.class)
@User(name="admin")
public class UserTest {
@Test
public void test(){
}
}
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.
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() {
// create an admin user in the database if it does not exist yet
}
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;
}
}
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.
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:
<?xml version="1.0" encoding="UTF-8"?>
<testrun
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 RedDeerRequirements.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>
</testrun>
<?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="RedDeerSchema.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>
This is how you declare the requirement that user with name ''admin'' has to be present in the database:
@RunWith(RedDeerSuite.class)
@User(name="admin")
public class UserTest {
@Test
public void test(){
}
}
To read the configuration from the Red Deer configuration file using your own schema you need to create a configuration object with JAXB annotations:
@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;
}
}
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):
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() {
// create an admin user in the database if it does not exist yet
}
public void setDeclaration(User user) {
this.user = user;
}
public Class<UserConfiguration> getConfigurationClass() {
return UserConfiguration.class;
}
public void setConfiguration(UserConfiguration config) {
this.userConfiguration = config;
}
}
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.
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.
<?xml version="1.0" encoding="UTF-8"?>
<testrun
xmlns="http://www.jboss.org/NS/Req"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.org/NS/Req RedDeerSchema.xsd">
<requirements>
<requirement class="org.jboss.reddeer.wiki.user.db.password.UserRequirement" name="userRequirement">
<property key="name" value="USERS_ADMINISTRATION" />
<property key="ip" value="127.0.0.1" />
<property key="password" value="abc123" />
</requirement>
</requirements>
</testrun>
This is how you inject the UserRequirement instance into the test and access the password:
@RunWith(RedDeerSuite.class)
@User(name="admin")
public class UserTest {
@InjectRequirement
private UserRequirement userRequirement;
@Test
public void test(){
System.out.println(userRequirement.getPassword());
}
}
The UserRequirement needs to provide methods for accessing data (in this case getPassword()):
public class UserRequirement implements Requirement<User>, PropertyConfiguration{
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface User {
String name();
}
private User 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() {
user.name();
// create an admin user in the database if it does not exist yet
}
@Override
public void setDeclaration(User user) {
this.user = user;
}
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;
}
}