关于tdd:单元测试私有方法:Facade模式

Unit-testing private methods: Facade pattern

许多开发人员认为测试私有方法是个坏主意。但是,我发现的所有示例都基于私有方法是私有的,因为调用它们可能会破坏内部对象的状态。但这不仅是隐藏方法的理由。

我们来看看Facade模式。我的班级用户需要2种公共方法。它们太大了。在我的示例中,他们需要从数据库的BLOB加载一些复杂的结构,解析它,填充一些临时COM对象,运行用户的宏来验证和修改这些对象,以及将修改后的对象序列化为XML。单个metod的相当大的功能:-)这两个公共方法都需要这些操作。所以,我创建了大约10个私有方法,并且有2个公共方法可以调用它们。实际上,我的私人方法不一定是私人的;他们不会破坏实例的内部状态。但是,当我不习惯测试私有方法时,我有以下问题:

  • 发布它们意味着用户的复杂性(他们有一个他们不需要的选择)
  • 我无法想象TDD风格对于如此大的公共方法,当你要编写500多行代码只是为了返回一些东西(甚至不是真正的结果)。
  • 从数据库中检索这些方法的数据,并且测试与DB相关的功能要困难得多。
  • 当我测试私有方法时:

  • 我不会发布会让用户感到困惑的细节。公共接口包括2种方法。
  • 我可以用TDD风格工作(逐步编写小方法)。
  • 我可以使用测试数据覆盖大部分类的功能,而无需数据库连接。
  • 有人可以形容,我做错了什么?我应该使用什么样的设计来获得相同的奖金,而不是测试私人方法?

    更新:在我看来,我已经提取了我能够进行的其他课程。所以,我无法想象我还能提取什么。从数据库加载由ORM层执行,解析流,序列化为XML,运行宏 - 一切都由独立类完成。该类包含非常复杂的数据结构,搜索和转换的例程,以及对所有提到的实用程序的调用。所以,我不认为可以提取其他东西;否则,它的责任(关于数据结构的知识)将在类之间划分。

    所以,我现在看到的最好的解决方法是分成两个对象(Facade本身和真实对象,私有方法变为公共)并将真实对象移动到某个地方,没人会试图找到它。在我的情况下(Delphi)它将是一个独立的单元,在其他语言中它可以是一个单独的名称空间。其他类似的选项是2个接口,感谢您的想法。


    我认为你将太多的责任(实施)放入外观。我通常认为这是其他类中实际实现的前端。

    因此,Facade中的私有方法可能是一个或多个其他类中的公共方法。然后你可以在那里测试它们。


    Could somebody describe, what am I
    doing wrong?

    也许没什么?

    如果我想测试一个方法,我将它作为默认(包)范围并测试它。

    您已经提到了另一个好的解决方案:使用两种方法创建一个接口。您的客户端访问这两种方法,其他方法的可见性无关紧要。


    私有方法用于封装一些在您尝试测试的类之外没有任何意义的行为。您永远不必测试私有方法,因为只有同一类的public或protected方法才会调用私有方法。

    可能只是你的课非常复杂,需要付出很大的努力来测试它。但是,我建议你寻找可以在他们自己的课程中突破的抽象。这些类将具有较小的项目范围和测试的复杂性。


    假设您有8个私有方法和2个公共方法。如果您可以独立执行私有方法,即不调用任何其他方法,并且没有破坏状态的副作用,那么单元测试该方法是有意义的。但在那种情况下,该方法不需要是私有的!

    在C#中我会使这些方法受到保护而不是私有,并在子类中公开它们以进行测试

    根据您的方案,将可测试方法公开并让用户拥有真正的外观可能更有意义,只需要他们的界面需要2种公共方法


    这是一个有点争议的话题......大多数TDD人认为重构你的方法以便更容易进行单元测试实际上会让你的设计变得更好。我认为这通常是正确的,但公共API的私有方法的特定情况绝对是一个例外。所以,是的,你应该测试私有方法,不,你不应该公开它。

    如果你在Java中工作,这里是我编写的一个实用工具方法,它将帮助你测试类中的静态私有方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import junit.framework.Assert;

    public static Object invokeStaticPrivateMethod(Class< ? > clazz, String methodName, Object... params) {
        Assert.assertNotNull(clazz);
        Assert.assertNotNull(methodName);
        Assert.assertNotNull(params);

        // find requested method
        final Method methods[] = clazz.getDeclaredMethods();
        for (int i = 0; i < methods.length; ++i) {
            if (methodName.equals(methods[i].getName())) {
                try {
                    // this line makes testing private methods possible :)
                    methods[i].setAccessible(true);
                    return methods[i].invoke(clazz, params);
                } catch (IllegalArgumentException ex) {
                    // maybe method is overloaded - try finding another method with the same name
                    continue;
                } catch (IllegalAccessException ex) {
                    Assert.fail("IllegalAccessException accessing method '" + methodName +"'");
                } catch (InvocationTargetException ex) {
                    // this makes finding out where test failed a bit easier by
                    // purging unnecessary stack trace
                    if (ex.getCause() instanceof RuntimeException) {
                        throw (RuntimeException) ex.getCause();
                    } else {
                        throw new InvocationException(ex.getCause());
                    }
                }
            }
        }

        Assert.fail("method '" + methodName +"' not found");
        return null;
    }

    这也许可以为非静态方法重写,但那些讨厌的私有方法通常是私有的,所以我从来不需要。 :)


    也许如果你花时间看看
    来自Mi?ko的Clean Code Tech会谈。他非常了解如何编写代码以进行测试。


    我不熟悉您的要求和设计,但似乎您的设计是程序性的而不是面向对象的。即你有2个公共方法和许多私有方法。如果将类分解为每个对象都有其角色的对象,则更容易测试每个"小"类。此外,您可以将"帮助程序"对象的访问级别设置为打包(Java中的默认值,我知道C#中存在类似的访问级别),这样您就不会在API中公开它们,但您可以单独对它们进行单元测试(如他们是单位)。