我们做 TDD 已经有一段时间了,我们在重构时遇到了一些问题。由于我们试图尽可能多地尊重 SRP(单一责任原则),我们创建了许多组合,我们的类使用这些组合来处理常见责任(例如验证、日志记录等)。让我们举一个非常简单的例子:
public class Executioner
{
public ILogger Logger { get; set; }
public void DoSomething()
{
Logger.DoLog("Starting doing something");
Thread.Sleep(1000);
Logger.DoLog("Something was done!");
}
}
public interface ILogger
{
void DoLog(string message);
}
当我们使用模拟框架时,我们会针对这种情况进行的测试类似于
[TestClass]
public class ExecutionerTests
{
[TestMethod]
public void Test_DoSomething()
{
var objectUnderTests = new Executioner();
#region Mock setup
var loggerMock = new Mock<ILogger>(MockBehavior.Strict);
loggerMock.Setup(l => l.DoLog("Starting doing something"));
loggerMock.Setup(l => l.DoLog("Something was done!"));
objectUnderTests.Logger = loggerMock.Object;
#endregion
objectUnderTests.DoSomething();
loggerMock.VerifyAll();
}
}
如您所见,测试清楚地知道我们正在测试的方法实现。我不得不承认这个例子太简单了,但我们有时会有一些包含职责的组合,这些职责不会为测试增加任何价值。
让我们为这个例子增加一些复杂性
public interface ILogger
{
void DoLog(LoggingMessage message);
}
public interface IMapper
{
TTarget DoMap<TSource, TTarget>(TSource source);
}
public class LoggingMessage
{
public string Message { get; set; }
}
public class Executioner
{
public ILogger Logger { get; set; }
public IMapper Mapper { get; set; }
public void DoSomething()
{
DoLog("Starting doing something");
Thread.Sleep(1000);
DoLog("Something was done!");
}
private void DoLog(string message)
{
var startMessage = Mapper.DoMap<string, LoggingMessage>(message);
Logger.DoLog(startMessage);
}
}
好的,这是一个例子。我会在我的 Logger 的实现中包含 Mapper 的东西,并在我的界面中保留一个 DoLog(string message) 方法,但这是一个证明我的担忧的例子
相应的测试引导我们
[TestClass]
public class ExecutionerTests
{
[TestMethod]
public void Test_DoSomething()
{
var objectUnderTests = new Executioner();
#region Mock setup
var loggerMock = new Mock<ILogger>(MockBehavior.Strict);
var mapperMock = new Mock<IMapper>(MockBehavior.Strict);
var mockedMessage = new LoggingMessage();
mapperMock.Setup(m => m.DoMap<string, LoggingMessage>("Starting doing something")).Returns(mockedMessage);
mapperMock.Setup(m => m.DoMap<string, LoggingMessage>("Something was done!")).Returns(mockedMessage);
loggerMock.Setup(l => l.DoLog(mockedMessage));
objectUnderTests.Logger = loggerMock.Object;
objectUnderTests.Mapper = mapperMock.Object;
#endregion
objectUnderTests.DoSomething();
mapperMock.VerifyAll();
loggerMock.Verify(l => l.DoLog(mockedMessage), Times.Exactly(2));
loggerMock.VerifyAll();
}
}
哇...假设我们将使用另一种方式来翻译我们的实体,我将不得不更改每个具有使用映射器服务的方法的测试。
无论如何,当我们进行重大重构时,我们真的会感到有些痛苦,因为我们需要更改一堆测试。
我很乐意讨论这类问题。我错过了什么吗?我们是否测试了太多东西?