使用 Spock 框架测试 SFTP 客户端

Testing an SFTP client with Spock Framework

我们实际上使用 JUnit 和很棒的 FakeSftpServerRule junit 规则来测试我们制作的自定义 SFTP 客户端。效果很好。

最后,我们想摆脱 junit 以支持 spock 框架,因为我们试图迁移到 groovy。

你们知道 FakeSftpServerRule 的任何等效项或将 junit 规则“切换”为 spock 规则等效项的任何方法吗?

非常感谢。

同一作者还发布了 Fake SFTP Server Lambda,与您使用的 JUnit 4 规则相比,它独立于测试框架。

如果您想坚持使用旧工具,Spock 1.3 也可以使用 JUnit 4 规则,并且在 Spock 2.x 中它也可以与 JUnit 4 兼容层一起工作。


更新:这是一个使用SSHJ库从SFTP服务器下载文件的示例程序,所以我们有一个正在测试的主题:

package de.scrum_master.Whosebug.q71081881;

import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.xfer.InMemoryDestFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class SFTPFileDownloader {
  private String host;
  private int port;
  private String user;
  private String password;

  public SFTPFileDownloader(String host, int port, String user, String password) {
    this.host = host;
    this.port = port;
    this.user = user;
    this.password = password;
  }

  protected static class ByteArrayInMemoryDestFile extends InMemoryDestFile {
    private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    @Override
    public ByteArrayOutputStream getOutputStream() {
      return outputStream;
    }
  }

  public String getFileContent(String path) throws IOException {
    try (
      SSHClient sshClient = setupSshj();
      SFTPClient sftpClient = sshClient.newSFTPClient()
    )
    {
      ByteArrayInMemoryDestFile inMemoryDestFile = new ByteArrayInMemoryDestFile();
      sftpClient.get(path, inMemoryDestFile);
      return inMemoryDestFile.getOutputStream().toString("UTF-8");
    }
  }

  private SSHClient setupSshj() throws IOException {
    SSHClient client = new SSHClient();
    client.addHostKeyVerifier(new PromiscuousVerifier());
    client.connect(host, port);
    client.authPassword(user, password);
    return client;
  }
}

以下 Spock 规范以两种方式使用 Fake SFTP Server Lambda:

  1. 在特征方法 "use original server class" 中,我们使用 withSftpServer 其作者预期的方式,即配置它并在单个 lambda 或 [=57= 中执行交互] 关闭。使它更加 spockish 的唯一方法是将结果分配给先前定义的变量,并稍后在闭包之外的 Spock 条件中使用它们。正如 Leonard 所说,生成的 Spock 规范代码不是最优的,因为消费者贪婪地想要立即执行所有交互并再次关闭服务器。

  2. 在特征方法"use custom server class"中,我们使用了一个自定义的CloseableFakeSftpServer,它无耻地利用了其父类的私有构造函数、方法和字段。在 Groovy class 中,我们可以做到这一点。但是当然,如​​果上游库能够扩展和开放一点,以支持更多spock-like的方式,首先创建和配置服务器,然后进行交互和验证结果,而不需要被仅限于 lambda 或闭包。我什至制作了助手 class @AutoCloseable 并使用 Spock @AutoCleanup 扩展以避免在 cleanup: 块中手动关闭。助手 class 还使用 Groovy 的 @Delegate FakeSftpServer 以便在其自己的接口中公开委托的方法。这是无法简单地扩展原始服务器 class 的解决方法,因为即使 Groovy 也不能调用私有超级构造函数。

所以这是测试变体 #2 的助手 class:

package de.scrum_master.Whosebug.q71081881

import com.github.stefanbirkner.fakesftpserver.lambda.FakeSftpServer

class CloseableFakeSftpServer implements AutoCloseable {
  @Delegate
  private FakeSftpServer fakeSftpServer
  private Closeable closeServer

  CloseableFakeSftpServer() {
    fakeSftpServer = new FakeSftpServer(FakeSftpServer.createFileSystem())
    closeServer = fakeSftpServer.start(0)
  }

  @Override
  void close() throws Exception {
    fakeSftpServer.fileSystem.close()
    closeServer.close()
  }
}

在这里我们终于有了两种可选特征方法的规范(我更喜欢第二种):

package de.scrum_master.Whosebug.q71081881

import spock.lang.AutoCleanup
import spock.lang.Specification

import static com.github.stefanbirkner.fakesftpserver.lambda.FakeSftpServer.withSftpServer
import static java.nio.charset.StandardCharsets.UTF_8

class SFTPServerTest extends Specification {
  @AutoCleanup
  def server = new CloseableFakeSftpServer()
  def user = "someone"
  def password = "secret"

  def "use original server class"() {
    given:
    def fileContent = null

    when:
    withSftpServer { server ->
      server.addUser(user, password)
      server.putFile("/directory/file.txt", "content of file", UTF_8)
      // Interact with the subject under test
      def client = new SFTPFileDownloader("localhost", server.port, user, password)
      fileContent = client.getFileContent("/directory/file.txt")
    }

    then:
    fileContent == "content of file"
  }

  def "use custom server class"() {
    given: "a preconfigured fake SFTP server"
    server.addUser(user, password)
    server.putFile("/directory/file.txt", "content of file", UTF_8)

    and: "an SFTP client under test"
    def client = new SFTPFileDownloader("localhost", server.port, user, password)

    expect:
    client.getFileContent("/directory/file.txt") == "content of file"
  }

}

Try it in the Groovy web console.