C# 单元测试和检查私有变量值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1093020/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 08:02:57  来源:igfitidea点击:

Unit testing and checking private variable value

c#unit-testingnunit

提问by TonE

I am writing unit tests with C#, NUnit and Rhino Mocks. Here are the relevant parts of a class I am testing:

我正在用 C#、NUnit 和 Rhino Mocks 编写单元测试。以下是我正在测试的课程的相关部分:

public class ClassToBeTested
{
    private IList<object> insertItems = new List<object>();

    public bool OnSave(object entity, object id)
    {
        var auditable = entity as IAuditable;
        if (auditable != null) insertItems.Add(entity);

        return false;            
    }
}

I want to test the values in insertItems after a call to OnSave:

我想在调用 OnSave 后测试 insertItems 中的值:

[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
     Setup();

     myClassToBeTested.OnSave(auditableObject, null);

     // Check auditableObject has been added to insertItems array            
}

What is the best practice for this? I have considered adding insertItems as a Property with a public get, or injecting a List into ClassToBeTested, but not sure I should be modifying the code for purposes of testing.

这方面的最佳做法是什么?我曾考虑将 insertItems 作为属性添加到公共 get,或将 List 注入 ClassToBeTested,但不确定我是否应该修改代码以进行测试。

I have read many posts on testing private methods and refactoring, but this is such a simple class I wondered what is the best option.

我已经阅读了很多关于测试私有方法和重构的文章,但这是一个如此简单的类,我想知道什么是最好的选择。

采纳答案by Mark Seemann

The quick answer is that you should never, ever access non-public members from your unit tests. It totally defies the purpose of having a test suite, since it locks you into internal implementation details that you may not want to keep that way.

快速回答是你永远不应该从你的单元测试中访问非公共成员。它完全违背了拥有测试套件的目的,因为它会将您锁定在您可能不想保持这种方式的内部实现细节中。

The longer answer relates to what to do then? In this case, it is important to understand why the implementation is as it is (this is why TDD is so powerful, because we use the tests to specifythe expected behavior, but I get the feeling that you are not using TDD).

更长的答案与接下来该怎么做有关?在这种情况下,重要的是要了解实现为何如此(这就是 TDD 如此强大的原因,因为我们使用测试来指定预期的行为,但我感觉您没有使用 TDD)。

In your case, the first question that comes to mind is: "Why are the IAuditable objects added to the internal list?" or, put differently, "What is the expected externally visibleoutcome of this implementation?" Depending on the answer to those questions, that'swhat you need to test.

在您的情况下,想到的第一个问题是:“为什么将 IAuditable 对象添加到内部列表中?” 或者,换句话说,“此实施的预期外部可见结果是什么?” 根据这些问题的答案,就是您需要测试的内容。

If you add the IAuditable objects to your internal list because you later want to write them to an audit log (just a wild guess), then invoke the method that writes the log and verify that the expected data was written.

如果您将 IAuditable 对象添加到您的内部列表中,因为您以后想将它们写入审计日志(只是一个疯狂的猜测),然后调用写入日志的方法并验证是否写入了预期的数据。

If you add the IAuditable object to your internal list because you want to amass evidence against some kind of later Constraint, then try to test that.

如果您将 IAuditable 对象添加到您的内部列表中,因为您想收集针对某种稍后约束的证据,请尝试对其进行测试。

If you added the code for no measurable reason, then delete it again :)

如果您无缘无故地添加了代码,请再次删除它:)

The important part is that it is very beneficial to test behaviorinstead of implementation. It is also a more robust and maintainable form of testing.

重要的部分是测试行为而不是实现是非常有益的。它也是一种更健壮和可维护的测试形式。

Don't be afraid to modify your System Under Test (SUT) to be more testable. As long as your additions make sense in your domain and follow object-oriented best practices, there are no problems - you would just be following the Open/Closed Principle.

不要害怕修改您的被测系统 (SUT) 以提高可测试性。只要您的添加在您的领域中有意义并遵循面向对象的最佳实践,就没有问题 -您只需遵循开放/封闭原则

回答by Esteban Araya

You shouldn't be checking the list where the item was added. If you do that, you'll be writing a unit test for the Add method on the list, and not a test for yourcode. Just check the return value of OnSave; that's really all you want to test.

您不应该检查添加项目的列表。如果这样做,您将为列表中的 Add 方法编写单元测试,而不是为您的代码编写测试。只需检查 OnSave 的返回值;这就是您想要测试的全部内容。

If you're really concerned about the Add, mock it out of the equation.

如果您真的很关心 Add,请将其排除在外。

Edit:

编辑:

@TonE: After reading your comments I'd say you may want to change your current OnSave method to let you know about failures. You may choose to throw an exception if the cast fails, etc. You could then write a unit test that expects and exception, and one that doesn't.

@TonE:在阅读您的评论后,我会说您可能想要更改当前的 OnSave 方法以让您了解失败。如果强制转换失败等,您可以选择抛出异常。然后您可以编写一个单元测试,期望和异常,一个不期望。

回答by Yishai

I would say the "best practice" is to test something of significance with the object that is different now that it stored the entity in the list.

我会说“最佳实践”是用现在不同的对象测试一些重要的东西,因为它将实体存储在列表中。

In other words, what behavior is different about the class now that it stored it, and test for that behavior. The storage is an implementation detail.

换句话说,类现在存储它的行为有何不同,并测试该行为。存储是一个实现细节。

That being said, it isn't always possible to do that.

话虽如此,但并非总是可以做到这一点。

You can use reflectionif you must.

如果必须,您可以使用反射

回答by Jeff Sternal

If I'm not mistaken, what you really want to test is that it only adds items to the list when they can be cast to IAuditable. So, you might write a few tests with method names like:

如果我没记错的话,您真正想要测试的是它仅在可以将项目强制转换为IAuditable. 因此,您可能会使用方法名称编写一些测试,例如:

  • NotIAuditableIsNotSaved
  • IAuditableInstanceIsSaved
  • IAuditableSubclassInstanceIsSaved
  • NotIAuditableIsNotSaved
  • IAuditableInstanceIsSaved
  • IAuditableSubclassInstanceIsSaved

... and so forth.

……等等。

The problem is that, as you note, given the code in your question, you can only do this by indirection - only by checking the private insertItems IList<object>member (by reflection or by adding a property for the sole purpose of testing) or injecting the list into the class:

问题是,正如您所指出的,鉴于您的问题中的代码,您只能通过间接方式来做到这一点 - 只能通过检查私有insertItems IList<object>成员(通过反射或添加仅用于测试目的的属性)或将列表注入班上:

public class ClassToBeTested
{
    private IList _InsertItems = null;

    public ClassToBeTested(IList insertItems) {
      _InsertItems = insertItems;
    }
}

Then, it's simple to test:

然后,测试很简单:

[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
     Setup();

     List<object> testList = new List<object>();
     myClassToBeTested     = new MyClassToBeTested(testList);

     // ... create audiableObject here, etc.
     myClassToBeTested.OnSave(auditableObject, null);

     // Check auditableObject has been added to testList
}

Injection is the most forward looking and unobtrusive solution unless you have some reason to think the list would be a valuable part of your public interface (in which case adding a property might be superior - and of course property injection is perfectly legit too). You could even retain a no-argument constructor that provides a default implementation (new List()).

注入是最具有前瞻性和不引人注目的解决方案,除非您有理由认为列表将成为公共接口的重要部分(在这种情况下,添加属性可能更好——当然,属性注入也是完全合法的)。您甚至可以保留一个提供默认实现的无参数构造函数 (new List())。

It is indeed a good practice; It might strike you as a bit overengineered, given that it's a simple class, but the testability alone is worth it. Then on top of that, if you find another place you want to use the class, that will be icing on the cake, since you won't be limited to using an IList (not that it would take much effort to make the change later).

这确实是一个很好的做法;鉴于它是一个简单的类,它可能会让您觉得有点过度设计,但仅凭可测试性就值得。然后最重要的是,如果您找到另一个想要使用该类的地方,那将锦上添花,因为您将不仅限于使用 IList(并不是说以后进行更改会花费很多精力) )。

回答by kyoryu

If the list is an internal implementation detail (and it seems to be), then you shouldn't test it.

如果列表是内部实现细节(而且似乎是),那么您不应该测试它。

A good question is, what is the behavior that would be expected if the item was added to the list? This may require another method to trigger it.

一个很好的问题是,如果将项目添加到列表中,预期的行为是什么?这可能需要另一种方法来触发它。

    public void TestMyClass()
    {
        MyClass c = new MyClass();
        MyOtherClass other = new MyOtherClass();
        c.Save(other);

        var result = c.Retrieve();
        Assert.IsTrue(result.Contains(other));
    }

In this case, i'm asserting that the correct, externally visible behavior, is that after saving the object, it will be included in the retrieved collection.

在这种情况下,我断言正确的、外部可见的行为是在保存对象后,它将包含在检索到的集合中。

If the result is that, in the future, the passed-in object should have a call made to it in certain circumstances, then you might have something like this (please forgive pseudo-API):

如果结果是,将来传入的对象在某些情况下应该调用它,那么您可能会遇到这样的事情(请原谅伪API):

    public void TestMyClass()
    {
        MyClass c = new MyClass();
        IThing other = GetMock();
        c.Save(other);

        c.DoSomething();
        other.AssertWasCalled(o => o.SomeMethod());
    }

In both cases, you're testing the externally visible behavior of the class, not the internal implementation.

在这两种情况下,您都在测试类的外部可见行为,而不是内部实现。

回答by Chris Golledge

The number of tests you need is dependent on the complexity of the code - how many decision points are there, roughly. Different algorithms can achieve the same result with different complexity in their implementation. How do you write a test that is independent of the implementation and still be sure you have adequate coverage of your decision points?

您需要的测试数量取决于代码的复杂性 - 大致有多少决策点。不同的算法可以达到相同的结果,但其实现的复杂度不同。你如何编写一个独立于实现的测试,并且仍然确保你有足够的决策点覆盖?

Now, if you are designing larger tests, at say the integration level, then, no, you would not want to write to implementation or test private methods, but the question was directed to the small, unit test scope.

现在,如果您正在设计更大的测试,比如在集成级别,那么,不,您不会想要编写实现或测试私有方法,但问题是针对小的单元测试范围。