简体   繁体   English

使方法“内部”去除依赖性(用于单元测试) - 一个好的做法? 有更好的方法吗?

[英]Making methods 'internal' to remove dependencies (for unit tests) - a good practice? Any better way?

I have a class as follows. 我有一个课程如下。

public class MyClass  
{  
    public MyMethod()  
    {  
      int x = CalculateSomething();  
    }

   private int CalculateSomething()  
   {  
      // Do something and return an int    
      return 100;  
   }  
}  

To unit test this I added [assembly: InternalsVisibleTo("MyTests")] and changed the private method to be internal virtual. 为了对单元测试,我添加了[assembly: InternalsVisibleTo("MyTests")]并将私有方法更改为内部虚拟。 In the unit test project I created a class MockMyClass and created private method as follows. 在单元测试项目中,我创建了一个类MockMyClass并创建了如下的私有方法。

public class MockMyClass : MyClass  
{  
   public bool MadeHappyNoise {get; set;}
   internal override int CalculateSomething()  
   {  
      MadeHappyNoise = true;  
      return base.CalculateSomething();  
   }  
}

The unit test is now as follows 单元测试现在如下

[TestMethod()]
public void WasCalculateSomethingCalledOK()  
{  
   MockMyClass mk = new MockMyClass();
   mk.MyMethod();  
   Assert.IsTrue(mk.MadeHappyNoise, "Oops...CalculateSomething not called...");  
}  

Few questions: Is this is a good way to remove dependencies? 几个问题:这是删除依赖关系的好方法吗? I personally don't like to change a method from private to internal but have no option (other than to use Reflection perhaps). 我个人不喜欢将方法从私有更改为内部但没有选项(除了使用Reflection之外)。 Also, the attribute InternalsVisibleTo("MyTests") residing in the production code is not good. 此外,驻留在生产代码中的属性InternalsVisibleTo(“MyTests”)也不好。 Can someone point me to a better solution please? 有人能指出我更好的解决方案吗? Thanks. 谢谢。

Hmm. 嗯。 I have some issues with that code, but we'll do one at a time. 我对该代码有一些问题,但我们会一次做一个。

  1. Why would you want to test if MyMethod calls CalculateSomething? 你为什么要测试MyMethod是否调用CalculateSomething? It's an implementation detail that is probably likely to change (what if it calls CalculateSomething2 tomorrow but apart from that still does what it's supposed to do?). 这是一个可能会改变的实现细节(如果它明天调用CalculateSomething2但除此之外还会做它应该做的事情?)。 If you want to test the code structure of MyMethod, do a code review, not a unit test. 如果要测试MyMethod的代码结构,请执行代码审查,而不是单元测试。

  2. You say that MyMethod is complex and you want to test the code flow inside. 你说MyMethod很复杂,你想测试里面的代码流。 If there are multiple paths inside, you still have to write a unit test for each path, so why can't you check the result of calling MyMethod instead of checking the inside of it? 如果里面有多个路径,你仍然需要为每个路径编写单元测试,那么为什么不能检查调用MyMethod的结果而不是检查它的内部?

    Another thought would be to try and refactor MyMethod into methods that lend themselves to easier testing (that's almost automatic if you do test-driven-development, a practice I recommend if you want to do serious unit testing. The "test later" approach almost always leads to code that is much more difficult to test). 另一个想法是尝试将MyMethod重构为方法,使其更容易进行测试(如果你进行测试驱动开发,这几乎是自动的,如果你想进行严格的单元测试,我推荐这种做法。“稍后测试”方法几乎总是导致代码更加难以测试)。

  3. If you still want to check the inner workings of MyMethod, maybe you can refactor the private methods you need to check this into another class (say "Calculations" in your example). 如果你仍然想检查MyMethod的内部工作原理,也许你可以重构你需要的私有方法来检查另一个类(在你的例子中说“计算”)。

    Then you can use a mock framework (like RhinoMocks for example), to mock that class. 然后你可以使用模拟框架(比如RhinoMocks )来模拟那个类。 The framework lets you define what functions you expect to be called in what order and what they should return. 该框架允许您定义您希望以什么顺序调用哪些函数以及它们应该返回什么。

    Usually you use mocks to lessen the environment requirements for unit tests, but you can use them in this way also. 通常你使用模拟来减少单元测试的环境要求,但你也可以这样使用它们。

Can you maybe refactor it to be like this: 你可以重构它是这样的:

public class MyClass  
{  
    private Calculator calculator;  

    public myMethod()  
    {  
      int x = calculateSomething();  
    }

    public void SetCalculator( Calculator c ){
        calculator = c;  
    }

   private int calculateSomething()  
   {  
        return calculator.CalculateSomething();
   }  
}  

And then have calculator as a separate class and set an instance on MyClass 然后将计算器作为一个单独的类并在MyClass上设置一个实例

public Class Calculator {

   public virtual int CalculateSomething()  
   {  
      // Do something and return an int    
      return 100;  
   }  
}

You could make Calculator implement an interface and then have a different Calculator implementation or a mock that you use in your tests. 您可以使Calculator实现一个接口,然后使用不同的Calculator实现或您在测试中使用的模拟。

It rather depends on the methods you are changing the scope of. 它取决于你改变范围的方法。 A unit is the smallest testable component of a piece of software - it rarely means one test per method. 单元是一个软件中最小的可测试组件 - 它很少意味着每个方法一个测试。

I find that comprehensively testing my public methods is enough to establish correct behaviour. 我发现全面测试我的公共方法足以建立正确的行为。 You might find that your tests start to constrain your code development if you wrap the private methods with tests. 如果使用测试包装私有方法,您可能会发现测试开始限制代码开发。

If your program is more procedural you might find that you need to test at the granular level you describe, in which case using friend assemblies is a fine solution. 如果您的程序更具程序性,您可能会发现需要在您描述的粒度级别进行测试,在这种情况下使用友元组件是一个很好的解决方案。 However, I'd suggest that you would rarely need to test methods that aren't public. 但是,我建议您很少需要测试非公开的方法。

Too much work for too little value. 价值太低的太多工作。 All that test tells me (if it passes) is that calling MyMethod calls another private method. 所有测试告诉我(如果它通过)是调用MyMethod调用另一个私有方法。 The unit test should be testing the behavior provided by MyMethod() - what should happen/change after a call to MyMethod? 单元测试应该测试MyMethod()提供的行为 - 调用MyMethod后会发生什么/改变? .

The title of the question is a bit misleading too - there is no dependency-related issue that I can see. 这个问题的标题也有点误导 - 我没有看到与依赖相关的问题。

You do not need InternalsVisibleTo for the most part.. simply test the private method through the public method that exercises it eg Write unit tests for MyMethod(). 大多数情况下你不需要InternalsVisibleTo。只需通过运行它的公共方法测试私有方法,例如为MyMethod()编写单元测试。 Also if you practice test-first programming, you'd already have tests that cover every line of code in the private method. 此外,如果您练习测试优先编程,您已经拥有涵盖私有方法中每行代码的测试。

If this is a piece of legacy code that you are too scared to touch, i would advise you to create a unit test which would construct MyClass. 如果这是一段您不敢触及的遗留代码,我建议您创建一个构建MyClass的单​​元测试。 Tentatively create a public property in MyClass to expose the value of x. 暂时在MyClass中创建一个公共属性以公开x的值。

In the unit test just created assert that value of x is 100 after MyClass is instantiated. 在单元测试中刚刚创建断言,在实例化MyClass之后x的值为100。 Once you have that in place, refactor like @alb suggests. 一旦你有了这个,像@alb建议的重构。 Run the test again, make sure x is still 100 and test the calculator class separately and eventually remove the tentative public property for x in MyClass. 再次运行测试,确保x仍为100并单独测试计算器类,最终删除MyClass中x的暂定公共属性。 hope that helps. 希望有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM