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章当然适用于这里。谢谢。这与我提出的将东西推到另一个类中的解决方案非常接近(尽管您的实现更好)。但是,这不可能导致小型类的激增吗?有许多小型类可以很好地完成一件事情,这比将所有难以维护和测试的东西都耦合在一起要好。它使事物坚固