单元测试和OO中的访问修饰符

3

我注意到为了单元测试面向对象代码中的每个单元,我需要将访问修饰符设置为public,即使是应该是protected或private的方法。这样做是否正确?

public class EnforceBusinessRules
{
    BusinessState m_state;

    public EnforceBusinessRules()
    {
        m_state = START;
    }


    public bool isInputcurrentlyFormatted(string input)
    {
        //code goes here to ensure the input passes formatting test
        //modify m_state appropriately
    }


    public bool InputContainsValidStartAndEndTokens(string input)
    {
        //code goes here to ensure that the start and end tokens of the input are of the type available in the system
        //modify m_state appropriately
    }


    public bool StartEndCommandisValidAccordingtoCurrentSystemSettings(string input)
    {
        //code goes here to check the start and End codes match the current start and end codes for the day
        //modify m_state appropriately
    }

    // and so on 
}

4
可能不需要。为什么需要直接测试私有方法(即不属于API的方法)?如果它们足够复杂需要单独进行测试,那么很可能需要将其拆分成一个单独的类来处理该行为... - Oliver Charlesworth
这个问题如果您可以发布您已经编写的代码,那么会更好。 - Rob Kielty
6个回答

8

单元测试是“黑盒”测试。您应该只测试外部可见的元素。如果测试所有内部工作,则无法正确地重构代码而不修改所有单元测试。

注:Black box testing(黑盒测试)指测试者只关注输入输出,不考虑内部实现细节的一种测试方法。

那么你的意思是说,不直接对我想保持私有的方法进行单元测试是可以的? - Kobojunkie
如果你的公共方法返回正确的值,那么你可以推断出你的私有方法也返回了正确的值。此外,它允许你更改这些私有方法而不更改测试。如果它们是错误的,它们应该破坏公共实现。 - Liam
2
实际上,单元测试是白盒测试。验收测试是黑盒测试。但在两者中,你只测试可见的(公共)API。 - Danny Varod

1

如果您发现需要更改访问修饰符,因为您有大量的私有成员代码需要测试,那么这可能是您的类太大的一个迹象。考虑将要测试的行为分解为具有公共接口的较小类,以便进行测试。您的代码可能正在遭受God Object代码气味。

请记住,受保护的成员与公共成员非常相似,只是可能的客户端空间更小(仅派生类)。您可以通过创建仅您的测试使用的派生对象来考虑对这些方法进行单元测试。通过这种方式,您将以客户端使用它们的方式测试这些方法。

以下是您可能修改代码的一种方式:

public class EnforceBusinessRules
{
    BusinessState m_state;

    public EnforceBusinessRules(IEnumerable<Rule> rules)
    {
        m_state = START;
    }

    public void Enforce(string input)
    {
        foreach (var rule in rules)
        {
            m_state = rule.EnforceRule(input);
        }
    }
}

public interface Rule
{
    public BusinessState EnforceRule(string input);
}

public class IsInputcurrentlyFormatted : Rule
{
    public BusinessState EnforceRule(string input)
    {
        //code goes here to ensure the input passes formatting test
    }
}

public class InputContainsValidStartAndEndTokens : Rule
{
    public BusinessState EnforceRule(string input)
    {
        //code goes here to ensure the input passes formatting test
    }
}

public class StartEndCommandisValidAccordingtoCurrentSystemSettings : Rule
{
    public BusinessState EnforceRule(string input)
    {
        //code goes here to ensure the input passes formatting test
    }
}

// and so on

嗯,我有很多私有方法,因为在处理输入时,我必须考虑很多条件。给定一个输入为18,我有大约20个规则来确保输入的通过。 - Kobojunkie
@Kobojunkie 为处理输入创建一个内部类。 - Danny Varod
甚至为每条规则创建一个类可能是值得的。然后,如果这些规则需要在不同的上下文中使用,它们将会完全独立于当前驱动它们的代码进行测试。 - Josh Peterson

0

不,我认为你的想法是错误的。你可以在不公开信息的情况下推断出一些东西。例如,属性的值可以从函数返回中实现。

对于受保护的成员来说,问问自己这意味着什么。这意味着派生类可以访问该成员。因此,在测试中创建一个派生类型。


0

看起来你没有成功创建getter和setter方法,以便正确封装私有数据。

单元测试应该通过getter和setter访问数据。

避免在类代码中实例化协作对象,而是注入这些依赖项


1
这些是代表我的流程各个步骤的方法。我决定将其分解,以便将步骤分成单元。 - Kobojunkie

0
如果您需要测试私有或受保护的属性或方法,则应将功能提取到单独的类中。每个类只应执行一项任务(单一职责原则)。很可能您的私有方法会在主要目的之外执行辅助功能。
提取后,会发生两件好事情:首先,您希望测试的代码现在可以完全由单元测试访问。其次,在EnforceBusinessRules类中就不必再担心它了。

0
通常,将不希望暴露给外部世界的方法/类/属性等暴露出去是一种不好的做法。我们在工作中遇到了类似的问题。我们编写了测试来调用公共方法和属性,因为这些公共方法应该在某个时候调用所有私有方法。对于具有内部类/属性/方法的情况,您可以使用InternalsVisableTo属性使它们对您的单元测试库可访问。
[assembly: InternalsVisibleTo("AssemblyB")]

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx


这通常是一个坏主意。虽然这可以解决问题,而且我自己也曾这样做过,但应尽可能避免。 - Liam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接