模式或做法进行单元测试方法调用静态方法 [英] Patterns or practices for unit testing methods that call a static method

查看:1176
本文介绍了模式或做法进行单元测试方法调用静态方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

截至晚了,我一直在大力琢磨一下最好的方式来模拟一个是从,我想测试一个类调用静态方法。就拿下面的代码:

As of late, I have been pondering heavily about the best way to "Mock" a static method that is called from a class that I am trying to test. Take the following code for example:

using (FileStream fStream = File.Create(@"C:\test.txt"))
{
    string text = MyUtilities.GetFormattedText("hello world");
    MyUtilities.WriteTextToFile(text, fStream);
}



据我所知,这是一个相当糟糕的例子,但它有三个静态方法调用,都稍有不同。该File.Create功能访问文件系统和我不拥有该功能。该MyUtilities.GetFormattedText是我自己的一个功能,它纯粹是无状态的。最后,MyUtilities.WriteTextToFile是我自己的一个功能,它访问文件系统。

I understand that this is a rather bad example, but it has three static method calls that are all different slightly. The File.Create function access the file system and I don't own that function. The MyUtilities.GetFormattedText is a function that I own and it is purely stateless. Finally, the MyUtilities.WriteTextToFile is a function I own and it accesses the file system.

我一直在思考最近是,如果这是遗留代码,我怎么可以重构它,使之更单元测试。我听到几个论点,静态函数不应该使用,因为它们很难测试。我有这个想法不同意,因为静态函数是有用的,我不认为,仅仅因为正在使用的测试框架不能处理得很好一个有用的工具,应该被丢弃。

What I have been pondering lately is if this were legacy code, how could I refactor it to make it more unit-testable. I have heard several arguments that static functions should not be used because they are hard to test. I disagree with this idea because static functions are useful and I don't think that a useful tool should be discarded just because the test framework that is being used can't handle it very well.

很多搜索和深思熟虑之后,我得出的结论是,基本上有 4的模式或可使用才能使调用静态函数单元测试的功能的做法。这些措施包括以下内容:

After much searching and deliberation, I have come to the conclusion that there are basically 4 patterns or practices that can be used in order to make functions that call static functions unit-testable. These include the following:


  1. 不要嘲笑的静态所有功能,只是让单元测试呼叫吧。

  2. 裹在实现与您需要它,然后使用依赖注入在你的类使用它的功能的接口的实例类的静态方法。我将把这个作为接口的依赖注入

  3. 使用摩尔(或TypeMock)劫持函数调用。

  4. 使用dependeny注射的功能。我将把这个作为函数依赖注入

  1. Don't mock the static function at all and just let the unit test call it.
  2. Wrap the static method in an instance class that implements an interface with the function that you need on it and then use dependency injection to use it in your class. I'll refer to this as interface dependency injection.
  3. Use Moles (or TypeMock) to hijack the function call.
  4. Use dependeny injection for the function. I'll refer to this as function dependency injection.

我听说过不少讨论关于前三的做法,但正如我在思考这个问题的解决方案,第四想法来到我的函数依赖注入。这类似于隐藏的接口后的静态功能,但没有实际需要创建一个接口和包装类。这方面的一个例子是以下内容:

I've heard quite a lot of discussion about the first three practices, but as I was thinking about solutions to this problem, the forth idea came to me of function dependency injection. This is similar to hiding a static function behind an interface, but without actually needing to create an interface and wrapper class. An example of this would be the following:

public class MyInstanceClass
{
    private Action<string, FileStream> writeFunction = delegate { };

    public MyInstanceClass(Action<string, FileStream> functionDependency)
    {
        writeFunction = functionDependency;
    }

    public void DoSomething2()
    {
        using (FileStream fStream = File.Create(@"C:\test.txt"))
        {
            string text = MyUtilities.GetFormattedText("hello world");
            writeFunction(text, fStream);
        }
    }
}



有时,创建一个接口,为静态函数调用的包装类可能是麻烦的,它可以污染有很多小班的,其唯一目的是调用一个静态函数您的解决方案。我所有的写代码,容易测试,但这种做法似乎是一个坏的测试框架一种解决方法。

Sometimes, creating an interface and wrapper class for a static function call can be cumbersome and it can pollute your solution with a lot of small classes whose sole purpose is to call a static function. I am all for writing code that is easily testable, but this practice seems to be a workaround for a bad testing framework.

当我在思考这些不同的解决方案,我来,所有的上述4实践可以在不同的情况下可以应用的理解。这里是我的想法是应用上述做法在正确cicumstances:

As I was thinking about these different solutions, I came to an understanding that all of the 4 practices mentioned above can be applied in different situations. Here is what I am thinking is the correct cicumstances to apply the above practices:


  1. 别吨模拟的静态如果它是纯粹无国籍并且不访问系统资源(如文件系统或数据库)的功能。当然,参数可以进行,如果被访问系统资源,那么这个介绍状态进入静态函数无妨。

  2. 使用接口的依赖注入当有您正在使用的几个静态函数都可以在逻辑上被添加到一个单一的接口。这里的关键是,有同时使用几种静态函数。我认为,在大多数情况下,这将不会是这样。有可能只被调用函数中的一个或两个静态函数。

  3. 使用摩尔当你嘲笑了外部库,如UI库或数据库的库(如LINQ to SQL中)。我的意见是,如果痣(或TypeMock)被用来劫持的CLR,以嘲笑自己的代码,那么这是一些重构需要做去耦对象的一个​​指标。

  4. 使用函数依赖注入当有代码的静态函数调用正在测试的一个小数目。这是我在大多数情况下,以测试在我自己的实用工具类调用静态函数的函数倾向​​于格局。

  1. Don't mock the static function if it is purely stateless and does not access system resources (such as the filesystem or a database). Of course, the argument can be made that if system resources are being accessed then this introduces state into the static function anyway.
  2. Use interface dependency injection when there are several static functions that you are using that can all logically be added to a single interface. The key here is that there are several static functions being used. I think that in most cases this will not be the case. There will probably only be one or two static functions being called in a function.
  3. Use Moles when you are mocking up external libraries such as UI libraries or database libraries (such as linq to sql). My opinion is that if Moles (or TypeMock) is used to hijack the CLR in order to mock your own code, then this is an indicator that some refactoring needs to be done to decouple the objects.
  4. Use function dependency injection when there is a small number of static function calls in the code that is being tested. This is the pattern that I am leaning towards in most cases in order to test functions that are calling static functions in my own utility classes.

这是我的想法,但我真的很感激一些这方面的反馈。什么是测试代码,其中一个外部静态函数被调用的最好方法?

These are my thoughts, but I would really appreciate some feedback on this. What is the best way to test code where an external static function is being called?

推荐答案

使用依赖注入(无论是选择2或4)绝对是我的首选进攻这样的方法。它不仅使测试更加简单它有助于分离关注,并保持类的越来越臃肿。

Using dependency injection (either option 2 or 4) is definitely my preferred method of attacking this. Not only does it make testing easier it helps to separate concerns and keep classes from getting bloated.

一个澄清,我需要做虽然是这是不正确的静态方法很难测试。当他们在另一种方法,是用来发生与静态方法的问题。这使得正在调用静态方法难以测试的静态方法不能被嘲笑的方法。这样做的通常的例子是I / O。在您的例子中,你正在写文本文件(WriteTextToFile)。若发现有此方法的过程中失败了怎么办?由于该方法是静态的,它不能被嘲笑,那么你不能根据需要创建这样的情况下失败的案例。如果你创建一个接口,那么你可以模拟调用WriteTextToFile并让它模拟误差。是的,你有几个接口和类,但通常您可以将类似的功能集中在一类逻辑

A clarification I need to make though is it is not true that static methods are hard to test. The problem with static methods occurs when they are used in another method. This makes the method that is calling the static method hard to test as the static method can not be mocked. The usual example of this is with I/O. In your example you are writing text to a file (WriteTextToFile). What if something should fail during this method? Since the method is static and it can't be mocked then you can't on demand create cases such as failure cases. If you create an interface then you can mock the call to WriteTextToFile and have it mock errors. Yes you'll have a few more interfaces and classes but normally you can group similar functions together logically in one class.

无依赖注入:
这是相当多的选项1,其中没有什么是嘲笑。我不认为这是一个可靠的策略,因为它不会让你彻底的测试。

Without Dependency Injection: This is pretty much option 1 where nothing is mocked. I don't see this as a solid strategy because it does not allow you to thoroughly test.

public void WriteMyFile(){
    try{
        using (FileStream fStream = File.Create(@"C:\test.txt")){
            string text = MyUtilities.GetFormattedText("hello world");
            MyUtilities.WriteTextToFile(text, fStream);
        }
    }
    catch(Exception e){
        //How do you test the code in here?
    }
}



依赖注入:

public void WriteMyFile(IFileRepository aRepository){
    try{
        using (FileStream fStream = aRepository.Create(@"C:\test.txt")){
            string text = MyUtilities.GetFormattedText("hello world");
            aRepository.WriteTextToFile(text, fStream);
        }
    }
    catch(Exception e){
        //You can now mock Create or WriteTextToFile and have it throw an exception to test this code.
    }
}

在此的另一面就是你想要的业务逻辑测试失败,如果文件系统/数据库不能读/写?如果我们要测试的数学是我们工资计算正确的,我们不希望IO错误导致测试失败。

On the flip side of this is do you want your business logic tests to fail if the file system/database can't be read/written to? If we're testing that the math is correct in our salary calculation we don't want IO errors to cause the test to fail.

无依赖注入:

这是一个有点奇怪的例子/方法,但我只用它来说明我的观点。

public int GetNewSalary(int aRaiseAmount){
    //Do you really want the test of this method to fail because the database couldn't be queried?
    int oldSalary = DBUtilities.GetSalary(); 
    return oldSalary + aRaiseAmount;
}



依赖注入:

public int GetNewSalary(IDBRepository aRepository,int aRaiseAmount){
    //This call can now be mocked to always return something.
    int oldSalary = aRepository.GetSalary();
    return oldSalary + aRaiseAmount;
}



增加速度是嘲讽的一个额外的振作。 IO是昂贵和减少IO会增加你测试的速度。不必等待一个数据库事务或文件系统的功能将提高你的测试中的表现。

Increased speed is an additional perk of mocking. IO is costly and reduction in IO will increase the speed of your tests. Not having to wait for a database transaction or file system function will improve your tests performance.

我从来没有用过TypeMock所以我不能谈论它了。我的印象,虽然是一样的你,如果你要使用它,然后有可能是一些重构可以做。

I've never used TypeMock so I can't speak much about it. My impression though is the same as yours that if you have to use it then there is probably some refactoring that could be done.

这篇关于模式或做法进行单元测试方法调用静态方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆