【问题标题】:How to test a class that connects to a FTP server?如何测试连接到 FTP 服务器的类?
【发布时间】:2012-04-11 08:21:28
【问题描述】:

我正在为我的应用程序开发实时更新。到目前为止,我已经创建了几乎所有的单元测试,但我不知道如何测试连接到 FTP 服务器并下载新版本的特定类。

要测试这个类,我应该创建一个 FTP 测试服务器并在我的单元测试中使用它吗?如果是这样,我如何确保这个 FTP 服务器始终与我的测试一致?我应该在测试开始之前手动创建我需要的每个文件,还是应该在我的测试类中自动创建(拆卸和设置方法)?

这个问题也适用于连接任何类型服务器的单元测试类。

编辑

我已经在模拟我的 ftp 类,所以在其他测试中我并不总是需要连接到 ftp 服务器。

让我看看我对 Warren 在评论中所说的话是否正确:

我认为,一旦您通过 TCP/IP 与单独的应用程序通信 我们应该称之为“集成测试”。一个不再测试一个 单位或方法,而是一个系统。

当单元测试需要与另一个应用程序(可以是 HTTP 服务器或 FTP 服务器)进行通信时,这是否不再是单元测试而是集成服务器?如果是这样,我是否通过尝试使用单元测试技术来创建此测试做错了?说我不应该对这个类进行单元测试是否正确?这对我来说确实有意义,因为单元测试似乎需要做很多工作。

【问题讨论】:

  • 如果你能设法编写一个测试来确保类的功能,我会使用这个测试——无论是单元测试还是集成测试。总比没有测试好。
  • 我的做法是将单元测试与集成测试分开。两者都从 dUnit 运行,但一种称为 AppUnitTests.exe,另一种称为 AppNetworkIntegrationTests.exe,仅包含网络/集成测试。如果我有一些需要很长时间的导入/导出/转换逻辑,我可能会制作另一个测试应用程序。当这些都被卡在 UnitTests.exe 中时,这会抑制单元测试并且不再有利于 TDD。

标签: delphi unit-testing ftp delphi-2010


【解决方案1】:

只需借用随您喜欢的任何套接字组件集(Indy、ICS 或其他)附带的 FTP 或 HTTP Server 演示。即时测试服务器。

我会将它放入一个工具文件夹中,以便与我的单元测试一起使用。我可能会编写一些代码来检查 TestFtpServer.exe 是否已经存在,如果没有,则启动它。

我会将其保留在我的单元测试应用程序的进程内存空间之外,因此是单独的进程。

请注意,当您开始进行 FTP 服务器操作时,单元测试应该真正称为“集成测试”。

我不会从我的单元测试中手动创建文件。我希望我的代码应该从版本控制中签出,并按原样从运行我的测试程序的批处理文件中构建,该文件知道一个名为 Tools 的子文件夹,其中包含 EXE,也许还有一个名为 @987654322 的文件夹@ 和 LocalData 可用于保存从服务器开始并传输到我的本地单元测试应用程序的数据。也许你可以破解你的演示服务器,让它在中途终止会话(当你想测试失败时),但我仍然认为你不会得到很好的覆盖。

注意如果您要进行自动更新,我认为再多的单元测试也无法解决问题。您需要处理许多与互联网相关的潜在问题。当您的主机名无法解析时会发生什么?当下载完成并失败时会发生什么?自动更新与单元测试的功能不太匹配。

【讨论】:

  • 我更喜欢ICS 附带的那个。它是一个功能齐全的 FTP 服务器,可提供更具体的测试,并将直观地显示它收到的内容和发回的内容。我将它用于 EDI 传输的各种测试,包括跨 VPN 的测试。 Indy 的演示并没有真正完全实现,有时很难在与安装在特定 IDE 中的 Indy 版本相匹配的版本中找到 - 您必须先解决编译器错误,然后才能开始尝试使用服务器本身。
  • ICS 非常好。但是,如果 OP 更熟悉 Indy,他们可能会发现效果很好。 Indy 演示的部分问题在于,其中很多在 Indy9 到 Indy10 API 的变化中被放弃了。
  • 对于运行服务器进行测试,它与用户熟悉的内容有何不同?您运行服务器然后忽略它,直到您查看备忘录控件以查看您的客户端发送和接收的内容。 :) ICS 的服务器演示可以让您测试比 Indy 演示更多的功能,这有时非常重要。
  • 好的,Ken,没什么大不了的。在这种情况下,我只是认为 OP 的选择并不重要。 Indy(正如 Remy 在下面指出的)有一些有趣的功能。实际上,您更喜欢 ICS,我也更喜欢。但两者都完全有能力。 OP 的问题甚至不应该以 FTP 为中心。也许 HTTP 是正确的选择,而 FTP 将在他的问题的未来编辑中消失。 :-)
【解决方案2】:

在测试中,目的始终是首先回答问题:测试什么——即测试的范围。

因此,如果您要测试 FTP 服务器实现,则必须创建 FTP 客户端。

如果您要测试 FTP 客户端,则必须创建 FTP 服务器。

因此,您必须缩小测试扩展的大小,直到达到单一级别。

它可能是例如为您的目的:

  • 获取为应用程序安装的当前文件列表;
  • 获取远程可用文件的列表;
  • 获取文件更新;
  • 检查文件是否正确(校验和?);
  • 等等……

每个测试项目都有一些模拟和存根。有关两者之间的区别,请参阅this article。简而言之(AFAIK),存根只是一个模拟对象,它总是有效的。而模拟(在每个测试中应该是唯一的)是可能改变测试结果(通过或失败)的元素。

对于 FTP 连接的确切目的,您可以使用例如(在测试客户端时)有一些返回文件列表的存根和一个模拟,它将测试 FTP 服务器的几个可能问题(超时、连接丢失、错误内容)。然后您的客户端将按预期做出反应。您的模拟可能是一个真正的 FTP 服务器实例,但它的行为会如预期那样触发所有潜在的错误。通常,每个错误都会引发一个异常,由测试单元跟踪,以便通过/失败每个测试。

编写好的测试代码有点困难。测试驱动的方法起初有点耗时,但从长远来看总是更好。一本好书在这里是强制性的,或者至少是一些参考文章(如上面链接的 Martin Fowler 的)。在 Delphi 中,使用接口和 SOLID 原则可以帮助您编写此类代码,并创建存根/模拟来编写测试。

根据我的实验,每个程序员有时都会在编写测试时迷失方向……好的测试编写可能比功能编写更耗时,在某些情况下……你被警告了!每个测试都应被视为一项功能,并应评估其成本:值得吗?这里不是更适合另一种测试吗?我的测试是否与它正在测试的功能分离?不是已经测试了吗?我是在测试我的代码还是第三方/库功能?

题外话,但我的两分钱:HTTP/1.1 现在可能比 FTP 更适合,即使是文件更新。你可以恢复一个 HTTP 连接,并行加载 HTTP 内容,而且这个协议比 FTP 对代理更友好。托管一些 HTTP 内容比托管 FTP 容易得多(一些 FTP 服务器也存在已知的安全问题)。如今,大多数软件更新都是通过 HTTP/1.1 执行的,而不是 FTP(例如 Microsoft 产品或大多数 Linux 存储库)。

编辑:

当您使用远程协议时,您可能会争辩说您正在进行集成测试。这可能是有道理的,但恕我直言,这是不一样的。

据我了解,当您让所有组件与实际应用程序一起工作时,就会进行集成测试,然后检查它们是否按预期工作。我关于 FTP 测试的建议是,您正在模拟 FTP 服务器,以便明确测试所有潜在问题(超时、连接或传输错误......)。这与集成测试不同:代码覆盖率要大得多。而且您只测试代码的一部分,而不是整个代码集成。这不是因为您正在使用一些远程连接,而是在进行集成测试:这仍然是单一测试。

当然,集成和系统测试应该在单元测试之后进行。但是 FTP 客户端单一测试可以模拟 FTP 服务器,在本地运行它,但测试在真实的大万维网中可能出现的所有潜在问题。

【讨论】:

  • 我认为,一旦您通过 TCP/IP 与单独的应用程序通信,我们应该将其称为“集成测试”。一个不再是测试一个单元或一个方法,而是一个系统。
【解决方案3】:

为知道如何与 FTP 服务器通信的一个组件编写几个集中的集成测试。对于这些测试,您需要在每次测试之前启动一个 FTP 服务器,将测试所需的所有文件放在那里,然后在测试之后关闭服务器。

完成此操作后,在所有其他测试中,您将不会使用真正连接到 FTP 服务器的组件,但您将使用它的伪造或模拟版本(由某些支持内存中的数据结构,而不是真实的文件和网络套接字)。这样,您就可以编写单元测试,不需要 FTP 服务器或网络连接,除了 FTP 客户端组件。

除了这些测试之外,可能还需要一些端到端测试来启动整个程序(与组件级别的集中集成测试不同)并连接真正的 FTP 服务器。端到端测试不能涵盖所有极端情况(与单元测试不同),但它们可以help to solve integration issues

【讨论】:

  • 是的,我已经在模拟我的 ftp 类,所以我并不总是需要连接到 ftp 服务器。
  • 您不仅应该模拟您的 FTP 类,还应该模拟它作为传输层。单元测试不应该知道您使用的是 FTP、HTTP 还是 Carrier-Pigeons。
【解决方案4】:

单元测试应该是快速的,闪电般的。任何减慢它们的东西都会阻止你运行它们。

它们还应该在一次运行到另一次运行中保持一致。测试实际的文件传输会在单元测试中引入随机失败的可能性。

如果您正在测试的类只是包装您正在使用的 ftp 库的 api,那么您已经达到了您不需要 unit 测试的应用程序的边界之一它。 (嗯,有时你会这样做。它被称为探索性测试,但一旦你得到答案,这些通常会被丢弃)

但是,如果类中有任何逻辑,您应该尝试将其与实际 api 隔离开来进行测试。为此,您可以为 ftp api 创建一个包装器。然后在您的单元测试中,您创建一个可以代替包装器的测试替身。有许多不同名称的变体:stub、fake、mock object。底线是您要确保您的单元测试不受任何外部影响。具有零星行为的单元测试并非毫无用处。

应该在集成测试中测试实际的文件传输机制,集成测试通常运行频率较低,因为它速度较慢。即使在集成测试中,您也希望尽可能地控制测试环境。 (即在本地网络上使用配置为模拟生产服务器的 ftp 服务器进行测试)。

请记住,您永远不会提前掌握所有内容。无论测试有多好,错误都会溜走。只需确保当他们这样做时,您添加另一个测试以在下次捕获他们。

我建议购买或查看 Gerard Meszaros 的 XUnit Test Patterns 副本。它是关于什么/何时/如何进行单元测试的有用信息的宝库。

【讨论】:

  • 嗯,这对我来说很有意义:如果您正在测试的类只是包装您正在使用的 ftp 库的 api,那么您不需要对其进行单元测试。
  • +1 表示快速。如果速度不快,那就是集成测试,而不是单元测试。
【解决方案5】:

如果您使用 Indy 10 的 TIdFTP 组件,那么您可以利用 Indy 的 TIdIOHandlerStream 类来伪造 FTP 连接,而无需实际与真正的 FTP 服务器建立物理连接。

创建一个TStream 对象,例如TMemoryStreamTStringStream,其中包含您希望TIdFTP 针对它发送的所有命令接收的FTP 响应(使用数据包嗅探器预先捕获这些命令以提供您知道需要包含什么),并将更新文件的副本放在您通常下载到的本地文件夹中。创建一个TIdIOHandlerStream 对象并将TStream 分配为其ReceiveStream,然后在调用Connect() 之前将该IOHandler 分配给TIdFTP.IOHandler 属性。

例如:

ResponseData := TStringStream.Create(
  '220 Welcome' + EOL +
  ... + // login responses here, etc...
  '150 Opening BINARY mode data connection for filename.ext' + EOL +
  '226 Transfer finished' + EOL +
  '221 Goodbye' + EOL);

IO := TIdIOHandlerStream.Create(FTP, ResponseData); // TIdIOHandlerStream takes ownership of ResponseData by default

FTP.IOHandler := IO;
FTP.Passive := False;  // Passive=True does not work under this setup

FTP.Connect;
try
  FTP.Get('filename.ext', 'c:\path\filename.ext');
  // copy your test update file to 'c:\path\filename.ext'...
finally
  FTP.Disconnect;
end;

【讨论】:

  • Kewl hack +1。我仍然认为抽象更好(没有 indy 或 ICS,或任何网络库依赖,不知道您使用的是 FTP 或 HTTP,只是一个接口)。
  • 即使你使用抽象,你仍然需要测试抽象背后的实现。
  • 同意。但是HTTP和FTP的实现测试可以由组件厂商来完成。
猜你喜欢
  • 2012-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-11
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多