单元测试私有方法:外观模式

Posted

技术标签:

【中文标题】单元测试私有方法:外观模式【英文标题】:Unit-testing private methods: Facade pattern 【发布时间】:2009-01-08 20:13:43 【问题描述】:

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

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

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

当我测试私有方法时:

    我不会发布会让用户感到困惑的细节。公共接口包括 2 个方法。 我可以以 TDD 风格工作(逐步编写小方法)。 我可以使用测试数据覆盖大部分课程的功能,而无需连接数据库。

谁能描述一下,我做错了什么?我应该使用什么设计来获得相同的奖金并且不测试私有方法?

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

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

【问题讨论】:

似乎已经有一个与此相同的问题:***.com/questions/250692/… 这是关于在 C# 中测试私有方法的博客coolaspdotnetcode.com/Web/… 外观不是一个实现,因此您对外观的测试不应该测试外观运行的所有实现是否有效。如果有的话,您可以通过外观运行一些系统/集成测试,但您的单元应该单独测试。然后你可以测试这些东西是否被正确地粘合在一起(例如,可以在不爆炸的情况下运行),但是当你处理更高级别的抽象时不要担心这些小事情。 【参考方案1】:

我认为您在外观中投入了太多责任(实施)。我通常会认为这是其他类中实际实现的前端。

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

【讨论】:

请查看更新。这个类的职责是持有相当复杂的数据结构和例程来进行搜索、转换等。其他所有东西都被提取到外部。所以,Facade 本身可以被提取,但我不认为其他任何东西也可以被提取。【参考方案2】:

谁能描述一下,我是什么 做错了吗?

也许什么都没有?

如果我想测试一个方法,我将其设为默认(包)范围并对其进行测试。

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

【讨论】:

【参考方案3】:

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

可能只是您的课程非常复杂,需要花费大量精力来测试它。但是,我建议您寻找可以分解为自己的类的抽象。这些类将具有较小的项目范围和要测试的复杂性。

【讨论】:

【参考方案4】:

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

【讨论】:

【参考方案5】:

如果你花时间看看 Clean Code Tech talks 来自米什科。他对如何编写代码以进行测试非常有洞察力。

【讨论】:

【参考方案6】:

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

如果您使用 Java,这是我编写的一个实用方法,可以帮助您测试类中的静态私有方法:

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;

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

【讨论】:

【参考方案7】:

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

在 C# 中,我会将此类方法设为受保护而不是私有,并将它们公开在子类中以供测试

考虑到您的情况,将可测试方法公开并让用户拥有真正的外观可能更有意义,其中只有他们接口所需的 2 个公共方法

【讨论】:

以上是关于单元测试私有方法:外观模式的主要内容,如果未能解决你的问题,请参考以下文章

深入 Laravel 内核之外观模式(门面模式)

Python设计模式-外观模式

设计模式之外观模式

设计模式:学习笔记(11)——外观模式

设计模式之外观模式

Java设计模式学习记录-外观模式