【发布时间】:2020-09-28 19:21:42
【问题描述】:
我有一个看起来像这样的测试方法:
public void TestConversion()
{
BuildMyNode(inputDocument)
}
public override MyXMLDocumentObject BuildMyNode(XmlDocument inputDocument)
{
Dictionary<string, long> myIdMap = await GetMyIdMap(inputDocument);
}
public async Task<Dictionary<string, long>> GetMyIdMap(XmlDocument inputDocument)
{
Dictionary<string, long> myIdMap = await MyDataService.GetMyMapAsync(myIds, _cancellationToken);
return myIdMap;
}
public async Task<Dictionary<string, long>> GetMyMapAsync(XmlNodeList myIds, CancellationToken cancellationToken)
{
var idMap = new Dictionary<string, long>();
using (SqlConnection connection = new SqlConnection())
{
//Build sql command
//Convert DataReader to idMap
}
return idMap;
}
所以我的测试依赖于我的数据库,这不好。如果我从 GetMyMapAsync 构建一个接口,然后在实现该接口的 TestConversion() 中实例化一些模拟数据,我将解决问题。但是,似乎我必须将其作为参数传递给 BuildMyNode、GetMyIdMap,然后再传递给 GetMyMapAsync。
所以我中间的两种方法看起来像:
public override MyXMLDocumentObject BuildMyNode(XmlDocument inputDocument, IGetMap getMap)
{
Dictionary<string, long> myIdMap = await GetMyIdMap(inputDocument);
}
public async Task<Dictionary<string, long>> GetMyIdMap(XmlDocument inputDocument, IGetMap getMap)
{
Dictionary<string, long> myIdMap = await getMap.GetMyMapAsync(myIds, _cancellationToken);
return myIdMap;
}
有没有更好的方法来做到这一点?
我尝试重构,添加一个带有我的 GetMyMapAsync 方法签名的委托。我在想我可以在不同的构造函数中通过委托的模拟版本。但是,通过调用堆栈向后工作比我最初意识到的要复杂得多。使用工厂模式和多个抽象类。这是我的代码的修订版:
//1
namespace Namespace.UnitTests
{
public class MyTests
{
[Fact]
public void TestConversion()
{
IDataProcessor _myDataProcessor = MyFactory.GetInstance(Type_2, settings);
XmlDocument expectedDocument = new XmlDocument();
expectedDocument.LoadXml(expectedData);
XmlDocument myDocument = _dataProcessor.ProcessItem(data);
Equal(myDocument.Documents[0].OuterXml, expectedDocument.OuterXml);
}
}
}
//2
namespace Namespace.Factory
{
public static class MyFactory
{
public static IDataProcessor GetInstance(MyEntityType type, MySettings settings)
{
IDataProcessor _processor;
if (MyEntityType.Type_1 == type)
{
_processor = new MyProcessor1(settings);
}
else if (MyEntityType.Type_2 == type)
{
_processor = new MyProcessor2(settings);
}
}
}
}
//3
namespace Namespace.DataProcessor
{
public class MyProcessor2 : BaseDataProcessor1
{
public MyProcessor2(MySettings settings)
: base(settings)
{
//Do some stuff here
}
}
}
//4
namespace Namespace.Base
{
public abstract class BaseDataProcessor1 : MyDataProcessor
{
public BaseDataProcessor1(MySettings settings)
: base(settings) //instantiate
{
}
}
}
//5
namespace Namespace.Base
{
public abstract class MyDataProcessor : IDataProcessor
{
public MyDataProcessor(MySettings settings)
{
this.settings = settings;
this.myDataService = new MyDataService(Settings); //instantiate
}
}
}
//6
namespace Namespace.Data
{
public delegate Task<Dictionary<string, long>> GetDictionary(XmlNodeList orgMasterIds, CancellationToken cancellationToken);
public class MyDataService
{
//Original constructor
public MyDataService(MySettings settings)
{
_settings = settings.NotNull();
GetDictionaryMethod = GetMyMapAsync; //assign delegate
}
//New constructor for testing purposes
public MyDataService(MySettings settings, GetDictionary myDictionary )
{
_settings = settings.NotNull();
GetDictionaryMethod = myDictionary; //assign delegate
}
public async Task<Dictionary<string, long>> GetMyMapAsync (XmlNodeList myIds, CancellationToken cancellationToken)
{
var idMap = new Dictionary<string, long>();
using (SqlConnection connection = new SqlConnection())
{
//Build sql command
//Convert DataReader to idMap
}
return idMap;
}
}
}
【问题讨论】:
-
你为什么不直接创建类女巫接受 IGetMap 接口的构造函数对象,而不是简单地调用使用该对象的 BuildMyNode 或 GetMyIdMap 而不是将其作为参数传递?
-
我想知道你展示的方法除了委派给另一个方法什么都不做,唯一发生“事情”的方法是
GetMyMapAsync,它从数据库加载数据。如果是这种情况,那么我将使用实际数据库对其进行测试,以 100% 确保代码正常工作。如果不是这种情况,并且您没有显示一些逻辑,那么我会将该逻辑提取到专用类中并重组其他类,因此我的重要逻辑在设计和运行时根本不依赖于数据库。大致的结构看起来是Load data -> Process data and return result -> Complete。
标签: c# unit-testing tdd