我有一个使用第三方FTP库http://ftps.codeplex.com/的类,我想模仿它,以便我可以单独测试该类,而不是FTP库。我已经做到了,但对我来说感觉很麻烦。详细地说,该类使用AlexPilotti.FTPS.Client.FTPSClient类的这些方法:
public string Connect(string hostname, ESSLSupportMode sslSupportMode)
public ulong PutFile(
string localFileName, string remoteFileName,
FileTransferCallback transferCallback)
委托AlexPilotti.FTPS.Client.FileTransferCallback看起来像这样:
public delegate void FileTransferCallback(
FTPSClient sender, ETransferActions action,
string localObjectName, string remoteObjectName,
ulong fileTransmittedBytes, ulong? fileTransferSize, ref bool cancel);
您可以看到问题,因为PutFile从FTP库获取委托,该委托也从库中获取两种不同的类型。我决定使用泛型来解耦这些类型。
为了能够创建一个模拟对象,我创建了一个接口,然后创建了一个派生类,它是FTP库功能的包装。
// Interface so that dependency injection can be used.
public delegate void FileTransferCallback<T1, T2>(T1 sender, T2 action,
string localObjectName, string remoteObjectName,
ulong fileTransmittedBytes, ulong? fileTransferSize,
ref bool cancel);
public interface IFTPClient<T1, T2> : IDisposable
{
string Connect(string hostname, System.Net.NetworkCredential credential);
ulong PutFile(
string localFileName, string remoteFileName,
FileTransferCallback<T1, T2> transferCallback);
}
// Derived class, the wrapper
using FTPSClient = FTPS.Client.FTPSClient;
using ETransferActions = FTPS.Client.ETransferActions;
public class FTPClient : IFTPClient<FTPSClient, ETransferActions>
{
public FTPClient()
{
client = new AlexPilotti.FTPS.Client.FTPSClient();
}
public ulong PutFile(
string localFileName, string remoteFileName,
FileTransferCallback<FTPSClient, ETransferActions> transferCallback)
{
callback = transferCallback;
return client.PutFile(localFileName, remoteFileName, TransferCallback);
}
public void Dispose()
{
client.Dispose();
}
public string Connect(
string hostname, System.Net.NetworkCredential credential)
{
return client.Connect(hostname, credential,
FTPS.Client.ESSLSupportMode.ClearText);
}
void TransferCallback(
FTPS.Client.FTPSClient sender, FTPS.Client.ETransferActions action,
string localObjectName, string remoteObjectName,
ulong fileTransmittedBytes, ulong? fileTransferSize,
ref bool cancel)
{
callback.Invoke(sender, action, localObjectName, remoteObjectName,
fileTransmittedBytes, fileTransferSize, ref cancel);
}
private AlexPilotti.FTPS.Client.FTPSClient client;
private FileTransferCallback<FTPSClient, ETransferActions> callback;
}
这是一个使用此接口的类,我进行单元测试。我把它剥了一下,所以只使用了Connect方法,但我希望它仍然可以解释我的问题。
public class FTPServerConnection<T1, T2>
{
public void Init(
IFTPClient<T1, T2> client, string serverName,
string userName, string passwd)
{
IsConnected = false;
ServerName = serverName;
UserName = userName;
Passwd = passwd;
ftpClient = client;
Connect();
}
public void Connect()
{
ftpClient.Connect(
ServerName,
new System.Net.NetworkCredential(UserName, Passwd));
}
public string ServerName { protected set; get; }
public string UserName { protected set; get; }
public string Passwd { protected set; get; }
public bool IsConnected { protected set; get; }
private IFTPClient<T1, T2> ftpClient;
}
最后是单元测试:
[TestMethod()]
public void FTPServerConnectionConstructorTest()
{
var ftpClient = new Mock<IFTPClient<object, object>>();
var ftpServer = new FTPServerConnection<object, object>();
ftpServer.Init(ftpClient.Object, "1.2.3.4", "user", "passwd");
Assert.AreEqual("1.2.3.4", ftpServer.ServerName);
Assert.AreEqual("user", ftpServer.UserName);
Assert.AreEqual("passwd", ftpServer.Passwd);
}
在这种情况下,通常的做法是什么?我的方法似乎很混乱,我想知道是否有更简单的方法。感谢。
答案 0 :(得分:1)
我认为这只会让你感到麻烦,因为你已经让第三方代表应对了,但对我来说它看起来像一个非常标准的抽象/嘲弄方法。
我唯一的评论是FTPServerConnection
的结构看起来有点不标准;它的编写方式你必须在无效状态下实例化一个(即没有客户端详细信息的那个)然后调用Init()
,它本身调用Connect()
(从外观上看您的测试永远不会被服务器类的客户端调用)。我会说在Init()
构造函数中提供FTPServerConnection
的参数更为正常,然后完全删除Init()
方法并让客户端执行此操作:
var ftpClient = new Mock<IFTPClient<object, object>>();
using (var ftpServer = new FTPServerConnection<object, object>(
ftpClient.Object,
"1.2.3.4",
"user",
"passwd"))
{
ftpServer.Connect();
}
...但是,关于你抽象FTP库的方式,我认为这是一个很好的方式:)