想象一下这样的课程:
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 "";
}
}
}
对于依赖于FileParser的类,我可以通过它的接口来模拟它的功能,一切都很好。但是,类本身内部存在逻辑,它依赖于TextFieldParser返回要检查的行。
我无法使用模拟“接口”TextFieldParser以便对该逻辑进行单元测试,因为它是来自Microsoft的没有接口的外部类。
我可以将if语句推送到像这样的单独函数中:
public bool HasSingleQuote(string lineToCheck)
{
return lineToCheck.Contains("'");
}
但这些不需要在课外访问。他们也不需要从其他地方打电话,所以他们不属于辅助类或类似的。因此,根据良好的设计原则,它们是私有的而非公开的,我应该通过他们的公共访问者来测试它们。在这种情况下,取决于不可测试的TextFieldParser。
我可以将TextFieldParser包装在我自己的类中并粘贴和接口,但感觉就像过度杀戮和不必要的代码复制。
我很欣赏这是一个不值得测试的简单例子,但我只是把它放在一起来说明问题。重构此代码以使我的逻辑可测试的最佳方法是什么?
答案 0 :(得分:2)
我会说测试你拥有的东西。 TextFieldParser
是一个实现细节。 MS会广泛测试它的发布功能。如果关注的是你正在进行条件检查的实现内部的逻辑,那么可以认为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 "";
}
}
然后将FileParser实现重构为......
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);
}
}
}
答案 1 :(得分:2)
测试问题是基于TextFieldParser是第三方依赖的事实,对吗?您可以使用的一种策略是将第三方依赖项包装在服务接口中,然后将其传递给FileParser。
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的该服务的默认实现,但您也可以编写一个只返回一组预定义数据的测试实现。