【问题标题】:Test Method Called Without Having Argument In Test Class在测试类中调用没有参数的测试方法
【发布时间】:2014-07-25 16:34:33
【问题描述】:

我有一个类,它接收带有有效负载字符串的消息。 然后将有效负载拆分并用于创建一个传递给 DAOInterface 的实体。

如何测试daoInterface.insert(entity) 是否已拨打?

要模拟 DAOInterface 然后验证对 DAO 的调用需要测试类中的实体,即

verify(daoInterface).insert(entity);

这是糟糕的设计,即在这个阶段创建实体吗?是否应该将 Sting[] 拆分传递给 DAOImplementaion 并在那里初始化。示例问题,

public class ServiceClass {

    @AutoWire
    private DAOInterface daoInterface;

    public void serviceMessage(Message<String> message) {

        MessageHeaders mh = new MessageHeaders(message.getHeaders());       
        String[] split = ((String) mh.get("payload")).split("_");

        code omitted
        ...

        String type = mh.get("WhatType");

        Entity entity = new Entity(split[0], split[1], split[2]);

        if (type.equals("one"))
        {
            daoInterface.insert(entity); //How to test?
        }
        else
        {
            if (type.equals("two"))
            {
                doaInterface.modify(entity); //How to test?
            }
        }
    }
}

【问题讨论】:

    标签: java unit-testing testing junit mockito


    【解决方案1】:

    您可以使用 Mockito Matchers 进行验证。

    如果您只关心使用 some Entity 调用该方法,则可以使用

    进行验证
    verify(daoInterface).insert(any(Entity.class));
    

    如果您关心哪个Entity,并且Entity 类有一个equals 方法,您可以创建一个与创建的实体相同的实体并使用它进行验证

    verify(daoInterface).insert(eq(expectedEntity);
    

    如果它比这两种情况都复杂,您也可以编写自己的参数匹配器。

    【讨论】:

    • 谢谢唐。使用实体 equals() 方法来验证初始化的实体是否符合预期,并且调用 insert 方法是我所追求的。
    【解决方案2】:

    您可以做的最简单的事情是向服务中注入另一个协作者,这会将 payload 转换为 Entity。这样您就可以控制对象的创建 (Inversion of Control)。像下面注入到ServiceClass 的示例应该可以完成这项工作:

    interface PayloadTransformer {
        public Entity transform(String payload);
    }
    

    通过这种方式,您的代码将易于测试,并且您可以拆分职责,这通常是一件好事。看看Single Responsibility principle

    将转换逻辑下推到 dao 几乎从来都不是一个好主意。

    顺便说一句。你可以写else if 而不需要额外的括号和缩进。它更具可读性:

    if (a) {
        // do something
    } else if (b) {
        // do something
    } else {
        // do something
    }
    

    最后一个建议 ServiceClass 是一个很糟糕的类名称。类这个词在这里是多余的。只需将其命名为 ServiceEntityServiceMessageService 或适合您情况的名称。 我也不会使用后缀 *Interface 来命名字段。我假设下面是一些注入的实现。最好将其命名为 entityDao 或仅命名为 dao。不过这取决于你:)

    【讨论】:

    • 上面的代码只是问题的一个例子,因此名称:)
    • “你能做的最简单的事情就是为服务注入另一个协作者”?国际奥委会?仍然需要调用 daoInterface.insert(entity)。
    • IoC - 控制反转。如果您将 dao 和 transformer 都注入到服务中,您将可以控制和访问测试端所需的一切。您可以测试与特定实例的交互,而不是像您接受的答案中的任何 Entity.class 。这些是正确的面向对象设计的基础。
    • 接受的应答器不会测试它测试的任何实体,如果呼叫是使用与您期望的特定实体相同的特定实体进行的。
    • 注入另一个对象 @AutoWired PayLoadTransformer paloadTransformer ... Entity entity = payloadTransformer.transform(payload); ... daoInterface.insert(entity); 在测试类中,您需要模拟转换器和 dao 以验证实体和字符串 verify(daoInterface).insert(entity); verify(pInt).transform(payload);
    【解决方案3】:

    如果您使用PowerMock, 之类的测试框架,您可以在测试中使用invoke private constructors and private methods。这使得注入模拟对象(如模拟 DAOInterface)变得容易,因此您可以稍后检索它并测试它是否被调用。

    例如,在 PowerMock 中,调用私有构造函数:

    public class ServiceClass{
    
            @Autowire
            private final DAOInterface dao;
    
            public ServiceClass() {
            }
    
            private ServiceClass(DAOInterface dao) {
                    this.dao = dao;
            }
    
    
    }
    

    你只需这样做:

    PrivateConstructorInstantiationDemo instance =  WhiteBox.invokeConstructor(
                                    PrivateConstructorInstantiationDemo.class, 
                                    new MockDAOInterface() );
    

    因此,如果您使用的是像上面这样的依赖注入框架,这很好地吻合。您通常不会在测试期间让依赖注入工作,因为它通常需要启动具有大量配置的大量代码。

    通过添加单个私有构造函数,您可以避免破坏封装,但您仍然可以在测试期间使用 PowerMock 等测试框架将模拟对象注入代码中。这被认为是最佳做法。

    您可以打破封装并向服务类添加可公开访问的方法或 ctor,但如果您的设计不需要它们,那么仅将它们添加用于测试并不是一个好习惯。这就是为什么人们如此努力地绕过 Mockito 和 PowerMock 等框架中的封装。这不仅仅是对私有代码的回避,而是因为您希望在保持封装的同时仍然能够进行测试。

    编辑:

    如果您不熟悉制作模拟对象,您应该对该主题进行一些 Google 搜索。这很常见,也是一项很好的技能。使用上面的代码,您可以制作自己的模拟对象。但是制作模拟是如此普遍,以至于大多数测试框架都会为你做这件事。

    例如,在 PowerMock 中,我只是查看了他们的page on making mocks here。你可以做一个这样的模拟

    DAOInteface myMock = createMock(DAOInterface.class);
    

    然后您可以要求 mock 验证方法是否被调用:

    expect(myMock.someMethod());
    

    现在模拟“期望”调用该方法,如果不是,它将为您的测试生成错误。其实挺甜的。

    您还可以从调用中返回值:

    expect(myMock.insert()).andReturn("Test succeeded");
    

    这样您的代码就会在调用该方法时看到“测试成功”的值。我没有看到“插入”确实返回值,这只是一个示例。

    【讨论】:

    • 如果需要这样的技巧来测试非常简单的类,那就意味着设计有问题。
    • 呃,没有。一直都需要这样的东西。
    • 如果您测试的某些代码无法更改,那么可以。否则,它清楚地表明您应该重构。我主要使用 Groovy/Spock 编写测试,这意味着我可以访问任何私有的东西(这是一个已知的 Groovy 功能​​/错误)。尽管如此,我从不这样做,因为这是一种糟糕的做法。您不需要直接测试/断言任何私有内容。
    • 顺便说一句。你的回答没有解决这个问题。据我所知,问题不在于嘲笑 dao,而在于测试与特定论点的交互。但是,从测试级别来看,无法控制参数实例化。因此,我猜为什么即使使用 PowerMock 也无法对其进行测试。
    • Mockito 当然不会忽略参数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-08
    • 2018-12-08
    • 2017-10-12
    • 2019-01-23
    • 1970-01-01
    • 1970-01-01
    • 2012-03-28
    相关资源
    最近更新 更多