Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/259.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何设计与不可测试函数绑定的可测试代码_C#_Unit Testing_Architecture_Refactoring - Fatal编程技术网

C# 如何设计与不可测试函数绑定的可测试代码

C# 如何设计与不可测试函数绑定的可测试代码,c#,unit-testing,architecture,refactoring,C#,Unit Testing,Architecture,Refactoring,想象这样一个类: public class FileParser : IFileParser { public string ParseFirstRowForDelimiters(string path) { using (TextFieldParser parser = new TextFieldParser(path)) { string line = parser.ReadLine(); if

想象这样一个类:

public class FileParser : IFileParser
{
    public string ParseFirstRowForDelimiters(string path)
    {
        using (TextFieldParser parser = new TextFieldParser(path))
        {
            string line = parser.ReadLine();

            if(lineContains("'"))
            {
                return "'";
            }

            if(lineContains("\"")
            {
                return "\"";
            }

            return "";
        }
    }
}
public bool HasSingleQuote(string lineToCheck)
{
    return lineToCheck.Contains("'");
}
对于依赖于FileParser的类,我可以通过其接口模拟其函数,一切正常。但是,类本身内部有一个逻辑,它依赖于TextFieldParser返回要检查的行

我不能用mock“接口”TextFieldParser来单元测试该逻辑,因为它是一个来自微软的外部类,没有接口

我可以将if语句推送到单独的函数中,如下所示:

public class FileParser : IFileParser
{
    public string ParseFirstRowForDelimiters(string path)
    {
        using (TextFieldParser parser = new TextFieldParser(path))
        {
            string line = parser.ReadLine();

            if(lineContains("'"))
            {
                return "'";
            }

            if(lineContains("\"")
            {
                return "\"";
            }

            return "";
        }
    }
}
public bool HasSingleQuote(string lineToCheck)
{
    return lineToCheck.Contains("'");
}
但是这些不需要在课堂外访问。也不需要从其他地方调用它们,这样它们就不属于helper类或类似的类。所以,根据好的设计原则,它们是私有的而不是公共的,我应该通过它们的公共访问器来测试它们。在本例中,这取决于不稳定的TextFieldParser

我可以将TextFieldParser包装在我自己的类中,并坚持使用它和接口,但这感觉像是过度杀戮和不必要的代码复制


我理解这是一个不值得测试的小例子,但我只是把它放在一起来说明这个问题。重构这段代码以使我的逻辑可测试的最佳方法是什么?

我会说测试你自己的
TextFieldParser
是一个实现细节。微软将在发布时对其功能进行广泛测试。如果关注的是执行条件检查的实现内部的逻辑,那么可以认为
IFileParser
实现可能做了太多的事情。我想起了SRP,只有一个改变的理由

public interface IDelimiterLogic {
    string Invoke(string line);
}
使用如下的实现

public class DefaultDelimiterLogic : IDelimiterLogic {
    public string Invoke(string line) {
        if (line.Contains("'")) {
            return "'";
        }

        if (line.Contains("\"")) {
            return "\"";
        }

        return "";
    }
}
然后将文件解析器实现重构为

public class FileParser : IFileParser {
    IDelimiterLogic delimiterLogic;
    public FileParser(IDelimiterLogic delimiterLogic) {
        this.delimiterLogic = delimiterLogic;
    }

    public string ParseFirstRowForDelimiters(string path) {
        using (TextFieldParser parser = new TextFieldParser(path)) {
            string line = parser.ReadLine();
            return delimiterLogic.Invoke(line);
        }
    }
}
因此,现在如果您想测试定界符逻辑,那么测试中的系统将是
IDelimiterLogic
实现

更新:

@JAllen在抽象第三方依赖关系方面也功不可没

public interface ITextFieldParser : IDisposable {
    bool EndOfData { get; }
    string ReadLine();    
}

public interface ITextFieldParserFactory {
    ITextFieldParser Create(string path);
}

public class TextFieldParserFactory : ITextFieldParserFactory {
    public ITextFieldParser Create(string path) {
        return new TextFieldParserWrapper(path);
    }
}

public class TextFieldParserWrapper : ITextFieldParser {
    TextFieldParser parser;
    internal TextFieldParserWrapper(string path) {
        parser = new TextFieldParser(path);
    }
    public bool EndOfData { get{ return parser.EndOfData; } }
    public string ReadLine() { return parser.ReadLine(); }
    public void Dispose() { parser.Dispose(); }
}
新的重构
IFileParser
实现

public class FileParser : IFileParser {
    IDelimiterLogic delimiterLogic;
    ITextFieldParserFactory parserFactory;

    public FileParser(IDelimiterLogic delimiterLogic, ITextFieldParserFactory parserFactory) {
        this.delimiterLogic = delimiterLogic;
        this.parserFactory = parserFactory;
    }

    public string ParseFirstRowForDelimiters(string path) {
        using (ITextFieldParser parser = parserFactory.Create(path)) {
            string line = parser.ReadLine();
            return delimiterLogic.Invoke(line);
        }
    }
}

测试问题是基于TextFieldParser是第三方依赖的事实,对吗?您可以使用的一种策略是将该第三方依赖项包装在服务接口中,然后将其传递给文件解析器

public interface ITextFieldParserService
{
   string ReadLine();
}

public class DefaultTextFieldParserService : ITextFieldParserService
{
   private TextFieldParser parser;
   public ITextFieldParserService Setup(string path)
   {
       parser = new TextFieldParser(path);
   }
   //you'd want some teardown method to dispose of TextFieldParser, or make
   //the service IDisposable probably
}

public class FileParser : IFileParser
{
   public FileParser(ITextFieldParserService textParserService)
   {
   }
   ...
   public string ParseFirstRowForDelimiters(string path)
   {
       var parser = textParserService.Setup(path)        
        string line = parser.ReadLine();

        if(lineContains("'"))
        {
            return "'";
        }

        if(lineContains("\"")
        {
            return "\"";
        }

        return "";         
   }

您可以有一个实际使用第三方TextFieldParser的该服务的默认实现,但也可以编写一个只返回一组预定义数据的测试实现

我会说测试一下你拥有什么
TextFieldParser
是一个实现细节。微软将在发布时对其功能进行广泛测试。如果关注的是执行条件检查的实现内部的逻辑,那么可以认为
IFileParser
实现可能做了太多的事情。我想起了SRP,只有一个理由需要改变。顺便说一句,有效地使用遗留代码是一本很棒的书。第14章和第15章当然适用于这里。谢谢。这与我提出的将东西推到另一个类中的解决方案非常接近(尽管您的实现更好)。但是,这不可能导致小型类的激增吗?有许多小型类可以很好地完成一件事情,这比将所有难以维护和测试的东西都耦合在一起要好。它使事物坚固