JUnit 4,JWebUnit,Arquillian和JSF单元示例教程

时间:2020-02-23 14:35:23  来源:igfitidea点击:

在开发生命周期中,我们大多数人都在寻找一种方法来确保已编写的工作单元能够正常工作并遵守合同。
当您同时使用一个类和一个对象时,这实际上可能会容易得多。
但是,如果您有大量包含大量应检查方法的组件,该怎么办?

这不是全部情况,虽然大多数软件都是逐步开发的,但是没有人可以在开始开发过程之前向您保证他/她拥有整个业务组件。
这将带来一些复杂性,因为一类的实现可能会多次演化,并且每次需要重新检查其运行正常且演化不会破坏其核心功能时,都会对其进行扩展。

在那之后,您可能会面临多少复杂性,以便多次重新检查某个类,以确保它不会由于您有意或者无意地进行的某些增强而被破坏(这是逐渐发展的正常结果)。
这实际上是JUnit框架及其所有相关基础的工作。

JUnit是一个开源软件,可以帮助您生成可以轻松,一致且有效地执行的测试用例,以验证您的组件是否按预期工作并尽早发现错误。

在JUnit上开发了许多框架。
HttpUnit,ServeltUnit,JSFUnit,JWebUnit ..等框架是这些软件的示例,您可以使用它们来验证组件,无论组件是否如此简单或者需要特定的容器来运行。

其中您将找到理解JUnit的基本术语并有效使用其功能所需的全部内容。
除此之外,您还将看到如何分别使用JUnit,JWebUnit和JSFUnit(Arquillian)来验证简单的Java类,Web Page,Portlet和JSF组件。

遵循以下主要要点,这些要点将在本教程中进行讨论,您可以直接导航到所需的那个要点:

  • 设置JUnit
  • 通过Eclipse利用JUnit
  • JUnit注释; @ Test,@ Before,@ After,@ AfterClass,@ BeforeClass
  • 超时与预期
  • 运行步者和运行步者类型
  • 断言和断言类型
  • 关于Hamcrest库 的一点点
  • JUnit扩展– JWebUnit
  • JUnit扩展– JSFUnit
  • Maven和JUnit
  • 概要

为确保您完全了解本教程将要讨论的所有概念,请确保还阅读以下内容:

  • 《 JSF入门教程》将帮助您了解有关JSF框架的基本概念并阐明最简单的示例。

  • Apache Pluto教程将帮助您了解有关Portlet,Portlet容器的基本概念并阐明HelloWorld Portlet。

设置JUnit

设置JUnit只不过是在编译路径和执行路径中添加了两个JAR。
执行以下所有步骤即可:

  • 从Maven中央存储库下载JUnit JAR。

  • 从Maven中央存储库下载hamcrest库。
    实际上,JUnit依赖于此库来获得更有吸引力,更简单和明确的断言。

  • 将JUnit和hamcrest库添加到您的类路径中。
    您可以使用的最简单的方法是将这些库设置在JDK扩展目录下。
    指定环境所引用的JDK。
    JAVA_HOME变量应该有所帮助。

  • 指定JDK扩展的位置,始终可以在JDK目录下找到此路径。
    %JAVA_HOME%\ jre \ lib \ ext。

  • 通过对控制台调用java -verbose来确保您在类路径中具有JUnit库,该控制台将立即打印出所有已加载的类。

Java的执行-详细

[Loaded org.junit.runner.JUnitCore from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.notification.RunListener from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.internal.TextListener from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.Describable from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.Runner from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.manipulation.Filterable from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.manipulation.Sortable from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.internal.runners.JUnit38ClassRunner from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.internal.JUnitSystem from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.Computer from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runners.model.RunnerBuilder from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.Computer from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runners.ParentRunner from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runners.Suite from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.notification.RunNotifier from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.notification.SynchronizedRunListener from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
[Loaded org.junit.runner.notification.StoppedByUserException from file:/C:/Program%20Files/Java/jdk1.6.0_26/jre/lib/ext/junit-4.12.jar]
  • 创建最简单的测试用例,如下所示

HelloWorldTestCase.java

import org.junit.Test;
import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@Test
	public void testHelloWorld(){
		//Print a message before assertion
		System.out.println("Before Getting Test Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test Execution ::");
	}

	public static void main(String [] args){
		//JUnitCore is provided by JUnit to run your Tests through consols
		org.junit.runner.JUnitCore.runClasses(HelloWorldTestCase.class);
	}
}
  • 打开控制台并编译该类,然后执行它。
    我们使用了assertEquals,它应该比较最后两个参数的值("调用equals()")。
    以后再说。

  • JUnitCore是JUnit提供的类,可让您直接通过控制台运行并显示结果。

  • 针对您的HelloWorldTestCase分别调用Java编译和解释。

  • 如果您更改了参数(例如hello和Hello),那么您当然会看到以下结果:

  • 由于断言失败,未显示第二条消息。

  • 不要浪费您的时间来了解您在HelloWorldTestCase上看到的内容,因为大多数这些组件/注释将在下一节中介绍。

<p>Setting your library inside your JDK extension should be much helper than providing them while program execution. This concept is so relevant of the ClassLoader and the way in which it loads the libraries and at which level the extension directory comes.</p>

通过Eclipse利用JUnit

即使您在通过命令控制台使用JUnit时可能会发现JUnit如此漂亮,但是当您在Eclipse中使用它时,您会怀疑Eclipse可以提供的这些功能肯定会导致您退出控制台而无需还原。

Eclipse IDE不仅提供了一种正确执行JUnit测试用例的方法,而且还为您提供了一个精美的图形用户界面,该界面可以向您指示已运行的测试用例是否成功完成。

下面是需要执行的步骤,这些步骤将引导您从Eclipse IDE中正确使用JUnit。

  • 从JDK扩展中删除JUnit和hamcrest JAR,以便您将引用Eclipse本身提供的那些。
    确保通过调用java -verbose并查找JUnit将其删除。

  • 安装Eclipse IDE,在本教程中,我将使用Eclipse Kepler。

  • 通过使用Eclipse嵌入式Maven快速入门原型创建最简单的Maven项目。

<p>It supposed to be familiar with Maven, but for those aren't. Maven is a build tool that can help you generating executable binaries, managing project's dependencies and much more. Maven also provides you a way to create a standard project with a ready-made internal structure.</p>
  • 填写所有必需的信息,groupId,artifactId和包,然后完成。

  • 确保您最近创建的项目没有任何问题或者错误。
    如果显示为是,则仅删除父部分,因为大多数原型都太旧了。

  • 您创建的项目将有一个JUnit库的引用,该库绝对太旧了,需要进行一些改进。
    打开您的pom文件,并将JUnit库的版本更改为4.12,并添加hamcrest maven依赖项。
    您的pom文件应如下所示:

pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.theitroad</groupId>
	<artifactId>HelloWorldTestCase</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>HelloWorldTestCase</name>
	<url>https://maven.apache.org</url>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
		<dependency>
			<groupId>org.hamcrest</groupId>
			<artifactId>hamcrest-core</artifactId>
			<version>1.3</version>
		</dependency>
	</dependencies>
</project>
  • 对项目进行常规清理,以便已安装这些引用的JAR。

  • 请注意,您的项目中有两个软件包。
    一个代表您的程序,另一个代表测试用例。
    我建议暂时将其高度删除,以便您可以重新启动。

<p>Don't give com.theitroad package that's located beneath src/test/java any care as we will have a separate section that shows you how can integrate your JUnit TestCases with Maven build tool.</p>
  • 创建在项目内部之前和包下方创建的HelloWorldTestCase类。

HelloWorldTestCase.java

package com.theitroad;

import org.junit.Test;
import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@Test
	public void testHelloWorld(){
		//Print a message before assertion
		System.out.println("Before Getting Test Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test Execution ::");
	}

	public static void main(String [] args){
		//JUnitCore is provided by JUnit to run your Tests through console
		org.junit.runner.JUnitCore.runClasses(HelloWorldTestCase.class);
	}
}
  • 编译项目,并确保没有任何错误。

  • 要执行您的项目,您有两个选择:通常像上面对任何具有main方法的普通Java类一样执行它。

  • 使用Eclipse IDE的JUnit工具执行它。

  • 使用第一种方法执行项目将导致您获得以下结果(CTRL + SHIFT + x&j):

  • 使用第二种方法执行该项目会使您获得以下不错的结果(CTRL + SHIFT + x&t:

  • 如上所述,控制台上的上述消息旁边现在有一个指示器可以告诉您TestCase的结果。

JUnit注释; @ Test,@ Before,@ After,@ AfterClass,@ BeforeClass

从现在开始,无需通过基本的命令控制台来使用JUnit,而是花费大量时间准备TestCases并提供一种运行它的方法,直接使用Eclipse并欣赏这些指示器。

这种用法不会考虑定义main方法,因此,您应该在每次要执行测试时使用CTRL + SHIFT + x&t。

在上一节中介绍了所有这些澄清之后,现在是时候深入关注JUnit概念及其各种功能了。

要在类中定义TestCase,应将@Test批注与定义的方法结合使用。
该类和此方法应具有以下规则:

  • 您的类必须定义为public并具有默认构造函数(零参数)。

  • 您的Test方法应该定义为公共方法,不带任何参数并返回void。

JUnit框架遇到@Test注释后,它将创建该类的实例,该实例包含该方法并调用@Test带注释的方法。
这是JUnit的原理及其工作原理。

如果您的Test类有两个用@Test注释的方法,则意味着JUnit将创建两个实例来调用这两个方法,依此类推。
因此,您无法在所有测试方法中重复使用实例变量值。

JUnit框架通过调用其包含的断言来判断某些@Test方法,以确保每个@Test方法都调用了特定的断言以消除任何混淆。
当您得到特定的@Test方法返回成功而没有使用任何断言的结果时,就会发生这种混乱。

不建议在@Test方法中再提供一个断言,这样您就可以准确知道哪个TestCase失败了以及由于什么原因。
但是实际上,JUnit框架本身并未施加任何约束。

您已经了解了如何使用一种@Test方法,但是现在让我们看看如何同时协作一种@Test方法。

HelloWorldTestCase.java

package com.theitroad;

import org.junit.Test;
import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@Test
	public void testHelloWorldOne(){
		//Print a message before assertion
		System.out.println("Before Getting Test #1 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #1 Execution ::");
	}

	@Test
	public void testHelloWorldTwo(){
		//Print a message before assertion
		System.out.println("Before Getting Test #2 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #2 Execution ::");
	}	

}

执行上面的示例将导致以下结果:

执行结果

Before Getting Test #1 Started ::
After Test #1 Execution ::
Before Getting Test #2 Started ::
After Test #2 Execution ::
  • 您可能会注意到,执行了两个TestCases,并通过JUnit的Eclipse指示器绘制了结果。

但是我想为您的测试执行一些之前/之后的执行,是在任何@Test方法之前或者之后执行的,并且在没有任何方面被@Test成功执行的情况下执行的操作。
然后,您应该分别使用@Before和@After。
现在,我们添加添加了@Before和@After注释的方法后,看看它如何工作。

HelloWorldTestCase.java

package com.theitroad;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@Before
	public void beforeExecutingTestCase(){
		System.out.println("Before Executing Test Case :: ");
	}

	@After
	public void afterExecutingTestCase(){
		System.out.println("After Executing Test Case :: ");
	}

	@Test
	public void testHelloWorldOne(){
		//Print a message before assertion
		System.out.println("Before Getting Test #1 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #1 Execution ::");
	}

	@Test
	public void testHelloWorldTwo(){
		//Print a message before assertion
		System.out.println("Before Getting Test #2 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #2 Execution ::");
	}	

}

执行以上操作将引导您进行以下操作:

执行结果

Before Executing Test Case ::
Before Getting Test #1 Started ::
After Test #1 Execution ::
After Executing Test Case ::
Before Executing Test Case ::
Before Getting Test #2 Started ::
After Test #2 Execution ::
After Executing Test Case ::

您可能会注意到@Before方法和@After在每个@Test方法之前和之后的执行情况。

<p>One important note here and it's beware from getting multiple @Before and @After methods defined as no one can tell you what's the order of their execution.</p>

另一方面,如果要在所有@Test方法之前和所有@Test方法之后调用方法,该怎么办?这就是使用@BeforeClass和@AfterClass时得到的。

HelloWorldTestCase.java

package com.theitroad;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@BeforeClass
	public static void beforeExecutingHelloWorldTestCases(){
		System.out.println("Before Executing HelloWorldTestCases :: ");
	}

	@AfterClass
	public static void afterExecutingHelloWorldTestCases(){
		System.out.println("After Executing HelloWorldTestCases :: ");
	}

	@Before
	public void beforeExecutingTestCase(){
		System.out.println("Before Executing Test Case :: ");
	}

	@After
	public void afterExecutingTestCase(){
		System.out.println("After Executing Test Case :: ");
	}

	@Test
	public void testHelloWorldOne(){
		//Print a message before assertion
		System.out.println("Before Getting Test #1 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #1 Execution ::");
	}

	@Test
	public void testHelloWorldTwo(){
		//Print a message before assertion
		System.out.println("Before Getting Test #2 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #2 Execution ::");
	}	

}

以上执行结果为:

执行结果

Before Executing HelloWorldTestCases ::
Before Executing Test Case ::
Before Getting Test #1 Started ::
After Test #1 Execution ::
After Executing Test Case ::
Before Executing Test Case ::
Before Getting Test #2 Started ::
After Test #2 Execution ::
After Executing Test Case ::
After Executing HelloWorldTestCases ::

您可能会注意到,@ BeforeClass和@AfterClass方法在所有@Test方法之前和之后都被调用过一次。

超时与预期

您已经了解了如何将@ Test,@ Before,@ After,@ BeforeClass和@AfterClass等注释与JUnit框架一起使用。
本节将重点讨论如何在失败之前的特定时间内等待测试用例,以及如何处理某些异常。

尽管不能使用特定的注释,但是@Test现在将具有可用于解析这些功能的其他属性。

为@Test注释传递了超时参数,以指定此方法在获取失败之前将等待的毫秒数。
当您要检查和验证给定代码的性能时,这实际上非常有用。

以下示例清楚地说明了如何使用它:

HelloWorldTestCase.java

package com.theitroad;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@BeforeClass
	public static void beforeExecutingHelloWorldTestCases(){
		System.out.println("Before Executing HelloWorldTestCases :: ");
	}

	@AfterClass
	public static void afterExecutingHelloWorldTestCases(){
		System.out.println("After Executing HelloWorldTestCases :: ");
	}

	@Before
	public void beforeExecutingTestCase(){
		System.out.println("Before Executing Test Case :: ");
	}

	@After
	public void afterExecutingTestCase(){
		System.out.println("After Executing Test Case :: ");
	}

	@Test
	public void testHelloWorldOne(){
		//Print a message before assertion
		System.out.println("Before Getting Test #1 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #1 Execution ::");
	}

	@Test
	public void testHelloWorldTwo(){
		//Print a message before assertion
		System.out.println("Before Getting Test #2 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #2 Execution ::");
	}	

	@Test(timeout=1)
	public void testWaitForATime(){
		//Print statement
		System.out.println("@Test Will Be Waiting For 100 Milliseconds :: Supposed Task Should Be Done Before Timeout");

		//Some adhoc processing
		for(int i = 0 ; i < 1000 ; i++){
			Math.random();
		}
		//Print statement
		System.out.println("Task Done Before Timeout :: This Test Has Finished Sucessfully ::");
	}

}

以下是上面列出的代码的详细信息:

  • 通过提供timeout参数,现在它可以执行不会超过特定时间量(以毫秒为单位)的Test方法。

  • 如果执行此方法超过了特定的毫秒数,即使该方法中的某些断言没有导致失败,它也将失败。

  • 如果执行此方法的时间不超过特定的毫秒数,则在没有因某些断言而导致失败的情况下,它将成功通过。

  • 将超时设置为1000将导致此测试成功完成,而将其设置为10000则不会成功。

这全部与超时有关,以及如何与@Test批注一起指定超时。
但是,如果您希望某些Test方法在未引发某些异常的情况下失败,或者反之亦然,该怎么办?在某些例外情况下应视为成功。
这实际上就是您使用预期参数时要使用的内容。
下面的示例向您显示一个Test方法,该方法生成一个数字并查找%2的结果以指定该数字是否为偶数。
如果是偶数,将初始化一个String变量,否则不初始化。

HelloWorldTestCase.java

package com.theitroad;

import java.io.File;
import java.io.UnsupportedEncodingException;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.*;

public class HelloWorldTestCase {

	@BeforeClass
	public static void beforeExecutingHelloWorldTestCases(){
		System.out.println("Before Executing HelloWorldTestCases :: ");
	}

	@AfterClass
	public static void afterExecutingHelloWorldTestCases(){
		System.out.println("After Executing HelloWorldTestCases :: ");
	}

	@Before
	public void beforeExecutingTestCase(){
		System.out.println("Before Executing Test Case :: ");
	}

	@After
	public void afterExecutingTestCase(){
		System.out.println("After Executing Test Case :: ");
	}

	@Test
	public void testHelloWorldOne(){
		//Print a message before assertion
		System.out.println("Before Getting Test #1 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #1 Execution ::");
	}

	@Test
	public void testHelloWorldTwo(){
		//Print a message before assertion
		System.out.println("Before Getting Test #2 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #2 Execution ::");
	}	

	@Test(timeout=1)
	public void testWaitForATime(){
		//Print statement
		System.out.println("@Test Will Be Waiting For 100 Milliseconds :: Supposed Task Should Be Done Before Timeout");

		//Some adhoc processing
		for(int i = 0 ; i < 100 ; i++){
			Math.random();
		}
		//Print statement
		System.out.println("Task Done Before Timeout :: This Test Has Finished Sucessfully ::");
	}

	@Test(expected=NullPointerException.class)
	public void testNullPointerExceptionShouldBeRaised() throws UnsupportedEncodingException{
		//Define variable
		String variable = null;
		//Get random number
		double value = Math.random() * 100;

		//Print out value
		System.out.println("Value :: "+(int)value);

		if((int)value % 2 == 0){
			//Specify variable value
			variable = "Even Number";
		}
		//Print out variable content with UTF-8 encoding
		System.out.println("This number is :: "+new String(variable.getBytes(),"UTF-8"));
	}

}

以下是有关上面列出的代码的详细说明:

  • 若要指定Test方法是否应生成某些异常,应在预期参数中提供@Test批注。

  • 如果抛出NullPointerException,则此测试将成功通过。
    否则,即使其断言通过,它也将失败。

运行步者和运行步者类型

您确实在引言和第一个示例中看到了如何使用console命令使用HelloWorldTestCase以及如何通过使用默认常规执行以及使用JUnit的Eclipse内置功能在Eclipse中进行操作。

无论您选择哪种方式运行TestCases;通过使用JUnit命令控制台或者通过Eclipse IDE,应该使用Runner的实现来运行TestCases。

虽然我们使用org.junit.runner.JUnitCore从命令行或者通过使用Eclipse默认类执行(即具有main方法的类)执行我们的TestCases,但是Eclipse使用默认的内置本机图形运行器来执行它们。

下表总结了JUnit使用的大多数运行程序及其各自的目的:

RunnerPurpose
org.junit.runner.JUnitCoreIs a facade for running tests, it supports running JUnit 4 test cases, JUnit 3.8 test cases and mixtures.
SuiteIs a standard runner that allows you to manually build a suite containing tests from many classes.
ParametrizedIs a standard runner that allows you to execute test cases with ability to passing parameters.
CategoriesIs a standard runner enabling test cases tagged with certain category to be included/excluded while execution

现在,让我们看看如何使用和利用每种类型的这些运行器。
您已经了解了如何使用JUnitCore,以便不再进行讨论。

在开始解释不同类型的Runners之前,重要的是首先讨论" @RunWith"的概念,在使用非默认JUnit Runners时会大量使用它。

当使用@RunWith注释类或者扩展使用@RunWith注释的类时,它将使用@RunWith注释所提及的类执行自己的TestCases。

也就是说,如果要使用Parametrized Runner,则应在类声明中定义@RunWith批注,后跟Parametrized.class值。
看下面的例子:

ParametrizedHelloWorldTestCase.java

package com.theitroad;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class ParametrizedHelloWorldTestCase{

	//Define parametrized's properties
	private String expected;
	private String actualValue;

	public ParametrizedHelloWorldTestCase(String expected, String actualValue) throws Throwable {
		//Set values
		this.expected = expected;
		this.actualValue = actualValue;
	}

	@Parameters
	public static Collection<Object []> dataParameters(){
		return Arrays.asList(new Object[][] {
				{"Hello World","Hello World"} ,
				{"Hi World","HI World"}
				});
	}

	@Test
	public void testConcatenation(){
		//Print out instance reference and values passed
		System.out.println("Instance Reference :: "+this+" :: Expected :: "+this.expected +" :: ActualValue :: "+this.actualValue);
		assertEquals(this.expected, this.actualValue);
	}

}

在查看执行结果之前,以下是上面列出的代码的详细说明:

  • 每当您要使用非默认Runner执行TestCases时,都使用@RunWith。
    Parametrized是一个专门的Runner,因此,您应该定义@RunWith和Paramerized.class,以告诉JUnit框架您将使用Parametrized Runner执行此Test类。

  • 要使用Parametrized Runner,您应该定义以下内容:定义将用于传递参数的属性。
    这是通过提供期望值和实际值实例变量来完成的。

  • 定义用于填充测试数据的方法;此方法应该用@Parameters注释,它也应该是静态的,并返回Collection <Object []>类型。

  • 定义一个参数化的构造函数,该函数将用于分配通过的测试数据。

现在,让我们看看上面执行这样的程序所产生的输出:

在结果上注意以下几点非常重要:

  • 由于您有两对测试数据{" Hello World"," Hello World"}"和{" Hi World"," HI World"}",并且由于@Test与实例创建一起被调用,因此,您应该注意到我们有两个带有两对参数的不同实例。
    注意对象引用。

  • 由于第一个assertEquals有望正确完成,而第二个`assertEquals'则不能正确完成,因此预期具有以下指标。

  • 两对参数将初始化两个不同的实例,因此将初始化两个不同的TestCases。
    如果一个失败,整个TestCase也将失败。

  • 创建这些类并传递相应的参数是JUnit框架的职责。

  • 创建的实例数等于在@Parametrized方法中定义的项目数。

现在,让我们来看第二个特殊的亚军;这是套房。
如果要聚合测试类,请确保必须使用Suite Runner。
以下打击示例向您展示了如何利用Suite Runner:

SuiteHelloWorldTestCase.java

package com.theitroad;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({HelloWorldTestCase.class,ParametrizedHelloWorldTestCase.class})
public class SuiteHelloWorldTestCase {

}

以下是上面列出的代码的详细说明:

  • 即使您没有提到任何@Test方法,但使用CTRL + SHIFT + x&t都会导致您分别在HelloWorldTestCaseParametrizedHelloWorldTestCase中执行所有TestCases。

  • 使用@Suite和@SuiteClasses可以将给定的类声明为Test类的持有者,以及应执行哪些Test类以及将其移到哪个优先级。

  • 如果在@SuiteClasses中更改了提到Test类的优先级,则执行的优先级也将随之更改。

  • 套件类将仅保留类的声明注释,因此将保持空白。
    也就是说,如果您在Suite类中定义@Test方法,则将不再执行该方法。

  • 执行以上代码将导致您获得以下结果。
    下面的结果向您显示了从一个称为now套件的Test类执行的所有@Test方法。

如果您正确理解Suite Runner,则应该顺利使用类别1,而不会出现任何问题。
您要做的只是对Test类进行分类,并指定要在执行中包括/排除的Test类。

<p>It's so important to know that Category Runner is a special type of Suite, where you should define your Test classes just like you did with Suite special Runner and do the inclusion and exclusion.</p>

下面是完整的示例,下面是使用类别运行器的必要步骤:

  • 创建您的类别;这是一组接口。

必填java

package com.theitroad.categories;

public interface Mandatory {

}

Optional.java

package com.theitroad.categories;

public interface Optional {

}
  • 用想要的类别注释您的Test类。
    例如,创建的" ParametrizedHelloWorldTestCase"将被注释为强制性的,而" HelloWorldTestCase"是可选的。

ParametrizedHelloWorldTestCase.java

package com.theitroad;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.theitroad.categories.Mandatory;

@RunWith(Parameterized.class)
@Category(Mandatory.class)
public class ParametrizedHelloWorldTestCase{

	//Define parametrized's properties
	private String expected;
	private String actualValue;

	public ParametrizedHelloWorldTestCase(String expected, String actualValue) throws Throwable {
		//Set values
		this.expected = expected;
		this.actualValue = actualValue;
	}

	@Parameters
	public static Collection<Object []> dataParameters(){
		return Arrays.asList(new Object[][] {
				{"Hello World","Hello World"} ,
				{"Hi World","Hi World"}
				});
	}

	@Test
	public void testConcatenation(){
		//Print out instance reference and values passed
		System.out.println("Instance Reference :: "+this+" :: Expected :: "+this.expected +" :: ActualValue :: "+this.actualValue);
		assertEquals(this.expected, this.actualValue);
	}

}

HelloWorldTestCase.java

package com.theitroad;

import static org.junit.Assert.assertEquals;

import java.io.UnsupportedEncodingException;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import com.theitroad.categories.Optional;

@Category(Optional.class)
public class HelloWorldTestCase {

	@BeforeClass
	public static void beforeExecutingHelloWorldTestCases(){
		System.out.println("Before Executing HelloWorldTestCases :: ");
	}

	@AfterClass
	public static void afterExecutingHelloWorldTestCases(){
		System.out.println("After Executing HelloWorldTestCases :: ");
	}

	@Before
	public void beforeExecutingTestCase(){
		System.out.println("Before Executing Test Case :: ");
	}

	@After
	public void afterExecutingTestCase(){
		System.out.println("After Executing Test Case :: ");
	}

	@Test
	public void testHelloWorldOne(){
		//Print a message before assertion
		System.out.println("Before Getting Test #1 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #1 Execution ::");
	}

	@Test
	public void testHelloWorldTwo(){
		//Print a message before assertion
		System.out.println("Before Getting Test #2 Started ::");

		//If assertion goes wrong, the execution of Test would fail
		assertEquals("Would Say Hello","Hello","Hello");

		//Print a message after execution of Test; if assertion goes well, this message would be shown
		System.out.println("After Test #2 Execution ::");
	}	

	@Test(timeout=1)
	public void testWaitForATime(){
		//Print statement
		System.out.println("@Test Will Be Waiting For 100 Milliseconds :: Supposed Task Should Be Done Before Timeout");

		//Some adhoc processing
		for(int i = 0 ; i < 100 ; i++){
			Math.random();
		}
		//Print statement
		System.out.println("Task Done Before Timeout :: This Test Has Finished Sucessfully ::");
	}

	@Test(expected=NullPointerException.class)
	public void testNullPointerExceptionShouldBeRaised() throws UnsupportedEncodingException{
		//Define variable
		String variable = null;
		//Get random number
		double value = Math.random() * 100;

		assertEquals("Would Say Hello","Hello","Hello");

		//Print out value
		System.out.println("Value :: "+(int)value);

		if((int)value % 2 == 0){
			//Specify variable value
			variable = "Even Number";
		}
		//Print out variable content with UTF-8 encoding
		System.out.println("This number is :: "+new String(variable.getBytes(),"UTF-8"));
	}

}
  • 为您创建的Test类提供类别不会对其进行任何更改,但会使它们成为Category Runner的目标。
    一旦对它们进行了CTRL + SHIFT + x&t的调用,您的Test类就可以继续工作。

CategorizedHelloWorldTestCase.java

package com.theitroad;

import org.junit.experimental.categories.Categories;
import org.junit.experimental.categories.Categories.ExcludeCategory;
import org.junit.experimental.categories.Categories.IncludeCategory;
import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;

import com.theitroad.categories.Mandatory;
import com.theitroad.categories.Optional;

@RunWith(Categories.class)
@IncludeCategory(Mandatory.class)
@ExcludeCategory(Optional.class)
@SuiteClasses({ParametrizedHelloWorldTestCase.class,HelloWorldTestCase.class})
public class CategorizeHelloWorldTestCase {

}

以下是上面列出的代码的详细说明:

  • 由于Category Runner是套件的一种特殊类型,因此您应该使用@RunWith,但是这次使用Category.class值。

  • @SuiteClasses将保存所有目标测试类。

  • @IncludeCategory和@ExcludeCategory分别用于包含和排除Test类。

  • 执行上面实施的Category Runner会导致您获得以下结果:

  • @IncludeCategory在其括号中提到了" ParametrizedHelloWorldTestCase",然后它将执行。

  • 由于@ExecludeCategory在其括号内提到了HelloWorldTestCase,因此它将不会执行。

断言和断言类型

您确实在@Test方法中使用assert(*)之前就已经看到过,并且根据最佳实践,每个@Test方法都应包括一个assert(*)静态方法,以便JUnit框架可以验证此Test用例并使确保它已通过或者失败。

我们所有的示例都使用了" assertEquals",它被称为可以使用的一种断言。
在此提供此部分以帮助您查看所有类型的断言,以便在@Test方法中利用所需的内容。

下表显示了可以使用的最多断言列表:

Assert MethodWhat's It Used For
assertArrayEquals("message";,A,B)Asserts the equality of the A and B arrays
assertEquals("message";,A,B)Asserts the equality of objects A and B.This assert will actually invokes the equals() method on the first object against second.
assertSame("message";,A,B)Asserts that the A and B objects have the same value.
assertTrue("message";,A)Asserts that A condition is evaluated to true
assertNotNull("message";,A)Asserts that A isn't null

在讨论这些断言以及在使用JUnit框架时如何使用它们并提供它们各自的示例之前,请先进行讨论。
重要的是要了解它们遵循的模式。

所有断言方法都遵循以下规则:提供其参数时,消息,预期结果。
也就是说,传递断言方法的参数应符合该规则。

<p>You should pass the message that assertions would throw once it's failed followed with the expected value that the assert would act upon and the result (Result of your equation or actual value) that will expected value be compared with.</p>

还有一个Delta参数,当您使用双精度参数时,有时会使用某些断言类型,例如assertEquals。
此Delta值用于确定您的实际值(方程式的结果)是否在期望值–增量和期望值+增量之内。

现在,以下示例在一个完整的演示中向您展示了所有上述提及的断言:

AssertionTypesTestCase.java

package com.theitroad;

import org.junit.Test;

public class AssertionTypesTestCases {
	@Test
	public void assertEqualArrays(){

		//Declare first array
		int [] arr1 = {1,2,3,4};

		//Declare second array
		int [] arr2 = {1,2,3,4};

		//Assert
		org.junit.Assert.assertArrayEquals("Arrays Aren't Equal ::", arr1, arr2);
	}

	@Test
	public void assertNoEqualArrays(){
		//Declare first array
		int [] arr1 = {1,2,3,4};

		//Declare second array
		int [] arr2 = {1,2,3,5};

		//Assert
		org.junit.Assert.assertArrayEquals("Arrays Aren't Equal ::", arr1, arr2);
	}	

	@Test
	public void assertEquals(){
		//Declare first object
		String message1 = "Hi World";

		//Declare second object
		String message2 = "Hi World";

		//Assert
		org.junit.Assert.assertEquals("Objects Aren't Equal ::", message1, message2);
	}

	@Test
	public void assertNotEquals(){
		//Declare first object
		String message1 = "Hi World";

		//Declare second object
		String message2 = "HI World";

		//Assert
		org.junit.Assert.assertEquals("Objects Aren't Equal ::", message1, message2);
	}	

	@Test
	public void assertSame(){
		//Declare first object
		String message1 = "Hi World";

		//Declare second object
		String message2 = message1;

		//Assert
		org.junit.Assert.assertSame("Objects Aren't Same ::", message1, message2);
	}

	@Test
	public void assertNotSame(){
		//Declare first object
		String message1 = "Hi World";

		//Declare second object
		String message2 = "HI World"; //if you make it Hi World, this assertion would pass

		//Assert
		org.junit.Assert.assertSame("Objects Aren't Same ::", message1, message2);
	}

	@Test
	public void assertTrue(){
		//Declare first object
		String message1 = "Hi World";

		//Declare second object
		String message2 = message1;

		//Assert
		org.junit.Assert.assertTrue("Result Isn't True ::", message1.equals(message2));
	}

	@Test
	public void assertNotTrue(){
		//Declare first object
		String message1 = "Hi World";

		//Declare second object
		String message2 = "Hi World";

		//Assert
		org.junit.Assert.assertSame("Result Isn't True ::", message1.equals(message2));
	}	

	@Test
	public void assertNull(){
		//Declare first object
		String message1 = null;

		//Assert
		org.junit.Assert.assertNull("Result Is Null ::", message1);
	}

	@Test
	public void assertNotNull(){
		//Declare first object
		String message1 = "Hi World";

		//Assert
		org.junit.Assert.assertNull("Result Isn't True ::", message1);
	}		

}

这是执行结果的下方,因为其中一半失败,因为它们各自的断言也失败了,最终将导致整个Test类失败。

请注意,所有失败的断言都会导致显示错误消息。
该消息是通过assert(*)本身提供的。

关于Hamcrest库 的一点点

您确实在上面看到了多少Assert(*)方法可以帮助您判断给定的方程式并对其进行断言,最终构成您的测试用例。
这些断言不仅仅是JUnit本身提供的断言。
Hamcrest库还对JUnit进行了扩展,并为您提供了另一套(称为匹配器)

本节将重点介绍最常用的方法,并为您提供可以利用它们的方法。
下表下方列出了其中的大多数:

MatcherDescription
containsStringTests whether a given string contains certain string
startWithTests whether a given string starts with a certain string
endsWithTests whether a given string ends with certain string
equalToIgnoringCaseTests whether a given string equals another one, ignoring case
equalToIgniringWhiteSpaceTests whether a given string equals another one, ignoring white spaces
closeToTests whether a given number is close to another number
greaterThanTests whether a given number is greater than another number
greaterThanOrEqualTests whether a given number is greater than or equal to another number
lessThan, lessThanOrEqualTests whether a given number is less than another number or if it is less than or equal another number; respectively
hasItem, hasItemsTests whether a given object(s) is presence in a collection
hasEntry, hasKey, hasValueTests whether a given entry <Key,Value>, key or value is presence in a Map
hasPropertyTests whether a given instance has a given property
notNullValue, nullValueTests whether a given reference is referring to null or not
sameInstanceTests whether a given two instances are referring the same object
instanceOf, isCompatibleTypeTests whether a given objects are of compatible type (are instances of another one)
notTests negation of a given matcher
anyOfCompose set of matchers with OR condition
allOfCompose set of matchers with AND condition

下面是提供的示例,该示例将使用所有这些匹配器:

HamcrestTestCases.java

package com.theitroad;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.hamcrest.object.IsCompatibleType;
import org.junit.Test;

public class HamcrestTestCases {

	@Test
	public void testStartWith(){

		//Create Employee instance
		Employee employee = new Employee();

		//Using assertThat to evaluate specific matcher
		assertThat(employee.getName(), startsWith("Empty"));
	}

	@Test
	public void testEndsWith(){

		//Create Employee instance
		Employee employee = new Employee();

		//Using assertThat to evaluate specific matcher
		assertThat(employee.getName(), endsWith("Name"));
	}	

	@Test
	public void testContainsWith(){

		//Create Employee instance
		Employee employee = new Employee();

		//Using assertThat to evaluate specific matcher
		assertThat(employee.getName(), containsString("y N"));
	}	

	@Test
	public void testEqualToIgnoringCase(){
		//Create Employee instance
		Employee employee = new Employee();

		//Using assertThat to evaluate specific matcher
		assertThat(employee.getName(), equalToIgnoringCase("eMpTy NaMe"));
	}

	@Test
	public void testEqualToIgnoringWhiteSpace(){
		//Create Employee instance
		Employee employee = new Employee();

		//Using assertThat to evaluate specific matcher
		assertThat(employee.getName(), equalToIgnoringWhiteSpace("    Empty       Name       "));
	}	

	@Test
	public void testhasItem(){
		//Create Employee instance
		Employee employee = new Employee();

		//Create employees list
		List<Employee> employees = new ArrayList<Employee>();

		//Add employee
		employees.add(employee);

		//Using assertThat to evaluate specific matcher
		assertThat(employees,hasItem(employee));
	}	

	@Test
	public void testhasItems(){
		//Create Employee instance
		Employee employee1 = new Employee();

		//Create Employee instance
		Employee employee2 = new Employee();		

		//Create employees list
		List<Employee> employees = new ArrayList<Employee>();

		//Add employee
		employees.add(employee1);

		//Add employee
		employees.add(employee2);

		//Using assertThat to evaluate specific matcher
		assertThat(employees,hasItems(employee1,employee2));
	}	

	@Test
	public void testhasEntry(){
		//Create Employee instance
		Employee employee1 = new Employee();

		employee1.setName("Mohammad");

		//Create a Map
		HashMap<String,Employee> employees = new HashMap<String,Employee>();

		//Add employee 1
		employees.put(employee1.getName(), employee1);

		//Assert
		assertThat(employees, hasEntry(employee1.getName(), employee1));
	}	

	@Test
	public void testhasProperty(){
		//Create Employee instance
		Employee employee = new Employee();

		//Assert
		assertThat(employee, hasProperty("name"));
	}	

	@Test
	public void testNotNull(){
		//Create Employee instance
		Employee employee = new Employee();

		//Assert
		assertThat(employee, notNullValue());
	}	

	@Test
	public void testSameInstance(){
		//Create employee
		Employee employee = new Employee();

		//Declare another employee variable
		Employee employee2 = employee;

		//Assert
		assertThat(employee, sameInstance(employee2));
	}

	@Test
	public void testInstanceOf(){
		//Create employee
		Employee employee = new Employee();

		//Assert
		assertThat(employee, instanceOf(Person.class));
	}	

	@Test
	public void testNotInstanceOf(){
		//Create employee
		Person person = new Person();

		//Assert
		assertThat(person, not(instanceOf(Employee.class)));
	}

	@Test
	public void testIsCompatibleType(){

		//Create isCompatible Matcher
		IsCompatibleType<Person> isPersonCompatible = new IsCompatibleType<Person>(Person.class);

		//Create isCompatible Matcher
		IsCompatibleType<Employee> isEmployeeCompatible = new IsCompatibleType<Employee>(Employee.class);

		//Assert; Employee class definition is compatible (instance) with/of Person
		assertThat(Employee.class,isPersonCompatible);

		//Assert; Person class definition is compatible (instance) with/of Person
		assertThat(Person.class,isPersonCompatible);

		//Assert; Person class definition is not compatible (instance) with/of Employee
		assertThat(Person.class,not(isEmployeeCompatible));
	}

	@Test
	public void testIsNotCompatibleType(){

		//Create isCompatible Matcher
		IsCompatibleType<String> isNotCompatible = new IsCompatibleType<String>(String.class);

		//Assert; employee class definition isn't compatible/instance with/of String
		assertThat(Employee.class,not(isNotCompatible));
	}

	@Test
	public void testAnyOf(){

		//Create isCompatible Matcher
		IsCompatibleType<Person> isPersonCompatible = new IsCompatibleType<Person>(Person.class);

		//Create isCompatible Matcher
		IsCompatibleType<Employee> isEmployeeCompatible = new IsCompatibleType<Employee>(Employee.class);

		//Assert; employee class is instance of Person OR employee
		assertThat(Employee.class,anyOf(isPersonCompatible,isEmployeeCompatible));
	}

	@Test
	public void testAllOf(){

		//Create isCompatible Matcher
		IsCompatibleType<Person> isPersonCompatible = new IsCompatibleType<Person>(Person.class);

		//Create isCompatible Matcher
		IsCompatibleType<Employee> isEmployeeCompatible = new IsCompatibleType<Employee>(Employee.class);

		//Assert; Person class is an instance of Person AND not of Employee
		assertThat(Person.class,allOf(isPersonCompatible,not(isEmployeeCompatible)));
	}					

}

class Person {

	private String type;

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

}

class Employee extends Person{

	private int id = 0;
	private String name = "Empty Name";

	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

}

基本上,JUnit框架并没有为您提供控制Hamcrest库的功能,因此最好将JUnit和Hamcrest两者结合起来以确保您拥有可靠,特定且简单的测试用例。

现在,您可以通过调用CTRL + SHIFT + x + t来执行它,以得到以下结果:

JUnit扩展– JWebUnit

JWebUnit是基于JUnit构建的框架,它提供了一种编写测试用例的方法,以用于Web应用程序测试。
下面的示例向您展示如何将其用于针对最简单的" HelloWorldPortlet"编写测试用例,该" HelloWorldPortlet"是先前为Apache Pluto Portal提供的其中一个教程中编写的。

使用Apache Pluto Portal将使编写JWebUnit案例更加有趣,因为在检查" HelloWorldPortlet"(实际上是Web资源)之前,您应该通过登录屏幕并导航至Portal页面。

实际上,我在示例中做了一些小的修改,这些修改将在此处显示。
进行此修改可确保JWebUnit能够识别所使用的某些组件。

HelloWorldPortlet.java

package com.theitroad.portlet;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class HelloWorldPortlet extends GenericPortlet {
	private static int renderCount = 0;
	private static int actionCount = 0;

	public void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {

		synchronized(this){
			renderCount++;
		}
		response.getWriter().print("<form id='main' action="+response.createActionURL()+">"
				+ "<p>Render has executed "+renderCount+"</p>"
				+ "<p>Action has executed "+actionCount+"</p>"
				+ "<button id='submit' value='submit' type='submit'>Submit</button>"
				+ "</form>");
	}

	public void processAction(ActionRequest actionRequest, ActionResponse actionResponse){
		synchronized(this){
			actionCount++;
		}
	}

  public void init( PortletConfig config ) throws PortletException {
      super.init( config );
  }
}

以下是上面编写的组件的提示:

  • HelloWorldPortlet是一个简单的Portlet资源,它负责显示具有在其上进行提交的功能的简单表单。

  • 访问HelloWorldPortlet门户页面将使渲染计数器增加一。

  • 针对HelloWorldPortlet提交操作按钮将使操作请求增加一。

下面的一系列图像将阐明您必须通过JWebUnit检查的HelloWorldPortlet执行和业务场景。

  • 访问Apache Pluto Portal基本URL https://10.10.90.3:8080/pluto。

  • 确保已修改了tomcat-users.xml,以便允许tomcat用户访问和管理。

  • 输入tomcat作为用户名和密码并提交登录。

  • 导航到Apache Pluto主页。

<p>Portlet isn't a standalone web resource, so that to make it accessible you should embed it inside a Portal page. We have created a HelloWorld Portal page and you're able to do that by following the basic instructions in the referenced Apache Pluto tutorial above.</p>
  • 创建一个HelloWorld Portal页面,并其中添加HelloWorldPortlet

  • 最初,根据HelloWorld Portal页面上的每个请求,将再次渲染HelloWordlPortlet
    如果您再次复制了链接https://10.10.90.3:8080/pluto/portal/HelloWorld并将其键入到同一浏览器中的另一个选项卡上,您将看到渲染已执行2等等。

  • 操作请求是Portlet支持的第二种请求类型。
    Portlet在其生命周期中具有两种不同的请求类型。

  • 为确保收到动作请求,应单击"提交"按钮。
    这还会启动另一个渲染请求,因此您必须看到"渲染已执行2"和"动作已执行1"。

现在,您已经了解了" HelloWorldPortlet"如何针对您对它执行的操作进行工作。
但是,如果您不想直接将其处理到浏览器中并希望通过使用JWebUnit来执行此操作,该怎么办。

表面上,实际上,这种自动执行可能会导致您产生大量的测试用例,这些用例可在每次需要时运行,以确保您所做的修改,完善和重构不会破坏Portlet的基本功能。

下面的示例向您展示了一个测试案例类,可以通过上面的例子来检查这种情况:

HelloWorldPortletTestCase.java

package com.theitroad;

import net.sourceforge.jwebunit.junit.JWebUnit;

import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.JVM)
public class HelloWorldPortletTestCase {

	@Before
	public void prepareBaseURL(){
		//Set base URL that JWebUnit will start from
		JWebUnit.setBaseUrl("https://10.10.90.3:8080/pluto");
	} 

	@Test
	public void testBaseUrl(){
		//Begin a conversation
		JWebUnit.beginAt("/portal");
	}

	@Test
	public void testShowingAndPopulatingLoginScreen(){

		//Assert if login screen is displayed, so that username and password field would be shown

		JWebUnit.assertTextFieldEquals("j_username", "");
		JWebUnit.assertTextFieldEquals("j_password", "");		

		//Populating login screen with the required values
		JWebUnit.setTextField("j_username", "tomcat");
		JWebUnit.setTextField("j_password", "tomcat");

		//Assert populating is done properly
		JWebUnit.assertTextFieldEquals("j_username", "tomcat");
		JWebUnit.assertTextFieldEquals("j_password", "tomcat");

		//Login Into Portal
		JWebUnit.clickButtonWithText("Login");
	}

	@Test
	public void testNavigateIntoHelloWorldPortalPage(){
		//Check if HelloWorld Portal page is exist or not
		JWebUnit.assertLinkPresentWithText("HelloWorld");
	}

	@Test
	public void testDoActualNavigationIntoHelloWorldPortalPage(){
		//Navigate into HelloWorld Portal page
		JWebUnit.clickLinkWithExactText("HelloWorld");

		//Check of the Render has executed message
		JWebUnit.assertTextPresent("Render has executed 1");
	}	

	@Test
	public void testInitiateActionRequestUponHelloWorldPortlet(){
		//Check if the submit button is exist
		JWebUnit.assertButtonPresent("submit");

		//Click submit against HelloWorld Portlet
		JWebUnit.clickButton("submit");
	}

	@Test
	public void testMessagesAfterActionRequestUponHelloWorldPortlet(){
		//Check of the Render has executed message
		JWebUnit.assertTextPresent("Render has executed 2");

		//Check of the Action has executed message
		JWebUnit.assertTextPresent("Action has executed 1");
	}
}

以下是上面列出的代码的详细信息:

  • 我已经将上面通过的功能用例分解为多个测试用例。

  • 如果您不使用@FixMethodOrder(MethodSorters.JVM),这些测试用例将不会按顺序执行。
    正如JUnit所假设的那样,每个@Test案例都彼此依赖,而事实上至少在我们的示例中它们并非如此。

  • 在上面的示例中将正常的浏览器导航作为测试用例时,已经检查了每个功能用例。
    找到基本URL,开始对话,显示并填充登录屏幕,检查是否存在" HelloWorld"门户页面,导航至该页面,检查导航结果,发起动作请求,检查发起的动作请求是否正确完成。

  • @FixMethodOrder自JUnit 4.11发布以来已发布,因此请谨慎使用不同版本的JUnit执行同一应用程序,而不要使用pom文件或者更高版本中提到的版本。

以下是上述测试用例的执行结果;如果您没有得到适当的结果,可以使用JWebUnit.getPageSource(),它会消耗返回HTML。

正如您在上面可能注意到的那样,所有测试用例均已成功通过,这意味着我们可以对浏览器使用常规导航时获得与您看到的相同结果。

实际上,将JWebUnit用于任何Web资源(例如Servlet,JSP,HTML以及导致HTML响应的任何资源),比将其用于Apache Portal Web应用程序要容易得多。
为了确保我们拥有应涵盖的大多数情况,给定的示例位于Apache Pluto Portal中。

JUnit扩展– JSFUnit

即使您可能在Internet上也可以找到JBoss的JSFUnit,但实际上它不是一个简单的框架,您需要对其核心有一些了解才能使它易于使用。

谈论JSF,意味着您拥有一个服务于您的应用程序的Servlet容器。
而且有很多组件需要检查;托管Bean,视图ID,表达式评估等。
所有这些组件都不只是普通的Java类,因此它们需要访问它并轻松指定其值。

在本部分中,您将使用一个容器来确保您的测试用例有效运行,除此之外,您还将学习最令人惊奇的JBoss框架,该框架主要用于此目的,它是Arquillian。

Arquillian是一个庞大的框架,可用于编写遵循Java Enterprise Edition的概念容器测试的真实测试用例。

无需启动和运行某些容器,您可以配置Arquillian以连接所需的任何容器,然后有效地执行测试用例。

为此,我想向您介绍一种确保此处引用的JSF应用程序能够正确执行并获得预期输出的方法,而无需在Tomcat容器上执行它。

通过使用faces-config.xml文件而不是使用JSF批注,对上面的示例进行了稍微的修改。
这样做是为了确保对于那些正在使用JSF 1.x的人来说,微部署的概念是如此清晰。

在开始解释如何使用Arquillian来完成此任务的完整示例之前,首先让我们看一下Arquillian测试用例应具备的资产:

  • @RunWith可以参考Arquillian Test Runner。

  • 用@Deployment注释的公共静态方法。

  • 至少一种使用@Test注释的方法。

还要考虑到:

  • 使用最新版本的Arquillian JSF Unit Integration.Arquillian JSF Unit 2.0.0.Beta2

  • ShrinkWrap API 1.2.2

  • 使用Tomcat 7。

  • 使用JDK 1.6。

首先,让我们快速了解Arquillian框架使用的每个组件:

  • @Deployment:是在容器内运行以生成存档部署(微型部署)的测试的强制方法; Java存档(JAR),Web存档(WAR)和企业存档(EAR)。
    该存档部署是由ShrinkWrap准备的。
<p><a href="https://developer.jboss.org/wiki/ShrinkWrap">ShrinkWrap </a>is a Java API for creating archives (jar, war and ear) in Java. By using ShrinkWrap, you're focusing on your Test Cases, by bringing all required libraries instead of bring all of them. As you Jan see below within the sample, no need to include all libraries within your Archive.</p>
  • @RunWith:前面已经讨论过,因为它用于标记要使用的其他测试运行程序。
    使用Arquillian框架时,必须使用它注释测试用例,以确保其正确执行。

如果您确实还原为上述示例,则应该看到它是如此简单,它包含以下内容:

  • 简单的helloWorld.xhtml网络资源。

  • 简单的faces-config.xml,其中包含一个已定义的托管Bean。

  • 简单的Web XML文件。

  • 简单托管的Bean,其中包含一个称为s1的属性。

  • JSF实现和API库。

  • JSTL库。

除了我们提到使用faces-config.xml而不是使用其注释外,此处将使用相同的示例。

现在,下面看Arquillian Test Class,然后在pom文件中找到所需的库:

ArquillianHelloWorldTestCase.java

package com.theitroad;

import java.io.File;
import java.io.IOException;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.jsfunit.api.InitialPage;
import org.jboss.jsfunit.jsfsession.JSFClientSession;
import org.jboss.jsfunit.jsfsession.JSFServerSession;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class ArquillianHelloWorldTestCase {

	@Deployment
	public static WebArchive createDeployment() {

		//Create a WAR (Micro deployment) with a given name called
		//helloworld.war
		WebArchive webArchive = ShrinkWrap.create(WebArchive.class,
				"helloworld.war");

		//Set web.xml inside created micro WAR
		webArchive.setWebXML(new File("src/main/webapp/WEB-INF/web.xml"));

		//Add classes
		webArchive.addPackage("com.theitroad.jsf.helloworld");

		//Add web resource
		webArchive.addAsWebResource(new File("src/main/webapp","helloWorld.xhtml"));

		//Add faces-config.xml as a web-inf resource
		webArchive.addAsWebInfResource(new File("src/main/webapp/WEB-INF/faces-config.xml"),"faces-config.xml");

		//Add required libraries
		webArchive.addAsLibrary(new File("C:/Users/mohammad.amr/.m2/repository/com/sun/faces/jsf-impl/2.1.13/jsf-impl-2.1.13.jar"));
		webArchive.addAsLibrary(new File("C:/Users/mohammad.amr/.m2/repository/com/sun/faces/jsf-api/2.1.13/jsf-api-2.1.13.jar"));
		webArchive.addAsLibrary(new File("C:/Users/mohammad.amr/.m2/repository/jstl/jstl/1.2/jstl-1.2.jar"));

		//return the deployment
		return webArchive;
	}

	@Test
	@InitialPage("/faces/helloWorld.xhtml")
	public void testInitialPage(JSFServerSession server, JSFClientSession client)
			throws IOException {

		//Test navigation to initial viewID
		Assert.assertEquals("/helloWorld.xhtml", server.getCurrentViewID());

	}

	@Test
	@InitialPage("/faces/helloWorld.xhtml")
	public void testHelloWorldBeanNotNull(JSFServerSession server, JSFClientSession client){

		//Test if Managed Bean is null
		Assert.assertNotNull(server.getFacesContext().getExternalContext().getSessionMap().get("helloWorld"));
	}

	@Test
	@InitialPage("/faces/helloWorld.xhtml")
	public void testS1Value(JSFServerSession server, JSFClientSession client){

		//Test if Managed Bean is null
		Assert.assertEquals("Hello World!!",server.getManagedBeanValue("#{helloWorld.s1}"));
	}
}

pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.theitroad</groupId>
	<artifactId>HelloWorldTestCase</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>HelloWorldTestCase</name>
	<url>https://maven.apache.org</url>
	<plugin>
		<artifactId>maven-surefire-plugin</artifactId>
		<version>2.17</version>
	</plugin>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<!-- HTML Unit library -->
		<dependency>
			<groupId>net.sourceforge.htmlunit</groupId>
			<artifactId>htmlunit</artifactId>
			<version>2.8</version>
		</dependency>
		<!-- JUnit Library -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
		<!-- Hamcrest Library -->
		<dependency>
			<groupId>org.hamcrest</groupId>
			<artifactId>hamcrest-core</artifactId>
			<version>1.3</version>
		</dependency>
		<!-- JWebUnit Library -->
		<dependency>
			<groupId>net.sourceforge.jwebunit</groupId>
			<artifactId>jwebunit-htmlunit-plugin</artifactId>
			<version>3.0</version>
		</dependency>
		<!-- Arquillian JUnit container library -->
		<dependency>
			<groupId>org.jboss.arquillian.junit</groupId>
			<artifactId>arquillian-junit-container</artifactId>
			<version>1.1.8.Final</version>
		</dependency>
		<!-- Arquillian Tomcat Container that's used for Arquillian.xml -->
		<dependency>
			<groupId>org.jboss.arquillian.container</groupId>
			<artifactId>arquillian-tomcat-remote-7</artifactId>
			<version>1.0.0.CR7</version>
		</dependency>
		<!-- JSF Implementation And API -->
		<dependency>
			<groupId>com.sun.faces</groupId>
			<artifactId>jsf-api</artifactId>
			<version>2.1.13</version>
		</dependency>
		<dependency>
			<groupId>com.sun.faces</groupId>
			<artifactId>jsf-impl</artifactId>
			<version>2.1.13</version>
		</dependency>
		<!-- Servlet API -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<!-- JSTL API -->
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<!-- Arquillian JUnit Library -->
		<dependency>
			<groupId>org.jboss.jsfunit</groupId>
			<artifactId>jsfunit-arquillian</artifactId>
			<version>2.0.0.Beta2</version>
		</dependency>
		<!-- Arquillian JUnit Core library -->
		<dependency>
			<groupId>org.jboss.jsfunit</groupId>
			<artifactId>jboss-jsfunit-core</artifactId>
			<version>2.0.0.Beta2</version>
		</dependency>
		<!-- ShrinkWrap Libraries -->
		<dependency>
			<groupId>org.jboss.shrinkwrap</groupId>
			<artifactId>shrinkwrap-api</artifactId>
			<version>1.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.shrinkwrap.resolver</groupId>
			<artifactId>shrinkwrap-resolver-api</artifactId>
			<version>2.2.0-beta-2</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.shrinkwrap.resolver</groupId>
			<artifactId>shrinkwrap-resolver-impl-maven</artifactId>
			<version>2.2.0-beta-2</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.shrinkwrap.descriptors</groupId>
			<artifactId>shrinkwrap-descriptors-api</artifactId>
			<version>1.0.0-beta-1</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.shrinkwrap.descriptors</groupId>
			<artifactId>shrinkwrap-descriptors-impl</artifactId>
			<version>1.0.0-beta-1</version>
		</dependency>
	</dependencies>
</project>

以下是上面列出的代码的详细说明:

  • 您已经创建了一个名为" ArquillianHelloWorldTestCase.java"的测试类。

  • 此类包含一个强制性的@Deployment方法,因为其测试用例需要一个容器(Servlet容器)。

  • @Deployment方法已返回Web存档。

  • 返回的Web存档包含一个Web XML资源,一个软件包,一个Web资源,一个" faces-config.xml"和三个必需的库; JSF实现和JSTL。
    这些资源已经明确提及,因此@Deployment完成执行后,Web存档将相应地包含这些资源。

  • 如果您未为Web存档绑定名称,则将通过ShrinkWrap实现将其与给定名称随机绑定。
    部署名称为" helloworld.war"。

  • @ Deployment将与您的(运行Tomcat 7)通信,以便将该部署作为helloworld.war安装在此处。

  • 成功发布部署后,将开始执行"测试用例"。

  • 所有测试用例方法都提到了两个参数: Arquillian框架会自动注入JSFServerSession和JSFClientSession。

  • 所有测试用例方法均假定将请求初始页面,并在其上进行测试用例为" /faces/helloWorld.xhtml",该页面使用" facelets"实现来呈现UI组件。

  • 在这些方法中传递断言将以绿色标记它们通过。
    否则,失败及其信息将在那里。

  • TestInitialPage将检查呈现的JSF视图是否具有/helloWorld.xhtml作为视图ID。

  • TestHelloWorldBeanNotNull将检查JSF框架是否注入了HelloWorld bean。

  • TestS1Value将检查属性" s1"的值是否等于Hello World!或者不。

为了确保您能够运行上述示例,请检查以下内容:

  • 您的Tomcat 7已启动并正在运行。

  • 您的源路径或者资源(src/main/resources)提到了Arquillian容器配置" arquillian.xml",它将使用JMX连接Tomcat。

arquillian.xml

<arquillian xmlns="https://jboss.org/schema/arquillian"
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
      https://jboss.org/schema/arquillian
      https://jboss.org/schema/arquillian/arquillian_1_0.xsd">
  <container qualifier="tomcat-remote-7" default="true">
      <configuration>
          <property name="user">tomcat</property>
          <property name="pass">tomcat</property>
      </configuration>
  </container>
</arquillian>
  • 确保您的Tomcat配置已允许与Tomcat进行JMX连接。
    这可以通过在catalina.bat中的JAVA_OPTS变量中添加以下行来完成

新的JAVA_OPTS

set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG% -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
  • 允许Tomcat用户具有管理脚本角色,因为该角色将用于执行部署命令。

tomcat-users.xml

<tomcat-users>
<role rolename="tomcat"
<role rolename="manager-gui"
<role rolename="manager-script"
<user username="tomcat" password="tomcat" roles="tomcat,manager-gui,manager-script"
</tomcat-users>

如果通过调用CTRL + SHIFT + x + t来执行此示例,则会看到:

从上面的示例执行中可以看到,所有案例均已成功执行并通过,而部署和测试案例检查过程已通过Arquillian JSF Unit框架完成。

一旦执行了测试用例并显示了执行结果,便从Tomcat webapps目录中删除了部署。

<p>Sometime this removal wasn't safe, so the exploded folder of your deployment is still there while the WAR is removed. There, you need to stop your Tomcat and remove it by yourself or your make sure using context.xml with antiJARLocking facility.</p>

Maven和JUnit

正如大多数开发人员所知,Maven是一个构建工具,用于构建,编译,打包和管理应用程序的依赖项。
即使它是一个构建和依赖工具,它也有助于提供一种通过内置生命周期执行测试用例的方法。

通过使用mvn test,您可以驱动Maven工具执行位于src/test/java下面的测试用例。

为此,我已经复制了上面创建的所有测试用例,并进行了一些较小的修改,这些修改会使这些测试用例在Maven运行时有效运行。
这些轻微的修改是:

  • 添加maven-surefire-report-plugin,该报告将用于报告测试用例执行的所有结果。

  • 增加HelloWorldTestCase.java中的超时时间。

  • 每次调用此方法时,在相同的上述类中修改testNullPointerExceptionShouldBeRaised以将其抛出。

  • 在src/test/java下创建一个新程序包,并将所有测试类复制到其中。

该应用程序的结构如下所示:

由于我们拥有的测试用例不同,并且已经使用了Apache 7和Apache Pluto服务器,因此它们应该启动并运行以正确执行这些测试用例;我已经从Eclipse本身为Apache 7修改了Tomcat端口。

一旦执行了mvn test,您应该会看到您的测试用例已被执行,结果将显示在控制台和Target文件夹" target/surefire-reports"下。

<p>Before executing your Test Cases, make sure you have your Apache Pluto and Apache Tomcat 7 up and running so that Arquillian and Portlet Test Cases get executed properly.</p>

您得到的执行结果将是:

您可能会注意到,Maven已经执行了其测试阶段,并成功执行了在src/test/java下定义的所有测试用例。

这不是您可能确切知道测试用例会发生什么的方式。
除了控制台消息外,您还可以访问target/surefire-reports文件夹,该文件夹将包含一组文件,这些文件向您显示测试用例产生的执行输出。