单元测试,集成测试或设计问题?

时间:2010-10-03 09:38:17

标签: java unit-testing hibernate integration-testing

我写的是我的第一个单元测试,我认为它过于依赖其他模块,我不确定是不是因为:

  • 这是一项复杂的测试
  • 我实际上已经编写了集成测试或
  • 我的设计存在问题

我首先要说的是,虽然我有大约4年的开发经验,但我从未学过,也没有教过自动化测试。
我刚刚用Hibernate完成了DAL实现的重大改变,我的一位同事建议我为新部件编写单元测试。
主要的变化是切换到Session-per-Request模式和更具建设性地使用应用程序事务 由于上述更改的性质,单元测试从特定请求到达并开始事务开始,测试在事务结束后结束,并检查事务是否执行了应该进行的更改。
这一项测试涉及初始化以下对象:

  • 内存数据库 - 以便有数据可供使用。
  • 初始化公司记录器 - 因为方法取决于它。
  • 初始化一个设计为单例的存储库 - 它是DAL的函数门,但它也存储其他东西,因此它是一个很大的对象来创建。
  • 初始化请求的处理程序,这也是一个单例 - 这包含要测试的方法。

我认为我实际上已经编写了一个集成测试,因为我需要初始化数据库,Hibernate和存储库,但是我不知道如果测试方法使用全部的情况我怎么写它呢?这些对象的动作,我很想知道事务处理的执行方式(在测试方法上完成)。

我很欣赏所有的评论和想法,如果不够清楚,我们很乐意详细说明或澄清。

谢谢,
Ittai

P.S。 HibernateSessionFactory实际上是HibernateUtil书籍中众所周知的Hibernate In Action,因历史原因而错误命名。

public class AdminMessageRepositoryUpdaterTest {
private static WardId wardId;
private static EmployeeId employeeId;
private static WardId prevWardId;
private static EmployeeId prevEmployeeId;

@Test
public void testHandleEmployeeLoginToWard(){
    AgentEmployeesWardsEngine agentEmployeesWardsEngine = new AgentEmployeesWardsEngine();
    AgentEngine agentEngine = new AgentEngine();
    //Remove all entries from AgentEmployeesWards table
    HibernateSessionFactory.beginTransaction();
    for (Agent agent : agentEngine.findAll()){
        agentEmployeesWardsEngine.removeAgentEntries(agent.getId());
    }
    HibernateSessionFactory.commitTransaction();//no need to try catch as this is done in a controlled environment
    int i=0;
    //build expectedSet
    Set<AgentEmployeesWards> expectedMappingsToChangeSet = new HashSet<AgentEmployeesWards>();
    //Mappings which should have ward updated
    expectedMappingsToChangeSet.add(new AgentEmployeesWards(new AgentId(1).getValue(), employeeId.getValue(), prevWardId.getValue(), true, TimestampUtils.getTimestamp(), i++));
    expectedMappingsToChangeSet.add(new AgentEmployeesWards(new AgentId(2).getValue(), employeeId.getValue(), prevWardId.getValue(), true, TimestampUtils.getTimestamp(), i++));
    //Mappings which should have employee updated
    expectedMappingsToChangeSet.add(new AgentEmployeesWards(new AgentId(3).getValue(), prevEmployeeId .getValue(), wardId.getValue(), false, TimestampUtils.getTimestamp(), i++));
    expectedMappingsToChangeSet.add(new AgentEmployeesWards(new AgentId(4).getValue(), prevEmployeeId.getValue(), wardId.getValue(), false, TimestampUtils.getTimestamp(), i++));

    //Prepare clean data for persistence
    Set<AgentEmployeesWards> cleanSet = new HashSet<AgentEmployeesWards>(expectedMappingsToChangeSet);
    //Mappings which should NOT have ward updated
    cleanSet.add(new AgentEmployeesWards(new AgentId(5).getValue(), employeeId.getValue(), prevWardId.getValue(), false, TimestampUtils.getTimestamp(), i++));
    cleanSet.add(new AgentEmployeesWards(new AgentId(6).getValue(), employeeId.getValue(), prevWardId.getValue(), false, TimestampUtils.getTimestamp(), i++));
    //Mappings which should NOT have employee updated
    cleanSet.add(new AgentEmployeesWards(new AgentId(7).getValue(), prevEmployeeId .getValue(), wardId.getValue(), true, TimestampUtils.getTimestamp(), i++));
    cleanSet.add(new AgentEmployeesWards(new AgentId(8).getValue(), prevEmployeeId.getValue(), wardId.getValue(), true, TimestampUtils.getTimestamp(), i++));
    HibernateSessionFactory.beginTransaction();
    for (AgentEmployeesWards agentEmployeesWards : cleanSet){
        agentEmployeesWardsEngine.saveNewAgentEmployeesWardsEntry(agentEmployeesWards);
    }
    HibernateSessionFactory.commitTransaction();//no need to try catch as this is done in a controlled environment
    //Close the session as to neutralize first-level-cache issues
    HibernateSessionFactory.closeSession();
    //Perform the action so it can be tested
    AdminMessageReposityUpdater.getInstance().handleEmployeeLoginToWard(employeeId, wardId, TimestampUtils.getTimestamp());

    //Close the session as to neutralize first-level-cache issues
    HibernateSessionFactory.closeSession();

    //Load actualSet from DAL
    Set<AgentEmployeesWards> actualSet = new HashSet<AgentEmployeesWards>(agentEmployeesWardsEngine.findByPrimaryEmployeeId(employeeId));
    actualSet.addAll(agentEmployeesWardsEngine.findByPrimaryWardId(wardId));

    //Prepare expected
    Set<AgentEmployeesWards> expectedSet = new HashSet<AgentEmployeesWards>();
    for (AgentEmployeesWards agentEmployeesWards : expectedMappingsToChangeSet){
        //We need to copy as the wardId and employeeId are properties which comprise the equals method of the class and so 
        //they cannot be changed while in a Set
        AgentEmployeesWards agentEmployeesWardsCopy = new AgentEmployeesWards(agentEmployeesWards);
        if (agentEmployeesWardsCopy.isEmployeePrimary()){
            //If this is a employee primary we want it to be updated to the new org-unit id
            agentEmployeesWardsCopy.setWardId(wardId.getValue());
        } else {
            //Otherwise we want it to be updated to the new employee id
            agentEmployeesWardsCopy.setEmployeeId(employeeId.getValue());
        }
        expectedSet.add(agentEmployeesWardsCopy);
    }
     //Assert between actualSet and expectedSet
    // Assert actual database table match expected table
   assertEquals(expectedSet, actualSet);


}
@BeforeClass
public static void setUpBeforeClass() throws SQLException,ClassNotFoundException{
    Class.forName("org.h2.Driver");
    Connection conn = DriverManager.getConnection("jdbc:h2:mem:MyCompany", "sa", "");

    ConfigurationDAO configDAO = new ConfigurationDAO();
    HibernateSessionFactory.beginTransaction();
    configDAO.attachDirty(new Configuration("All","Log", "Level", "Info",null));
    configDAO.attachDirty(new Configuration("All","Log", "console", "True",null));
    configDAO.attachDirty(new Configuration("All","Log", "File", "False",null));

    HibernateSessionFactory.commitTransaction();
    Logger log = new Logger();
    Server.getInstance().initialize(log);
    Repository.getInstance().initialize(log);
    AdminMessageReposityUpdater.getInstance().initialize(log);

    AdminEngine adminEngine = new AdminEngine();
    EmployeeEngine employeeEngine = new EmployeeEngine();
    HibernateSessionFactory.beginTransaction();
    Ward testWard = new Ward("testWard", 1, "Sales", -1, null);
    adminEngine.addWard(testWard);
    wardId = new WardId(testWard.getId());
    Ward prevWard = new Ward("prevWard", 1, "Finance", -1, null);
    adminEngine.addWard(prevWard);
    prevWardId = new WardId(prevWard.getId());

    Employee testEmployee = new Employee("testEmployee", "test", null, "employee", "f", prevWardId.getValue(), null, false, true);
    employeeEngine.setEmployee(testEmployee);
    employeeId = new EmployeeId(testEmployee.getId());

    Employee prevEmployee = new Employee("prevEmployee", "prev", null, "employee", "f", wardId.getValue(), null, false, true);
    employeeEngine.setEmployee(prevEmployee);
    prevEmployeeId = new EmployeeId(prevEmployee.getId());
    HibernateSessionFactory.commitTransaction();
    HibernateSessionFactory.closeSession();
}
@AfterClass
public static void tearDownAfterClass(){
    AdminEngine adminEngine = new AdminEngine();
    EmployeeEngine employeeEngine = new EmployeeEngine();
    HibernateSessionFactory.beginTransaction();
    employeeEngine.removeEmployeeById(employeeId);
    employeeEngine.removeEmployeeById(prevEmployeeId);
    adminEngine.removeWardById(wardId);
    adminEngine.removeWardById(prevWardId);
    HibernateSessionFactory.commitTransaction();
    HibernateSessionFactory.closeSession();
}
}

1 个答案:

答案 0 :(得分:4)

是的,这肯定是一个集成测试。集成测试没有任何问题,它们是测试策略的重要组成部分,但它们必须限于验证模块是否正确组装并正确设置配置。

如果你开始使用它们来测试功能,你会得到太多的东西而且会发生两件非常糟糕的事情:

  1. 测试变得令人沮丧地缓慢

  2. 过早设计骨化

  3. 后一个问题是因为您现在正在集成测试中耦合您的设计,即使模块本身完全分离。如果你有机会进行重构,很可能会打破十几个集成测试,要么你不会找到勇气,要么管理层会阻止你清理(“我工作!!!不要触摸它”综合症)。

    解决方案是通过“嘲弄”环境对您编写的所有部分进行单元测试。有很好的框架可以帮助制作模拟对象,我个人使用EasyMock很多。然后,您将在验证方法功能的同时描述与世界其他地方的互动

    在单元测试中,您将获得代码所依赖的依赖项的详细描述。您还将在此处发现设计问题,因为如果您在单元测试中遇到复杂的模拟行为,那么这意味着存在设计问题。这是很好的早期反馈。

    对基础设施代码进行单元测试是没有意义的,因为它可能已经过单元测试,无论如何你无能为力。

    然后添加1或2个目标集成测试以验证所有部件按预期工作,查询返回正确的对象,正确处理事务等...

    这平衡了在组装时验证一切正常的需要,具有重构能力,缩短测试时间并设计松散耦合模块的能力。

    确实需要一些经验才能实现。我建议找一位经验丰富的开发人员,他之前已经做过这件事并提供饮料以换取这方面的指导。问了很多问题。