Jer*_*acs 54 c# unit-testing moq mocking
我有一个Transfer类,简化它看起来像这样:
public class Transfer
{
public virtual IFileConnection source { get; set; }
public virtual IFileConnection destination { get; set; }
public virtual void GetFile(IFileConnection connection,
string remoteFilename, string localFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void PutFile(IFileConnection connection,
string localFilename, string remoteFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void TransferFiles(string sourceName, string destName)
{
source = internalConfig.GetFileConnection("source");
destination = internalConfig.GetFileConnection("destination");
var tempName = Path.GetTempFileName();
GetFile(source, sourceName, tempName);
PutFile(destination, tempName, destName);
}
}
Run Code Online (Sandbox Code Playgroud)
IFileConnection界面的简化版本如下所示:
public interface IFileConnection
{
void Get(string remoteFileName, string localFileName);
void Put(string localFileName, string remoteFileName);
}
Run Code Online (Sandbox Code Playgroud)
System.IO.IOException当IFileConnection具体类失去与远程连接的连接,发送电子邮件和不发送电子邮件时,真正的类应该处理抛出的内容.
我想使用Moq创建一个Transfer类,并Transfer在所有属性和方法中使用它作为我的具体类,除非GetFile调用该方法 - 然后我希望它抛出System.IO.IOException并确保Transfer类正确处理它.
我使用合适的工具吗?我是以正确的方式来做这件事的吗?我将如何为该单元测试编写设置NUnit?
Ufu*_*arı 71
这是你如何嘲笑你的 FileConnection
Mock<IFileConnection> fileConnection = new Mock<IFileConnection>(
MockBehavior.Strict);
fileConnection.Setup(item => item.Get(It.IsAny<string>,It.IsAny<string>))
.Throws(new IOException());
Run Code Online (Sandbox Code Playgroud)
然后实例化您的Transfer类并在方法调用中使用mock
Transfer transfer = new Transfer();
transfer.GetFile(fileConnection.Object, someRemoteFilename, someLocalFileName);
Run Code Online (Sandbox Code Playgroud)
更新:
首先,您必须仅模拟您的依赖项,而不是您正在测试的类(在本例中为Transfer类).在构造函数中声明这些依赖项,可以轻松查看您的类需要使用哪些服务.在编写单元测试时,它还可以用假货替换它们.目前,用假货取代这些属性是不可能的.
由于您使用另一个依赖项设置这些属性,我会这样写:
public class Transfer
{
public Transfer(IInternalConfig internalConfig)
{
source = internalConfig.GetFileConnection("source");
destination = internalConfig.GetFileConnection("destination");
}
//you should consider making these private or protected fields
public virtual IFileConnection source { get; set; }
public virtual IFileConnection destination { get; set; }
public virtual void GetFile(IFileConnection connection,
string remoteFilename, string localFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void PutFile(IFileConnection connection,
string localFilename, string remoteFilename)
{
connection.Get(remoteFilename, localFilename);
}
public virtual void TransferFiles(string sourceName, string destName)
{
var tempName = Path.GetTempFileName();
GetFile(source, sourceName, tempName);
PutFile(destination, tempName, destName);
}
}
Run Code Online (Sandbox Code Playgroud)
这样你就可以模拟internalConfig并使它返回执行你想要的IFileConnection模拟.
这就是我设法做我想做的事情:
[Test]
public void TransferHandlesDisconnect()
{
// ... set up config here
var methodTester = new Mock<Transfer>(configInfo);
methodTester.CallBase = true;
methodTester
.Setup(m =>
m.GetFile(
It.IsAny<IFileConnection>(),
It.IsAny<string>(),
It.IsAny<string>()
))
.Throws<System.IO.IOException>();
methodTester.Object.TransferFiles("foo1", "foo2");
Assert.IsTrue(methodTester.Object.Status == TransferStatus.TransferInterrupted);
}
Run Code Online (Sandbox Code Playgroud)
如果这种方法有问题,我想知道; 其他答案表明我做错了,但这正是我试图做的.
我想这就是您想要的,我已经测试了这段代码并且可以正常工作
使用的工具是:(所有这些工具都可以作为Nuget软件包下载)
http://fluentassertions.codeplex.com/
http://autofixture.codeplex.com/
https://nuget.org/packages/AutoFixture.AutoMoq
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var myInterface = fixture.Freeze<Mock<IFileConnection>>();
var sut = fixture.CreateAnonymous<Transfer>();
myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
.Throws<System.IO.IOException>();
sut.Invoking(x =>
x.TransferFiles(
myInterface.Object,
It.IsAny<string>(),
It.IsAny<string>()
))
.ShouldThrow<System.IO.IOException>();
Run Code Online (Sandbox Code Playgroud)
编辑:
让我解释:
编写测试时,必须确切知道要测试的内容,这称为:“被测对象(SUT)”,如果我的理解正确,那么在这种情况下,您的SUT为: Transfer
因此,考虑到这一点,您不应该模拟您的SUT,如果替换您的SUT,那么您实际上就不会测试真实的代码
当您的SUT具有外部依赖项(非常常见)时,您需要替换它们,以便隔离测试您的SUT。当我说替代品时,我指的是根据您的需要使用模拟,虚拟,模拟等
在这种情况下,您的外部依赖关系是IFileConnection这样,因此您需要为此依赖关系创建模拟并将其配置为引发异常,然后只需调用您的SUT real方法并断言您的方法将按预期方式处理异常
var fixture = new Fixture().Customize(new AutoMoqCustomization());:此linie初始化了一个新的Fixture对象(Autofixture库),该对象用于创建SUT,而不必明确地担心构造函数参数,因为它们是自动创建或模拟的,在这种情况下使用Moq
var myInterface = fixture.Freeze<Mock<IFileConnection>>();:这将冻结IFileConnection依赖关系。冻结意味着当被问到时,Autofixture将始终使用此依赖项,为简单起见,就像单例。但有趣的是,我们正在创建此依赖项的Mock,您可以使用所有Moq方法,因为这是一个简单的Moq对象
var sut = fixture.CreateAnonymous<Transfer>();:AutoFixture在这里为我们创建SUT
myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>();在这里,您正在配置依赖项以在Get调用方法时引发异常,而该接口中的其余方法均未配置,因此,如果尝试访问它们,则会收到意外的异常
sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>();:最后,是时候测试您的SUT了,该行使用FluenAssertions库,它只是TransferFiles 从SUT调用real方法,并且它接收模拟的参数,IFileConnection因此只要您IFileConnection.Get在SUT TransferFiles方法的正常流程中调用,模拟对象将调用引发已配置的异常,这是断言您的SUT正确处理了异常的时候,在这种情况下,我只是确保使用ShouldThrow<System.IO.IOException>()(来自FluentAssertions库)引发了异常
推荐参考文献:
http://martinfowler.com/articles/mocksArentStubs.html
http://misko.hevery.com/code-reviewers-guide/
http://misko.hevery.com/presentations/
http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded
http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded
| 归档时间: |
|
| 查看次数: |
76656 次 |
| 最近记录: |