单元测试,集成测试还是设计中的问题? [英] Unit-Test, Integration test or problem in design?

查看:62
本文介绍了单元测试,集成测试还是设计中的问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm written my first unit-test and I think it is too dependent on other modules and I'm not sure whether it's because:

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

我首先要说的是,尽管我有大约4年的开发经验,但我从未学习过自动测试. 我刚刚与Hibernate一起完成了DAL实现的一项重大更改,我的一位同事建议我为新零件编写单元测试.
主要变化是切换到按请求的会话"模式和对应用程序事务的更具建设性的使用.
由于上述更改的性质,单元测试从特定请求到达并开始事务的点开始,并且测试在事务结束后结束,并且它检查事务是否执行了应该执行的更改.
此测试涉及初始化以下对象:

I'll first say that although I have around 4 years of experience in development I never learned, nor were taught, automated testing.
I've just finished a major change in our DAL implementation, with Hibernate, and a colleague of mine suggested I write unit-tests for the new parts.
The main change was with respect to switching to the Session-per-Request pattern and the more constructive use of application transactions.
Because of the nature of the above change the unit-test begins at the point where a specific request arrives and begins a transaction and the test ends after the transaction ends and it checks whether the transaction performed the changes it was supposed to.
This one test involves initializing the following objects:

  • 内存中的DB-,以便可以处理数据.
  • 初始化公司记录器,因为方法取决于它.
  • 初始化一个设计为单例的存储库-它是函数访问DAL的大门,但它也存储其他内容,因此是创建的大对象.
  • 初始化也是单例的请求处理程序-保留要测试的方法.

我认为我实际上已经编写了一个集成测试,因为我需要初始化数据库,Hibernate和存储库,但是在测试方法使用所有方法的情况下,我不确定如何编写它这些对象的作用,我很想看看事务处理如何执行(在测试的方法上完成).

I think I've actually written an integration test, as I need to init the DB, Hibernate and the repository, but I'm not sure how I could've written it otherwise given the circumstances where the tested method uses all these objects for its action and I'm interested to see how the transaction handling performs (which is done on the tested method).

对于所有评论和想法,我将不胜感激,如果它们不够明确,我将很乐意详细阐述或整理一下.

I'd appreciate all comments and thoughts and will gladly elaborate or clear things up if they are not clear enough.

谢谢,
伊泰

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

P.S. The HibernateSessionFactory is in-fact the commonly known HibernateUtil from the Hibernate In Action book, wrongly named for historical reasons.

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();
}
}

推荐答案

是的,这绝对是一个集成测试.集成测试没有错,它们是测试策略的重要组成部分,但必须仅限于验证模块是否正确组装以及配置是否正确.

Yep, this definitely is an integration test. There is nothing wrong wih integration tests and they are an important part of a test strategy, but they must be limited to verifying iif the modules are properly assembled and the configuration is properly set.

如果您开始使用它们来测试功能,则会得到太多,并且会发生2件非常糟糕的事情:

If you start using them to test functionality, you will get too much of them and 2 very bad things happen :

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

  1. The tests become frustratingly slow

过早设计骨化设置

后一个问题是因为您现在在集成测试中耦合您的设计,即使模块本身已完全耦合.如果您找到重构的机会,那么它很可能会破坏十几种集成测试,或者您将找不到勇气,或者管理层会阻止您清理工作(我努力!不要碰它"综合症).

The latter problem is because you are now coupling your design in the integration tests, even if the modules themselves are perfectly decoupled. If you find an opportunity to refactor, chances are it will break a dozen integration tests, and either you won't find the courage, or management will prevent you from cleaning up (The "I works!!! Do not touch it" syndrome).

解决方案是通过模拟"环境来对您编写的所有部分进行单元测试.有很多不错的框架可以帮助快速制作模拟对象,我个人经常使用EasyMock.然后,您将在验证方法功能的同时描述与世界其他地方的交互

The solution is to unit test all parts you have written by "mocking" out the environment. There are nice frameworks to help make mock objects on the fly, I personally use EasyMock a lot. YOu then describe the interactions with the rest of the world while verifying the functionality of your methods

在单元测试中,您现在将获得有关代码所依赖的依赖项的详细说明.您还会在这里发现设计问题,因为如果在单元测试中出现复杂的模拟行为,则意味着存在设计问题.这是很好的早期反馈.

In the unit tests you will get now a nice detailed description of the dependencies your code is relying on. You will also spot design problems here because if you get convoluted mock behavior in the unit tests, then it means there are design issues. This is great early feedback.

对基础结构代码进行单元测试没有意义,因为它可能已经进行了单元测试,而且您无能为力.

It does not make sense to unit-test the infrastructure code, as it probably already has been unit-tested, and there is nothing you can do about it anyway.

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

Then add 1 or 2 targeted integration tests to verify all the parts work as expected, the queries return the right objects, the transactions are properly handled, etc...

这平衡了验证组装后一切正常的需求,重构能力,缩短测试时间并设计松耦合模块的需求.

This balances out the need to verify everything works when assembled, with the ability to refactor, keeping your test times short and designeing loosely coupled modules.

尽管确实需要一些经验.我建议找一个经验丰富的开发人员,他曾经做过此事,并提供饮料以换取该领域的指导.问很多问题.

It does take some experience though to pull it off. I would recommend to find an experienced developer who has done this before and offer beverages in exchange for mentoring in this area. Ask a lot of questions.

这篇关于单元测试,集成测试还是设计中的问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆