JUnit 是一个简单易用的单元测试框架。随着敏捷开发和TDD的风行,它的重要性也日益显现。
介绍:
1、Test接口包含实现类TestSuite和实现抽象类TestCase,同时TestSuite拥有对Test的集合引用(Composite模式)。如图所示:
2、包含两类测试:TestCase和TestSuite。TestCase一个测试用例,而多个测试用例可以组合成一个TestSuite测试套件。
3、TestCase在实现Test接口的同时也实现了Assert断言接口,Assert这个接口中提供了许多方便断言判断的静态方法,如Assert.assertEquals(), Assert.assertNotNull(), Assert.assertTrue()等,这些在我们的测试用例都可以方便应用。
4、TestCase提供了setUp()和tearDown()方法。setUp()在测试用例执行之前调用,tearDown()在测试用例执行之后调用,TestCase中默认空实现,我们可以在setUp()中执行一些例如打开文件,建立数据库连接的操作,而在tearDown()中执行一些相应的如关闭文件,关闭数据库连接的操作。
使用:
1、编写我们自己的测试用例,需要继承TestCase,我们需要运行的测试方法必须以test开头。
2、为了实现自动化测试,我们可能需要同时运行多个测试用例,这时候我们可以借助TestSuite来实现,有两种方式:
a. 继承自TestSuite,举例如下:
public class AllImportsTests extends TestSuite {
public static Test suite () {
TestSuite suite = new TestSuite ("Test for pet.test");
suite.addTestSuite (ImportATest.class );
suite.addTestSuite (ImportBTest.class );
return suite;
}
}
b. 继承自TestCase,举例如下:
public class AllGetListTests extends TestCase {
public static Test suite () {
TestSuite suite = new TestSuite ("Test for pet.test");
suite.addTestSuite (NewATest.class );
suite.addTestSuite (NewBTest.class );
return suite;
}
public void testAllGetList(){
TestRunner .run (suite ());
}
}
衍生:
通过JUnit衍生了用于性能测试的JUnitPerf ,用于数据库隔离测试的DBUnit 等。
1、JUnitPerf:
JUnitPerf 提供了两个测试基类,LoadTest 和TimedTest, 这两种类型都基于 Decorator 设计模式并利用 JUnit 的 suite
机制。LoadTest 和计时器一起运行,它通过运行所需的次数(时间间隔由配置的计时器控制),在一个特定的测试用例上创建一个人工负载。TimedTest
为测试样例创建一个(时间)上限 —— 如果超过这个时间,那么测试失败。
LoadTest的用法:
//自定义测试类
public class MyTest extends TestCase {
Logger log = Logger.getLogger(MyTest.class );
public MyTest(String name){
super(name);
}
public void testCircle () throws java.lang.Exception{
int len = 1000;
for(int i = 0; i < len; i++){
log.info("Number: " + i);
}
Assert.assertTrue(true);
}
}
int userCount = 10; //并发访问数
int delaySeconds = 100; //延迟时间,单位毫秒
int repeatCount = 10; //重复次数
Test myTestCase = new MyTest("testCircle");
1. 并发测试
Test normalTest = new LoadTest(myTestCase, userCount);
2. 延时测试
Timer timer = new ConstantTimer(delaySeconds);
//double seed = 0.001;
//Timer timer = new RandomTimer(delaySeconds, seed);
Test delayTest = new LoadTest(myTestCase, timer);
3. 重复测试
Timer timer = new ConstantTimer(delaySeconds);
Test decorTestCase = new RepeatedTest(myTestCase, repeatCount);
Test repeatTest = new LoadTest(decorTestCase, userCount, timer);
4. 本地线程测试 (?)
Test factory1 = new TestFactory(MyTestCase.class);
Test localTest1 = new LoadTest(factory1, userCount);
Test factory2 = new TestMethodFactory(MyTest.class, "testCircle");
Test loadTest2 = new LoadTest(factory2, userCount);
TimedTest的用法:
long timeElapsed = 2000;
//不论是否超时都会运行完程序,是否失败由运行时间决定。
Test timedTest = new TimedTest(testCase, timeElapsed);
//不等待方法完成,如果超时就测试失败退出程序。
Test timedTest = new TimedTest(testCase, timeElapsed, false);
代码示例:
/**
* 时限测试
* @author xiaowei
*
*/
public class MyTimedTest {
public static Test suite(){
long timeElapsed = 2000;
Test testCase = new MyTest("testCircle");
/**
* 创建一个计时测试,它等待MyTest.testCircle方法完成之后
* 如果耗时超过2秒就测试失败
* create a timed test that waits for the completion of the
* MyTest.testCircle method and
* then fails if the elapsed time exceeded 2 second
*/
Test timedTest = new TimedTest(testCase, timeElapsed);
/**
* 创建一个计时测试,它当MyTest.testCircle方法执行时计算
* 如果已经耗时超过2秒就立即结束,测试失败
* create a timed test that fails immediately when the elapsed time
* of the MyTest.testCircle test method exceeds 2 second
* Test timedTest = new TimedTest(testCase, timeElapsed, false);
*/
return timedTest;
}
public static void main(String[] args){
TestRunner.run(MyTimedTest.suite());
}
}
/**
* 负载测试
* @author xiaowei
*
*/
public class MyLoadTest {
public static Test normalSuite(){
/**
* 创建一个装载测试,它利用10个并发用户分别访问MyTest.testCircle方法,
* 并且所有用户都是同时开始
* create a load test of 10 concurrent users with each user
* running the MyTest.testCircle method once
* and all users starting simultaneously
*/
int users = 10;
Test testCase = new MyTest("testCircle");
Test loadTest = new LoadTest(testCase, users);
return loadTest;
}
public static Test delaySuite(){
/**
* 创建一个装载测试,它利用10个并发用户分别访问MyTest.testCircle方法,
* 并且这些用户访问间隔为0.1秒
* create a load test of 10 concurrent users with each user
* running the MyTest.testCircle method once
* and with a 0.1 second delay between the addition of users
*/
int users = 10;
int delay = 100;
Timer timer = new ConstantTimer(delay);
// double seed = 0.001;
// Timer randomTimer = new RandomTimer(delay, seed);
Test testCase = new MyTest("testCircle");
Test loadTest = new LoadTest(testCase, users, timer);
return loadTest;
}
public static Test repeatedSuite(){
/**
* 创建一个装载测试,它利用10个并发用户分别访问MyTest.testCircle方法10次
* create a load test of 10 concurrent users with each user running
* the MyTest.testCircle method for 20 iterations
*/
int users = 10;
int iterations = 5;
Timer timer = new ConstantTimer(10);
Test testCase = new MyTest("testCircle");
Test repeatedTest = new RepeatedTest(testCase, iterations);
Test loadTest = new LoadTest(repeatedTest, users, timer);
return loadTest;
}
public static Test threadLocalSuite(int which){
/**
* If a test case intended to be decorated as a LoadTest contains
* test-specific state in the setUp() method, then the TestFactory
* should be used to ensure that each concurrent user thread
* uses a thread-local instance of the test. For example,
* to create a load test of 10 concurrent users with each user
* running a thread-local instance of MyTest
*/
int users = 10;
Test factory = new TestFactory(MyTest.class);
Test loadTest = new LoadTest(factory, users);
/**
* or, to load test a single test method
*/
Test factory2 = new TestMethodFactory(MyTest.class, "testCircle");
Test loadTest2 = new LoadTest(factory2, users);
if(which == 1){
return loadTest;
}else{
return loadTest2;
}
}
public static void main(String[] args){
TestRunner.run(MyLoadTest.normalSuite());
System.out.println();
TestRunner.run(MyLoadTest.delaySuite());
System.out.println();
TestRunner.run(MyLoadTest.repeatedSuite());
System.out.println();
TestRunner.run(MyLoadTest.threadLocalSuite(1));
System.out.println();
TestRunner.run(MyLoadTest.threadLocalSuite(2));
}
}
2、DBUnit:
DBUnit 扩展自JUnit,它的用途是为了保证涉及数据库操作的测试进行之前数据库处于一种以至状态。
DBUnit提供了几个实用的操作接口:
IDatabaseConnection 代表DBUnit到数据库的一个连接。
IDataSet 代表一个数据表的集合,它可能来源一个数据库表,XML文件或者Excel文件。它的实现很多,最常用的有FlatXmlDataSet、XmlDataSet、QueryDataSet、DatabaseDataSet等。
DatabaseOperation 代表对于数据集的一项操作,可选值包括:
DatabaseOperation.UPDATE :这个操作假定表数据已经存在于目标数据库,否则失败。
DatabaseOperation.INSERT :这个操作假定表数据不存在于目标数据库,否则失败。涉及外键操作时,注意数据集中必须按照适当的顺序排列。
DatabaseOperation.DELETE :这个操作删除表数据,只是删除存在于数据集中的数据而非表中的所有数据。
DatabaseOperation.DELETE_ALL :这个操作删除所有表数据。
DatabaseOperation.TRUNCATE :这个操作等同于DatabaseOperation.DELETE_ALL,但它不记录日志,操作更快,无法实现回滚,而且有些数据库不支持。
DatabaseOperation.REFRESH :这个操作强制提交数据集到目标数据库。
DatabaseOperation.CLEAN_INSERT :这个操作结合了DELETE_ALL和INSERT操作,为了实现测试前的数据集处于一个已知的状态。
使用:
1、创建数据集,可以从数据库、XML文件和Excel等途径创建IDataSet;
2、继承DatabaseTestCase,实现它的getConnection()和getDataSet()方法,分别用于获取数据库连接和数据集。例如:
public class BaseDBUnitTestCase extends DatabaseTestCase {
@Override
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSet("employee.xml");
}
@Override
protected IDatabaseConnection getConnection() throws Exception {
IDatabaseTester databaseTester = new JdbcDatabaseTester(
"com.mysql.jdbc.Driver",
"jdbc:mysql://127.0.0.1/mydb",
"root",
"root");
return databaseTester.getConnection();
}
}
3、(可选)实现getSetUpOperation()和getTearDownOperation()方法
例如:
protected DatabaseOperation getSetUpOperation() throws Exception
{
return DatabaseOperation.REFRESH;
}
protected DatabaseOperation getTearDownOperation() throws Exception
{
return DatabaseOperation.NONE;
}
4、添加测试方法。
5、利用DBUnit提供的断言判断数据集和数据表是否相同,Assert提供了两个静态断言方法。
public class Assertion { public static void assertEquals (ITable expected, ITable actual) public static void assertEquals (IDataSet expected, IDataSet actual)}
例如:
// Fetch database data after executing your codeIDataSet databaseDataSet = getConnection().createDataSet();ITable actualTable = databaseDataSet.getTable("TABLE_NAME");// Load expected data from an XML datasetIDataSet expectedDataSet = new FlatXmlDataSet(new File("expectedDataSet.xml"));ITable expectedTable = expectedDataSet.getTable("TABLE_NAME");// Assert actual database table match expected tableAssertion.assertEquals(expectedTable, actualTable);DBUnit补充 :
1、使用查询获取数据库快照: ITable actualJoinData = getConnection().createQueryTable("RESULT_NAME", "SELECT * FROM TABLE1, TABLE2 WHERE ..."); 2、对比时忽略某些列: DefaultColumnFilter columnFilter = new DefaultColumnFilter(); columnFilter.excludeColumn("PK*"); columnFilter.excludeColumn("*TIME");FilteredTableMetaData metaData = new FilteredTableMetaData( originalTable.getTableMetaData(), columnFilter);3、行排序: 默认情况下DBUnit对于数据库表会按照主键进行排序。如果缺少主键或者由数据库产生,并且行排序没有指定, assertEquals就会失败。又必须手动在使用IDatabaseConnection.createQueryTableshi指定order by子句, 或者使用下面的方式: Assertion.assertEquals(new SortedTable(expected), new SortedTable(actual, expected.getTableMetaData()));
其他测试框架:
TestNG:
TestNG 是跟JUnit并列的另外一个测试框架,它偏向于高级应用,以JDK5的注解为主要编程手段。尽管JUnit4中也提供了类似的注解语法,但是TestNG仍然具有许多JUnit不具有的高级特性。
1、更多灵活性。 TestNG比JUnit4具有更多的灵活性,JUnit4中由@BeforeClass标记的方法必须是static的,当然其中引用的变量也必须是static类型的。
2、依赖性测试。 在TestNG中通过使用 Test
注释的 dependsOnMethods = {"dependMethodName"}可以实现依赖性测试,即被依赖的方法测试失败后,这个方法就会被跳过。
3、失败和重运行。 在JUnit4中,如果测试套件包括1000项测试,其中3项失败,很可能就会迫使您重新运行整个测试套件(修改错误以后)。一旦 TestNG 中出现失败,它就会创建一个XML配置文件,对失败的测试加以说明。如果利用这个文件执行 TestNG 运行程序,TestNG 就只运行失败的测试。所以,在前面的例子里,您只需重新运行那三个失败的测试,而不是整个测试套件。
4、参数化测试。 TestNG可以为测试方法指定参数,并且支持高级参数特性(由Test注解的dataProvider指定),而JUnit4不行。
5、定义测试组。 TestNG 可以定义测试组的能力。每个测试方法都可以与一个或多个组相关联,但可以选择只运行某个测试组。要把测试加入测试组,只要把组指定为 @Test
标注的groups参数。
6、更简单的异常检测。 使用 TestNG 的 @ExpectedExceptions
标注可以使代码编写惊人地容易和简单。
Junit4和TestNG的比较见这里:http://www.ibm.com/developerworks/cn/java/j-cq08296/
TestNG如何使单元测试轻而易举见这里:http://www.ibm.com/developerworks/cn/java/j-testng/
Selenium:
Selenium 是 ThoughtWorks 专门为 Web 应用程序编写的一个验收测试工具。据 Selenium 主页所说,与其他测试工具相比,使用 Selenium 的最大好处是:
Selenium 测试直接在浏览器中运行,就像真实用户所做的一样。Selenium 测试可以在 Windows、Linux 和 MacintoshAnd 上的 Internet Explorer、Mozilla 和 Firefox 中运行。其他测试工具都不能覆盖如此多的平台。
使用 Selenium 和在浏览器中运行测试还有很多其他好处。下面是主要的两大好处:
Selenium包括两种模式:TestRunner和Driven。
TestRunner的测试脚本是用 HTML 语言通过一个简单的表布局编写的,包括命令(断言)、目标和值三部分。其中断言通常使用的是组件的 ID 或名称,但 XPath 和 DOM 定位符也是受支持的。
例如:
<table> <tr> <td>open</td> <td>/change_address_form.html</td> <td></td> </tr> <tr> <td>type</td> <td>address_field</td> <td>Betelgeuse state prison</td> </tr> <tr> <td>clickAndWait</td> <td>//input[@name='Submit']</td> <td></td> </tr> <tr> <td>verifyTextPresent</td> <td>Address change successful</td> <td></td> </tr> </table>
测试套件
要达到对应用程序的完全测试覆盖,通常需要不止一个测试用例。这就是 Selenium 使用测试套件的原因。测试套件用于将具有类似功能的一些测试用例编成一组,以便让它们按顺序运行。
测试套件和测试用例一样,都是用简单的 HTML 表编写的。Selenium 执行的缺省测试套件的名称是 TestSuite.html。下面的例子展示了一个测试套件,该套件像通常的用户一样测试应用程序。
注意,测试套件使用一个只包含一列的表,表中的每一行指向一个包含某个测试用例的文件。
例如: <table> <tr> <td>Test suite for the whole application</td> </tr> <tr> <td><a href="test_main_page.html">Access _fcksavedurl="test_main_page.html">Access main page</a></td> </tr> <tr> <td><a href="test_login.html">Login to application</a></td> </tr> <tr> <td><a href="test_address_change.html">Change address</a></td> </tr> <tr> <td><a href="test_logout.html">Logout from application</a></td> </tr> </table>
Driven Selenium 脚本是用多种受支持的编程语言中的一种编写的 —— 目前可用的有 Java、Ruby 和 Python 驱动程序。这些脚本在浏览器之外的一个单独的进程中运行。驱动程序的任务是执行测试脚本,并通过与运行在浏览器中的 browser bot 进行通信来驱动浏览器。驱动程序与 browser bot 之间的通信使用一种简单的特定于 Selenium 的连接语言 Selenese。
driven 脚本比 test runner 脚本更强大、更灵活,可以将它们与 xUnit 框架集成。driven 脚本的缺点(与 test runner 脚本相比)是,这种脚本编写和部署起来更复杂。这是因为驱动程序必须执行以下任务:
参考见这里:http://www.ibm.com/developerworks/cn/java/wa-selenium-ajax/