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