如何测试连接到FTP服务器的类?

13

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

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

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

编辑

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

让我看看沃伦在他的评论中说的是否正确:

我认为,一旦你通过TCP/IP与另一个应用程序进行通信,我们就应该将其称为“集成测试”。 我们不再测试单元或方法,而是测试系统。

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


2
如果你能编写一个测试来确保类的功能,我会使用这个测试——无论它被称为单元测试还是集成测试。这仍然比没有测试要好。 - Uwe Raabe
1
我处理这个问题的方式是将单元测试和集成测试分开。两者都从dUnit运行,但一个叫做AppUnitTests.exe,另一个叫做AppNetworkIntegrationTests.exe,只包含网络/集成测试。如果我有一些需要很长时间的导入/导出/转换逻辑,我可能会将它们作为另一个测试应用程序。当所有这些都被塞进UnitTests.exe中时,这会减少进行单元测试的动力,并且不再有助于TDD。 - Warren P
5个回答

11

在测试中,首要目的是回答一个问题:什么被测试了——也就是测试的范围。

因此,如果你正在测试FTP服务器的实现,你将需要创建一个FTP客户端。

如果你正在测试FTP客户端,则需要创建一个FTP服务器。

因此,你必须缩小测试范围,直到达到单元级别。

例如,对于你的目的,可能包括:

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

每个被测试的项都需要一些模拟和存根。关于两者之间的区别,请参见这篇文章。简而言之(依我所知),存根只是一个仿真对象,总是有效的。而模拟(在每个测试中应该是唯一的)是可能改变测试结果(通过或失败)的元素。

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

编写良好的测试代码有点困难。测试驱动的方法一开始可能需要花费一些时间,但从长远来看总是更好的。必须阅读一本好书,或至少参考一些参考文章(如上面链接的Martin Fowler的文章)。在Delphi中,使用接口和SOLID原则可以帮助你编写这样的代码,并创建存根/模拟来编写测试。

通过我的实验,每个程序员有时会在编写测试时感到迷失...好的测试编写可能比功能编写更耗费时间,在某些情况下...你被警告了!每个测试都应该被视为一个特性,并且它的成本应该得到评估:这值得吗?这里难道没有更适合的另一个测试吗?我的测试与正在测试的特性分离了吗?它不是已经被测试了吗?我是在测试我的代码,还是第三方/库的特性?

题外话,但我想说一下:HTTP / 1.1 现在甚至对于文件更新可能是一个更好的选择,而非 FTP。您可以恢复 HTTP 连接,分块并行加载 HTTP 内容,并且这种协议比 FTP 更容易在代理中使用。而且,与 FTP 相比,托管一些 HTTP 内容要容易得多(一些 FTP 服务器也存在已知的安全问题)。大多数软件更新现在都是通过 HTTP / 1.1 进行的,而不是 FTP(例如 Microsoft 产品或大多数 Linux 资源库)。

编辑:

您可能会认为,当您使用远程协议时,您正在进行集成测试。这可能是有道理的,但我认为这并不相同。

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

当然,在单元测试之后,还应执行集成和系统测试。但是,FTP 客户端单元测试可以模拟本地运行的 FTP 服务器,同时测试可能出现在真正的全球网络中的所有潜在问题。


我认为,一旦你通过TCP/IP与另一个应用程序交互,我们应该称之为“集成测试”。你不再是在测试一个单元或一个方法,而是在测试一个系统。 - Warren P

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;

+1 给酷炫的黑客。我仍然认为抽象更好(没有 indy 或 ICS,或任何网络库依赖项,也不知道你是否使用 FTP 或 HTTP,只是一个接口)。 - Warren P
即使您使用抽象,仍然必须测试抽象背后的实现。 - Remy Lebeau
同意。但HTTP和FTP的实现测试可以由组件供应商完成。 - Warren P

4

单元测试应该快速,闪电般地快。任何会减缓它们速度的东西都会让你不想运行它们。

它们还应该在每次运行时保持一致。测试实际文件传输将引入随机失败的可能性。

如果您要测试的类仅仅是使用ftp库的api进行封装,那么您已经到达了应用程序的边界,您不需要进行单元测试。(好吧,有时候你确实需要。这被称为探索性测试,但这些通常会被抛弃,一旦你得到答案)

然而,如果类中有任何逻辑,您应该尝试从实际api中隔离它进行测试。您可以通过创建ftp api的包装器来实现这一点。然后在您的单元测试中,您创建一个测试替身,可以代替该包装器。有很多变化,有不同的名称:存根、伪造对象、模拟对象。最重要的是,您要确保您的单元测试与任何外部影响隔离开来。具有零散行为的单元测试不仅毫无用处,还不如不做。

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

请记住,您永远无法一开始就捕捉到所有错误。无论测试有多好,错误都会漏过去。只要确保发现错误后添加另一个测试来捕获它们。

我建议购买或查看 Gerard Meszaros 的 XUnit Test Patterns,它是关于单元测试何时/如何进行的宝库,提供了大量有用的信息。


嗯,这对我来说很有道理:如果你正在测试的类只是简单地封装了你所使用的FTP库的API,那么你就不需要对它进行单元测试。 - Rafael Colucci
如果速度不够快,那就是集成测试而不是单元测试。 - Warren P

2
只需借助您喜欢的套接字组件集(Indy、ICS等)中自带的FTP或HTTP服务器演示即可进行快速测试。我会将其放入一个工具文件夹中,以便与我的单元测试一起使用。我可能会编写一些代码来检查TestFtpServer.exe是否已运行,如果没有,则启动它。为了保持独立的进程,我需要将其保留在单元测试应用程序的进程内存空间之外。
请注意,当您开始进行FTP服务器操作时,单元测试实际上应该被称为“集成测试”。
我不会手动从我的单元测试中创建文件。我希望我的代码能够从版本控制中检出并构建,就像批处理文件一样,运行我的测试程序,该程序知道一个名为Tools的子文件夹,其中包含EXE和可能包含ServerData和LocalData文件夹的数据,这些文件夹可以用于保存正在服务器上启动并传输到我的本地单元测试应用程序的数据。也许您可以通过修改演示服务器使其在会话的某个部分中断(当您想要测试失败时),但我仍然认为您无法获得很好的覆盖率。
请注意,如果您正在执行自动更新,那么我认为无论进行多少单元测试都无法解决所有潜在的因互联网而导致的问题。当您的主机名无法解析时会发生什么?当下载进行到一半失败时会发生什么?自动更新与单元测试的能力并不是很匹配。

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

0

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

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

除了这些测试之外,还可能希望拥有一些端到端测试,这些测试启动整个程序(与组件级专注的集成测试不同),并连接真实的FTP服务器。端到端测试无法涵盖所有边角情况(与单元测试不同),但它们可以帮助解决集成问题


是的,我已经在模拟我的ftp类,这样我就不必总是连接到ftp服务器了。 - Rafael Colucci
不仅应该模拟您的FTP类,还应该将其模拟为传输层。单元测试不应该知道您是使用FTP、HTTP还是信鸽。 - Warren P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接