首页
Preview

如何测试私有方法

为了测试一个方法,你需要执行它,但是直接调用私有方法可能会很困难甚至不可能,这取决于你使用的编程语言。在本文中,我将主要讲述Python和Java,但是所描述的技术肯定可以应用于许多其他语言(但我不能确定是否适用于所有语言)。

什么是私有方法?

你是否曾经问过自己,为什么你需要私有方法?你需要私有数据来维护一致性,但为什么需要私有方法?

严格来说,你不需要。但它们仍然有至少两个主要原因有所帮助:

  • 你想要提取一些处理私有数据的代码;
  • 你想要提取一些不使用私有数据但仍然不适合API的代码(因为用户根本不关心)。

虽然第一个原因可以防止私有数据损坏,但第二个原因仅仅是使你的API更清晰。

不要测试它

令人惊讶的是,当我第一次接触Java编程并谷歌“如何测试Java中的私有方法”时,最受欢迎的答案是“为什么要测试私有方法?用户调用公共方法,测试它们。”

对我来说,这听起来很疯狂。我肯定可以忽略公共方法调用私有方法的事实,只是想象私有方法的代码被内联,但这意味着每个调用私有方法的公共方法都会测试私有方法的代码。

在下面的示例中,你真的想测试私有方法,而不是两个几乎相同的公共方法。

有人可能会注意到,一些重构可以使我不需要测试私有方法。这是正确的,我们稍后会讨论它。但是测试_set_status而没有任何更改仍然是一种清洁和合理的方法。我不相信“不要测试私有方法”。

直接调用它

调用私有方法的最简单和最直接的方法是,你知道,直接调用它。这正是我们在Python和其他有“按照约定私有”的语言(如Perl)中所做的。


class Entity(object):
  # ...

  def set_status_processing(self):
    return self._set_status('processing')

  def set_status_processed(self):
    return self._set_status('processed')

  def _set_status(self, status):
    self._status = status
    self._history.add(status)
    self._log('...'.format(status))

在Java中,“直接调用它”的方法是改变方法的可见性。

第一种方法是将方法设置为“包私有”(没有访问修饰符),并将测试放置在同一个包中。这是一种相当常见的做法,但你仍然可能希望(或已经有)另一种代码结构。

第二种方法是将方法设置为公共的。为了让人们知道你仍然不想调用这个方法,你可以使用Guava的@VisibleForTesting注解或你喜欢的任何其他约定(这实际上是Python和Perl所做的)。顺便说一下,IDEA完全理解该注解,并会警告你在测试之外使用这个公共方法。

嵌套类

你也可以在测试的类中放置一个测试类(至少在Java中是这样),但这看起来并不好。你必须在同一个文件中使用两个类,并且你的生产二进制文件实际上包含了测试代码。

反射

在一些语言中,反射可以很好地完成,在Ruby中就很简单:


class User
    private
    def set_state()
        # ...
    end
end

User.new.send(:set_state)

但在Java中,这样的代码非常繁重,我放弃了在这里提供一个例子。更重要的是,你必须放弃你最喜欢的IDE为你做的所有酷炫的东西。

消除私有

个人而言,我更喜欢“直接调用它”的方法,并喜欢在Java中使用@VisibleForTesting来实现它。

但是,让我们再次讨论一下通过消除测试私有方法来避免测试私有方法的重构(通过消除它们)。

关键是将所有私有数据和私有方法合并到另一个类的对象中,该类不包含私有方法或数据,但将该类的实例放入原始类的唯一私有属性中。听起来并不简单,但是请考虑以下示例:

之前:

class Entity {
    enum Status { PROCESSING, PROCESSED };

    private Status status;
    private History history;

    public Entity(Status status) {
        this.status = status;
        this.history = new History();
    }

    void setStatusProcessing() {
        setStatus(Status.PROCESSING);
    }

    void setStatusProcessed() {
        setStatus(Status.PROCESSED);
    }

    private void setStatus(Status status) {
        this.status = status;
        this.history.add(status);
    }
}

之后:

class Entity {
    enum Status { PROCESSING, PROCESSED };

    static class EntityPrivateData {
        public Status status;
        public History history;

        public EntityPrivateData(Status status) {
            this.status = status;
            this.history = new History();
        }

        public void setStatus(Status status) {
            this.status = status;
            this.history.add(status);
        }
    }

    private EntityPrivateData data;

    public Entity(Status status) {
        data = new EntityPrivateData(status);
    }

    public void setStatusProcessing() {
        data.setStatus(Status.PROCESSING);
    }

    public void setStatusProcessed() {
        data.setStatus(Status.PROCESSED);
    }
}

所以现在你可以自由地测试EntityPrivateData,唯一保留的私有内容是data属性。就像我之前说的,你实际上不需要私有方法,只需要私有数据。

所描述的方法不仅对测试私有方法有用,而且对你软件更有表现力的设计也有用。你可以使用任意数量的这种私有数据类,因此它们在语义上更有意义,而不仅仅是技术上。

对我来说,这种模式最重要的一点是它证明你在技术上可以消除所有私有方法。但我仍然怀疑每次都这样做是否合理,你的代码可能会膨胀而没有任何显著的好处。

本文是在Nikolay Rys的帮助下撰写的。

参见

Testing private methods: easier than you think! - Axel Fontaine - Entrepreneur, Architect…

How do I test a class that has private methods, fields or inner classes?

译自:https://medium.com/@vadimpushtaev/how-to-test-private-methods-4bc57d4410ff

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
阿波
The minute I see you, I want your clothes gone!

评论(0)

添加评论