Unitils开发指南

      单元测试应该是简单和直观的,而现实中的项目大多都是采用多层方式的,如EJB和hibernate的数据驱动层的中间件技术。

      unitils来源于一个尝试,就是希望能以更务实的方式来看待单元测试......

      这个指南会告诉你,什么项目可以使用unitils。 并在这个指导方针页 中你可以了解到测试的准侧和它的特点。如果您想了解如何可以配置unitils ,并得以迅速地启动,请查看cookbook 。

  • unitils的断言
  • unitils的模块
  • 数据库的测试
  • 数据库的自动测试
  • hibernate的测试
  • jpa的测试
  • spring的测试
  • mock object的测试
  • 今后的方向

      unitils的断言

      在开始这个指南之前我们先说明一下独立于unitils核心模块的断言。在下面的例子中,不需要进行配置,将unitils的jar包和依赖包放在你的classpath下,就可以进行测试了。

      通过反射进行断言

      一个典型的单元测试包含了结果值和期望值的比较,unitils提供了断言的方法以帮助你进行该操作,让我们看看实例2中对有着id、first name、last name属性的User类的2个实例的比较

public class User {
    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertEquals(user1, user2);
    你期望这个断言是成功的,因为这两个实例含有相同的属性,但是运行的结果并非如此,应为User类并没有覆写
equals()方法,所以assertEquals是对两个实例是否相等进行判断(user1 == user2)结果导致了比较的失败。
    假设你像如下代码一样实现了equals方法
public boolean equals(Object object) {
    if (object instanceof User) {
        return id == ((User) object).id;
    }
    return false;
}

       这在你的程序逻辑中是一个合乎逻辑的实现,当两个User实例拥有相同的id的时候,那么这两个实例就是相等的。然而这种方式在你的单元测试中并不合适,并不能通过id的相同来认为两个user是相同的。

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "Jane", "Smith");
assertEquals(user1, user2);

       这个断言将会成功,但这并不是你所期望的,因此不要使用assertEquals来对两个对象进行判定是否相等(外覆类和java.lang.String类除外)。要想断言他们相等,一种方法就是断言每个属性相等。

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertEquals(user1.getId(), user2.getId());
assertEquals(user1.getFirst(), user2.getFirst());
assertEquals(user1.getLast(), user2.getLast());

       unitils提供了一些方法来帮助你执行断言更加的简单,通过反射,使用ReflectionAssert.assertRefEquals上面的代码重写如下:

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertRefEquals(user1, user2);
   这个断言将通过反射对两个实例中的每个属性都进行比较,先是id、然后是first name、最后是last name。
   如果一个属性本身也是一个对象,那么将会使用反射进行递归比较,这同样适合与集合、map、和数组之间的比较,他们
的每个元素会通过反射进行比较。如果值是一个基本类型或者是一个外覆类,那么将会使用==进行值的比较,因此下面的断
言会取得成功
assertRefEquals(1, 1L); 

List<Double> myList = new ArrayList<Double>();
myList.add(1.0);
myList.add(2.0);
assertRefEquals(Arrays.asList(1, 2), myList);

      宽松的断言

     出于可维护性,这一点是十分重要的,举例说明:如果你要计算一个帐户的余额,那你就没比较检查这个帐户的名称。他只会增加复杂性,使之更难理解。如果你想让你的测试代码更容易生存,更容易重构的话,那请确保你断言的范围。

      宽松的顺序

      在比较集合和数组的时候你可能并不关心他们中元素的顺序,通过使用ReflectionAssert.assertRefEquals方法并配合ReflectionComparatorMode.LENIENT_ORDER参数将忽略元素的顺序。

List<Integer> myList = Arrays.asList(3, 2, 1);
assertRefEquals(Arrays.asList(1, 2, 3), myList, LENIENT_ORDER);
   无视默认
   第二种的从宽方式是使用ReflectionComparatorMode.IGNORE_DEFAULTS模式,当这种模式被设置的时候,java
的默认值,如null、0、false将会不参与断言(忽略)。
   举个例子,如果你有一个User类,该类有着first name,last name,street等属性,但是你仅仅想对first name
和street进行检查而忽略其他的属性。
User actualUser   = new User("John", "Doe", new Address("First street", "12", "Brussels"));
User expectedUser = new User("John",  null, new Address("First street", null,       null));
assertRefEquals(expectedUser, actualUser, IGNORE_DEFAULTS);

      你所期望忽略的属性的对象必须放在断言左边,如果放在右边那么依然进行比较。

assertRefEquals(null, anyObject, IGNORE_DEFAULTS);  // Succeeds
assertRefEquals(anyObject, null, IGNORE_DEFAULTS);  // Fails

      宽松的日期

      第三种从宽处理是ReflectionComparatorMode.LENIENT_DATES,当两个日期都是值,或者都是null的时候,实际的日期将会被忽略(即断言为相等)。

Date actualDate =   new Date(44444);
Date expectedDate = new Date();
assertRefEquals(expectedDate, actualDate, LENIENT_DATES);

      assertLenEquals

      ReflectionAssert还提供了一种断言,他提供宽松的顺序又提供无视的忽略。

List<Integer> myList = Arrays.asList(3, 2, 1);
assertLenEquals(Arrays.asList(1, 2, 3), myList); 

assertLenEquals(null, "any");  // Succeeds
assertLenEquals("any", null);  // Fails

      属性断言

      assertLenEqualsassertRefEquals都是比较对象,ReflectionAssert也提供方法对对象的属性进行比较。(依赖与ONGL)。

      一些属性比较的例子

assertPropertyLenEquals("id", 1, user);  //断言user的id属性的值是1 
assertPropertyLenEquals("address.street", "First street", user); //断言user的address的street属性

      在这个方式中你期望的值和判定的对象也可以使用集合

assertPropertyLenEquals("id", Arrays.asList(1, 2, 3), users);
assertPropertyLenEquals("address.street", Arrays.asList("First street", 
"Second street", "Third street"), users);
 
 
Unitils模块

配置

和大多数的项目一样,unitils也需要一些配置,默认情况下有3个配置,每个配置都覆写前一个的配置

  1. unitils-default.properties 默认的配置,在unitils发行包中。
  2. unitils.properties 可包含项目的全部配置
  3. unitils-local.properties 可以包含用户特定配置

第一个配置文件unitils-default.properties,它包含了缺省值并被包含在unitils的发行包中。我们没有必要对这个文件进行修改,但它可以用来作参考。

第二个配置文件unitils.properties,它是我们需要进行配置的文件,并且能覆写缺省的配置。举个例子,如果你的项目使用的是oracle数据库,你可以创建一个unitils.properties文件并覆写相应的driver class和database url。

database.driverClassName=oracle.jdbc.driver.OracleDriver
database.url=jdbc:oracle:thin:@yourmachine:1521:YOUR_DB

这个文件并不是必须的,但是一旦你创建了一个,你就需要将该文件放置在项目的classpath下

最后一个文件,unitils-local.properties是可选的配置文件,它可以覆写项目的配置,用来定义开发者的具体设置,举个例子来说,如果每个开发者都使用自己的数据库schema,你就可以创建一个unitils-local.properties为每个用户配置自己的数据库账号、密码和schema。

database.userName=john
database.password=secret
database.schemaNames=test_john

每个unitils-local.properties文件应该放置在对应的用户文件夹中(System.getProperty("user.home"))。

本地文件名unitils-local.properties也可以通过配置文件定义,在unitils.properties覆写unitils.configuration.localFileName就可以。

unitils.configuration.localFileName=projectTwo-local.properties

 

启用你的unitils

unitils的功能是依赖于基础的测试框架,要使用unitils的功能,就必须先enable他们,这样做的目的也是为了容易扩展。目前支持的框架有:

  1. JUnit3 :org.unitils.UnitilsJUnit3
  2. JUnit4 :org.unitils.UnitilsJUnit4
  3. TestNG:org.unitils.UnitilsTestNG

举个例子,如果使用JUnit3,你要使用unitils

import org.unitils.UnitilsJUnit3;
public class MyTest extends UnitilsJUnit3 {
}

通常你将创建你自己的包含一些公共行为的测试基类,如dataSource的注入,你可以让这个基类继承unitils测试类。

当你使用的是JUnit4的话,你也可是使用@RunWith来代替继承unitils测试类

import org.junit.runner.RunWith;
import org.unitils.UnitilsJUnit4TestClassRunner;
@RunWith(UnitilsJUnit4TestClassRunner.class)
public class MyTest {
}

 

模块系统

在开始举例之前,让我们先了解一下unitils概念。

unitils的结构被设计成了十分容易扩展,每个模块提供了一种服务,当执行Test的时候通过TestListener调用相应的服务。

image

这种设计采用了一个统一的方式提供服务,如果你需要加入其他的服务,无需去改编测试基类(UnitilsJUnit4这些类)。要加入新的服务只需要添加一个新的模块并在unitls配置文件中登记这个模块。

目前unitils中所有有效的模块如下:

  1. DatabaseModule 数据库单元测试的维护和连接池。
  2. DbUnitModule 使用DBUnit来管理测试数据。
  3. hibernatemodule 支持Hibernate的配置和自动数据库映射检查。
  4. EasyMockModule 支持创建mock和宽松的反射参数匹配。
  5. InjectModule 支持在一个对象中注入另一个对象。
  6. SpringModule 支持加载spring的上下文配置,并检索和Spring Bean注入。

数据库测试

在创建企业级应用的时候,数据层的单元测试因为其复杂性往往被遗弃,Unitils大大降低了测试的复杂性,使得数据库的测试变得容易并且易维护。已下介绍databasemodule和dbunitmodule进行数据库的单元测试。

用dbUnit管理测试数据

数据库的测试应该在单元测试数据库上运行,单元测试数据库给我们提供了一个完整的并有着很好细粒度控制的测试数据,DbUnitModule是在dbunit的基础上进一步的为数据库的测试提供数据集的支持。

加载测试数据集

让我们以UserDAO中一个简单的方法findByName(检查姓氏和名字)为例子开始介绍。他的单元测试如下:

@DataSet

public class UserDAOTest extends UnitilsJUnit4 {

    @Test

    public void testFindByName() {

        User result = userDao.findByName("doe", "john");

        assertPropertyLenEquals("userName", "jdoe", result);

    }

    @Test

    public void testFindByMinimalAge() {

        List<User> result = userDao.findByMinimalAge(18);        

        assertPropertyLenEquals("firstName", Arrays.asList("jack"), result);

    }

}

    @DateSet 注解表示了测试需要寻找dbunit的数据集文件进行加载,如果没有指明数据集的文件名,则Unitils自动在class文件的同目录下加载文件名为 className.xml的数据集文件。(这种定义到class上面的数据集称为class级别的数据集)

    数据集 文件必须是dbunit的FlatXMLDataSet文件格式,其中包含了所要测试的数据。测试数据库表中所有的内容将会被删除,然后再插入数据集中的 数据。如果表不属于数据集中的,哪么该表的数据将不会被删除。你也可以明确的加入一个空的表元素,例如<MY_TABLE/>(可以达到删除 测试数据库表中内容的作用),如果要明确指定一个空的值,那么使用值[null]。

   为UserDAOTest我们创建一个数据集,并放在UserDAOTest.class文件同目录下。

<?xml version='1.0' encoding='UTF-8'?>

<dataset>

    <usergroup name="admin" />  

    <user userName="jdoe"  name="doe"   firstname="john"   userGroup="admin" />

    <usergroup name="sales" />    

    <user userName="smith" name="smith" userGroup="sales" />

    

</dataset>

   测试运行的时候,首先将删除掉usergroup表和user表中的所有内容,然后将插入数据集中的内容。其中name为smith的firstname的值将会是null。

   假设testFindByMinimalAge()方法将使用一个特殊的数据集而不是使用class级别的数据集,你可以定义一个UserDAOTest.testFindByMinimalAge.xml 数据集文件并放在测试类的class文件同目录下。

<?xml version='1.0' encoding='UTF-8'?>

<dataset>

    <user userName="jack" age="18" />

    <user userName="jim"  age="17" />

</dataset>

这时,你在testFindByMinimalAge()方法使用@DataSet注解,他将覆盖class级的数据集

public class UserDAOTest extends UnitilsJUnit4 {

@Test

@DataSet("UserDAOTest.testFindByMinimalAge.xml")

public void testFindByMinimalAge() {

List<User> result = userDao.findByMinimalAge(18); 

assertPropertyLenEquals("firstName", Arrays.asList("jack"), result);

}

}

不要过多的使用method级的数据集,因为过多的数据集文件意味着你要花大量的时间去维护,你优先考虑的是使用class级的数据集。

配置数据集加载策略

缺省情况下数据集被写入数据库采用的是clean insert策略。这就意味着数据在被写入数据库的时候是会先删除数据集中有使用的表的数据,然后在将数据集中的数据写入数据库。加载策略是可配额制的,我们通过修改DbUnitModule.DataSet.loadStrategy.default 的属性值来改变加载策略。假设我们在unitils.properties属性文件中加入以下内容:

DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.InsertLoadStrategy 

这时加载策略就由clean insert变成了insert,数据已经存在表中将不会被删除,测试数据只是进行插入操作。

加载策略也可以使用@DataSet的注解属性对单独的一些测试进行配置:

@DataSet(loadStrategy = InsertLoadStrategy.class) 

对于那些树形DbUnit的人来说,配置加载策略实际上就是使用不同的DatabaseOperation,以下是默认支持的加载策略方式:

CleanInsertLoadStrategy: 先删除dateSet中有关表的数据,然后再插入数据。

InsertLoadStrategy: 只插入数据。

RefreshLoadStrategy: 有同样key的数据更新,没有的插入。

UpdateLoadStrategy: 有同样key的数据更新,没有的不做任何操作。

配置数据集工厂

 在Unitils中数据集文件采用了multischema xml 格式,这是DbUnits的FlatXmlDataSet 格式的扩展。配置文件格式和文件的扩展可以采用DataSetFactory 

虽然Unitils当前只支持一种数据格式,但是我们可以通过实现DataSetFactory来使用其他文件格式。当你想使用excel而不是xml格式的时候,可以通过unitils.property中的DbUnitModule.DataSet.factory.default 属性和@DataSet 注解来创建一个DbUnit's XlsDataSet 实例。

验证测试结果

有些时候我们想在测试时完毕后使用数据集来检查数据库中的内容,举个例子当执行完毕一个存储过程后你想检查一下啊数据是否更新了没有。

下面的例子表示的是禁用到一年内没有使用过的帐户

public class UserDAOTest extends UnitilsJUnit4 { 

    @Test @ExpectedDataSet 

    public void testInactivateOldAccounts() { 

        userDao.inactivateOldAccounts(); 

    } 

注意在test方法上增加了一个@ExpectedDataSet 注解。这将指明unitils将使用UserDAOTest.testInactivateOldAccounts-result.xml 这个数据集的内容和数据库的内容进行比较。

<?xml version='1.0' encoding='UTF-8'?> 

<dataset> 

    <user userName="jack" active="true" /> 

    <user userName="jim"  active="false" /> 

</dataset> 

根据这个数据集,将会检查是否有两条和记录集的值相同的记录在数据库中。而其他的记录和表将不理会。

使用的是@DataSet 注解的话,文件名可以明确指出,如果文件名没有明确指出来,那么文件名将匹配className .methodName -result.xml 

使用少使用结果数据集,加入新的数据集意味着更多的维护。替代方式是在代码中执行相同的检查(如使用一个findactiveusers()方法)。

使用多模式的数据集

一个程序不单单只是连接一个数据库shema。Unitils采用了扩展的数据集xml来定义多schemas下的数据。以下就是一个读取数据到2个不同的schemas中的例子:

<?xml version='1.0' encoding='UTF-8'?> 

<dataset xmlns="SCHEMA_A" xmlns:b="SCHEMA_B"> 

    <user id="1" userName="jack" />     

    <b:role id="1" roleName="admin" /> 

</dataset> 

在这个例子中我定义了两个schemas,SCHEMA_A 和 SCHEMA_B第一个schema,SCHEMA_A 被连接到默认的xml命名空间中,第二个schema,SCHEMA_B 被连接到命名空间b。如果表xml元素的前缀使用了命名空间b,那么该表就是schema SCHEMA_B 中的,如果没有使用任何的命名空间那么该表将被认为是SCHEMA_A  

中的。以上例子中测试数据定义了表SCHEMA_A.user SCHEMA_B.role

如果在数据集中没有配置一个默认的命名空间,那么将会采用在unitils.properties中的属性database.schemaNames 的第一个值作为默认的

database.schemaNames=SCHEMA_A, SCHEMA_B 

这个配置将SCHEMA_A 作为缺省的schema,这样你可以简化数据集的声明。

<?xml version='1.0' encoding='UTF-8'?> 

<dataset xmlns:b="SCHEMA_B"> 

    <user id="1" userName="jack" />     

    <b:role id="1" roleName="admin" /> 

</dataset> 

连接测试数据库 

在以上所有的例子中,我们都有一件重要的事情没有做:当我们进行测试的时候,怎样连接数据库并得到DataSource

当测试套件的第一个测试数据库的案例运行的时候,Unitils将会通过属性文件创建一个DataSource 的实例来连接你单元测试时的数据库,以后的测试中都将使用这个DataSource 实例。连接配置的详细内容如下:

database.driverClassName=oracle.jdbc.driver.OracleDriver 

database.url=jdbc:oracle:thin:@yourmachine:1521:YOUR_DB 

database.userName=john 

database.password=secret 

database.schemaNames=test_john 

配置章节所说的那样,你可以将连接数据库的驱动类和url地址配置到unitils.properties 中去,而用户名,密码以及schema可以配置到unitils-local.properties 中去,这样可以让开发人员连接到自己的单元测试数据库中进行测试而不会干预到其他的人。

在属性或者setter方法前使用注解@TestDataSource ,将会将DataSource 实例注入到测试实例中去,如果你想加入一些代码或者配置一下你的datasource,你可以做一个抽象类来实现该功能,所有的测试类都继承该类。一个简单的例子如下:

public abstract class BaseDAOTest extends UnitilsJUnit4 { 

    @TestDataSource 

    private DataSource dataSource; 

     

    @Before     

    public void initializeDao() { 

        BaseDAO dao = getDaoUnderTest(); 

        dao.setDataSource(dataSource); 

    } 

    protected abstract BaseDAO getDaoUnderTest(); 

上面的例子采用了注解来取得一个datasource的引用,另外一种方式就是使用DatabaseUnitils.getDataSource() 方法来取得datasource。

事务

出于不同的原因,我们的测试都是运行在一个事务中的,其中最重要的原因如下:

数据库的很多action都是在事务正常提交后才做,如SELECT FOR UPDATE 和触发器

许多项目在测试数据的时候都会填写一些测试数据,每个测试运行都会修改或者更新了数据,当下一个测试运行的时候,都需要将数据回复到原有的状态。

如果使用的是hibernate或者JPA的时候,都需要每个测试都运行在事务中,保证系统的正常工作。

缺省情况下,事务管理是disabled的,事务的默认行为我们可以通过属性文件的配置加以改变:

DatabaseModule.Transactional.value.default=commit 

采用这个设置,每个的测试都将执行commit,其他的属性值还有rollback disabled 

我们也可以通过在测试类上使用注解@Transactional 来改变默认的事务设置,如:

@Transactional(TransactionMode.ROLLBACK) 

public class UserDaoTest extends UnitilsJUnit4 { 

通过这种class上注解的事务管理,可以让每个测试都确保回滚,@Transactional 注解还可以继承的,因此我们可以将其放在父类中,而不必每个子类都进行声明。

.........

如果你使用Unitils的spring支持(见使用spring进行测试)你如果配置了PlatformTransactionManager 的bean,那么unitils将会使用这个事务管理。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要编写和运行JUnit单元测试,需要使用JUnit的相关jar包。其中,junit-4.12.jar是JUnit 4.12版本的核心库,可以从中央仓库地址https://mvnrepository.com/artifact/junit/junit/4.12下载。此外,还有其他一些引用的jar包,包括junit-4.11.jar、dbunit-2.4.9.jar、unitils-3.3-with-dependencies.zip和mockito-1.9.5.zip等。你可以根据需要选择下载相应版本的jar包,并将其导入到你的项目中以便进行JUnit单元测试。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JUnit单元测试打进jar包方法](https://blog.csdn.net/zhanngle/article/details/90242570)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [单元测试Junit所需要的jar包](https://blog.csdn.net/qq_38712932/article/details/85099192)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [junit单元测试jar包集](https://download.csdn.net/download/ruoshuirou/8247917)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值