You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对基于Renci.SshNet的SFTP包装类进行单元测试?

SftpClient封装与单元测试问题

我封装了Renci.SshNet.Sftp.SftpClient实现了MySftpClient类,编写单元测试时调用Connect方法会尝试真实连接导致报错,无法断言该方法的调用次数。现咨询以下问题:

  1. 能否模拟基类方法?还是应采用组合而非继承,为SftpClient定义接口并注入到MySftpClient的构造函数中?
  2. 是否应为SftpClient使用.NET依赖注入,例如services.AddScoped<ISftpClient, SftpClient>()?

我的包装类

public class MySftpClient: SftpClient, IMySftpClient
{
    public MySftpClient(string host, int port, string username, string password) : base(new PasswordConnectionInfo(host, port, username, password)) { }

    public new void Connect()
    {
        base.Connect();
    }
}

单元测试代码

[TestMethod]
public void Connect_Successful_Valid()
{
    var mySftpClient = new MySftpClient("localhost", 22, "root", "password");

    mySftpClient.Connect();
    // 此处报错,因为代码尝试使用模拟连接信息连接真实服务器
    // 无法断言Connect()方法被调用一次
}

可能的解决方案

public class MySftpClient: IMySftpClient
{
    private ISftpClient sftpClient;

    public MySftpClient(
        ISftpClient sftpClient,
        string host,
        int port,
        string username,
        string password
    ) { // 实例化PasswordConnectionInfo... }

    public void Connect()
    {
        sftpClient.Connect();
    }
}

问题解答

1. 优先采用组合模式,不建议模拟基类方法

直接继承SftpClient后,无法直接模拟base.Connect()——因为SftpClient的Connect方法不是虚方法,主流模拟框架(如Moq)只能处理虚方法或接口方法。

你提出的组合方案是正确方向:为SftpClient定义抽象接口(如ISftpClient),让MySftpClient依赖该接口而非继承SftpClient。这样单元测试时可以轻松模拟接口实例,验证Connect调用次数,同时避免真实网络连接。

2. 建议通过.NET依赖注入管理SftpClient

是的,应该用依赖注入容器配置ISftpClient的实例,比如services.AddScoped<ISftpClient, SftpClientAdapter>()(根据业务场景选择Scoped/Transient/Singleton)。这么做的好处:

  • 统一管理SftpClient的生命周期,避免资源泄漏
  • 测试环境中可直接替换为模拟实现,无需修改业务代码
  • 符合依赖倒置原则,让MySftpClient的依赖更清晰

优化后的实现示例

// 定义抽象接口,包含需要使用的SftpClient方法
public interface ISftpClient
{
    void Connect();
    void Disconnect();
    // 按需添加UploadFile、DownloadFile等方法
}

// 适配原生SftpClient的实现类
public class SftpClientAdapter : ISftpClient
{
    private readonly Renci.SshNet.Sftp.SftpClient _innerClient;

    public SftpClientAdapter(string host, int port, string username, string password)
    {
        _innerClient = new Renci.SshNet.Sftp.SftpClient(new PasswordConnectionInfo(host, port, username, password));
    }

    public void Connect() => _innerClient.Connect();
    public void Disconnect() => _innerClient.Disconnect();
    // 实现其他接口方法...
}

// 业务封装类
public class MySftpClient : IMySftpClient
{
    private readonly ISftpClient _sftpClient;

    // 通过构造函数注入依赖
    public MySftpClient(ISftpClient sftpClient)
    {
        _sftpClient = sftpClient;
    }

    public void Connect()
    {
        _sftpClient.Connect();
    }
}

单元测试示例(使用Moq)

[TestMethod]
public void Connect_ShouldInvokeSftpClientConnectOnce()
{
    // 创建ISftpClient的模拟实例
    var mockSftp = new Mock<ISftpClient>();
    var mySftpClient = new MySftpClient(mockSftp.Object);

    // 调用待测试方法
    mySftpClient.Connect();

    // 断言Connect方法被调用一次
    mockSftp.Verify(client => client.Connect(), Times.Once);
}

内容的提问来源于stack exchange,提问作者Mihai Socaciu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 04:30:32