13

I am developing a live update for my application. So far, I have created almost all unit tests but I have no idea how to test an specific class that connects to a FTP server and downloads new versions.

To test this class, should I create an FTP test server and use it in my unit tests? If so, how can I make sure this FTP server is always consistent to my tests? Should I create manually every file I will need before the test begin or should I automate this in my Test Class (tear down and setup methods)?

This question also applies to unit testing classes that connects do any kind of server.

EDIT

I am already mocking my ftp class so I dont always need to connect to the ftp server in other tests.

Let me see if I got this right about what Warren said in his comment:

I would argue that once you're talking to a separate app over TCP/IP we should call that "integration tests". One is no longer testing a unit or a method, but a system.

When a unit test needs to communicate to another app (that can be a HTTP server or FTP server) is this no longer a unit test but a integration server? If so, am I doing it wrong by trying to use unit testing techniques to create this test? Is it correct to say that I should not unit test this class? It does make sense to me because it seems to be a lot of work for a unit test.

Rafael Colucci
  • 6,018
  • 4
  • 52
  • 121
  • 2
    If you can manage to write a test that ensures the functionality of the class I would use this test - be it called a unit test or an integration test. It is still better than no test at all. – Uwe Raabe Mar 27 '12 at 20:02
  • 1
    The way I do it is separate my unit tests from my integration tests. Both run from dUnit, but one is called AppUnitTests.exe and one is called AppNetworkIntegrationTests.exe and contains only network/integration tests. If I had some import/export/convert logic that takes a long time I might make those yet another test app. When those all get jammed into UnitTests.exe, that creates a disincentive to unit testing and is no longer conducive to TDD. – Warren P Mar 27 '12 at 20:18

5 Answers5

11

In testing, the purpose is always first to answer the question: what is tested - that is, the scope of the test.

So if you are testing a FTP server implementation, you'll have to create a FTP client.

If you are testing a FTP client, you'll have to create a FTP server.

You'll have therefore to downsize the test extend, until you'll reach an unitary level.

It may be e.g. for your purpose:

  • Getting a list of the current files installed for the application;
  • Getting a list of the files available remotely;
  • Getting a file update;
  • Checking that a file is correct (checksum?);
  • and so on...

Each tested item is to have some mocks and stubs. See this article about the difference between the two. In short (AFAIK), a stub is just an emulation object, which always works. And a mock (which should be unique in each test) is the element which may change the test result (pass or fail).

For the exact purpose of a FTP connection, you may e.g. (when testing the client side) have some stubs which return a list of files, and a mock which will test several possible issues of the FTP server (time out, connection lost, wrong content). Then your client side shall react as expected. Your mock may be a true FTP server instance, but which will behave as expected to trigger all potential errors. Typically, each error shall raise an exception, which is to be tracked by the test units, in order to pass/fail each test.

This is a bit difficult to write good testing code. A test-driven approach is a bit time consuming at first, but it is always better in the long term. A good book is here mandatory, or at least some reference articles (like Martin Fowler's as linked above). In Delphi, using interfaces and SOLID principles may help you writing such code, and creating stubs/mocks to write your tests.

From my experiment, every programmer can be sometimes lost in writing tests... good test writing can be more time consuming than feature writing, in some circumstances... you are warned! Each test shall be see as a feature, and its cost shall be evaluated: is it worth it? Is not another test more suitable here? Is my test decoupled from the feature it is testing? Is it not already tested? Am I testing my code, or a third-party/library feature?

Out of the subject, but my two cents: HTTP/1.1 may be a better candidate nowadays than FTP, even for file update. You can resume a HTTP connection, load HTTP content by chunks in parallel, and this protocol is more proxy friendly than FTP. And it is much easier to host some HTTP content than FTP (some FTP servers have also known security issues). Most software updates are performed via HTTP/1.1 these days, not FTP (e.g. Microsoft products or most Linux repositories).

EDIT:

You may argue that you are making integration tests, when you use a remote protocol. It could make sense, but IMHO this is not the same.

To my understanding, integration tests take place when you let all your components work together as with the real application, then check that they are working as expected. My proposal about FTP testing is that you are mocking a FTP server in order to explicitly test all potential issues (timeout, connection or transmission error...). This is something else than integration tests: code coverage is much bigger. And you are only testing one part of the code, not the whole code integration. This is not because you are using some remote connection that you are doing integration tests: this is still unitary testing.

And, of course, integration and system tests shall be performed after unitary tests. But FTP client unitary tests can mock a FTP server, running it locally, but testing all potential issues which may occur in the real big world wide web.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • I would argue that once you're talking to a separate app over TCP/IP we should call that "integration tests". One is no longer testing a unit or a method, but a system. – Warren P Mar 27 '12 at 19:19
5

If you are using Indy 10's TIdFTP component, then you can utilize Indy's TIdIOHandlerStream class to fake an FTP connection without actually making a physical connection to a real FTP server.

Create a TStream object, such as TMemoryStream or TStringStream, that contains the FTP responses you expect TIdFTP to receive for all of the commands it sends (use a packet sniffer to capture those beforehand to give you an idea of what you need to include), and place a copy of your update file in the local folder where you would normally download to. Create a TIdIOHandlerStream object and assign the TStream as its ReceiveStream, then assign that IOHandler to the TIdFTP.IOHandler property before calling Connect().

For example:

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • +1 for Kewl hack. I still think abstract is better (no indy or ICS, or any Network library dependency, no knowledge that you're using FTP or HTTP, just an interface). – Warren P Mar 27 '12 at 20:16
  • Even if you use abstraction, you still have to test the implementations that are behind the abstraction. – Remy Lebeau Mar 27 '12 at 22:33
  • Agreed. But the implementation test for HTTP and FTP can be done by the component vendors. – Warren P Mar 27 '12 at 22:42
4

Unit tests are supposed to be fast, lightening fast. Anything that slows them down discourages you from wanting to run them.

They are also supposed to be consistent from one run to another. Testing an actual file transfer would introduce the possibility for random failures in your unit tests.

If the class you are testing does nothing more than wrap the api of the ftp library you are using then you've reached one of the boundaries of your application you don't need to unit test it. (Well, sometimes you do. Its called exploratory testing but these are usually thrown away once you get your answer)

If, however, there is any logic in the class you should try to test it in isolation from the actual api. You do this by creating a wrapper for the ftp api. Then in your unit tests you create a test double that can stand in as a replacement for the wrapper. There are lots of variations that go by different names: stub, fake, mock object. Bottom line is you want to make sure your unit tests are isolated from any external influence. A unit test with sporadic behavior is less than useless.

Testing the actual file transfer mechanism should be done in integration testing which is usually run less frequently because its slower. Even in integration testing you'll want to try to control the test environment as much as possible. (i.e. testing with a ftp server on a the local network that is configured to mimic the production server).

And remember, you'll never catch everything up front. Errors will slip through no matter how good the tests are. Just make sure when they do that you add another test to catch them the next time.

I would recommend either buying or checking out a copy of XUnit Test Patterns by Gerard Meszaros. Its a treasure trove of useful information on what/when/how to unit test.

Kenneth Cochran
  • 11,954
  • 3
  • 52
  • 117
  • Well, this does make sense to me a lot: If the class you are testing does nothing more than wrap the api of the ftp library you are using then you don't need to unit test it. – Rafael Colucci Mar 27 '12 at 19:40
  • +1 for Fast. If it's not fast, it's an integration test, not a unit test. – Warren P Mar 27 '12 at 20:15
2

Just borrow the FTP or HTTP Server demo that comes with whatever socket component set you prefer (Indy, ICS, or whatever). Instant test server.

I would put it into a tools folder to go with my unit tests. I might write some code that checks if TestFtpServer.exe is already live, and if not, launch it.

I would keep it out of my unit test app's process memory space, thus the separate process.

Note that by the time you get to FTP server operations, unit testing should really be called "integration testing".

I would not manually create files from my unit test. I would expect that my code should check out from version control, and build, as it is, from a batch file, which runs my test program, which knows about a sub-folder called Tools that contains EXEs and maybe a folder called ServerData and LocalData that could be used to hold the data that is starting out on the server and being transferred down to my local unit test app. Maybe you can hack your demo server to have it terminate a session part way through (when you want to test failures) but I still don't think you're going to get good coverage.

Note If you're doing automatic updates, I think that no amount of unit testing is going to cut it. You need to deal with a lot of potential issues that are internet related. What happens when your hostname doesn't resolve? What happens when a download gets part way through and fails? Automatic-updating is not a great match with the capabilities of unit testing.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • I prefer the one that comes with [ICS](http://www.overbyte.be). It's a fully-featured FTP server that offers more specific testing and will visually display both what it received and what it's sending back. I use it for all kinds of testing of EDI transmissions, including testing across a VPN. Indy's demo is not really fully implemented, and sometimes can be hard to find in a version that matches the version of Indy installed in a particular IDE - you have to resolve the compiler errors before you can even start trying to use the server itself. – Ken White Mar 27 '12 at 19:09
  • ICS is excellent. However if the OP is more familiar with Indy they might find that works fine. Part of the problem with Indy demos is that a lot of them got abandoned in the Indy9 to Indy10 API change. – Warren P Mar 27 '12 at 19:17
  • For running a server to test against, what difference does it make what the user is familiar with? You run the server and then ignore it until you're looking at a memo control to see what your client sent and received. :) ICS's server demo lets you test a lot more functionality than the Indy demo does, which can be pretty important sometimes. – Ken White Mar 27 '12 at 19:20
  • Okay, Ken, no big disagreement. I just don't think that the OP's choice matters as much as all that, in this case. Indy (as Remy points out below) has some interesting capabilities. You prefer ICS, and so do I, actually. But both are fully capable. The OP's question shouldn't even be FTP centric. Perhaps HTTP is the right choice, and FTP is going away in a future edit of his question. :-) – Warren P Mar 27 '12 at 20:20
0

Write a couple of focused integration tests for the one component which knows how to communicate with an FTP server. For those tests you will need to start an FTP server before each tests, put there any files needed by the test, and after the test shutdown the server.

With that done, in all other tests you won't use the component which really connects to an FTP server, but you will use a fake or mock version of it (which is backed by some in-memory data structure instead of real files and network sockets). That way you can write unit tests, which don't need an FTP server or network connection, for everything else except the FTP client component.

In addition to those tests, it might be desirable to also have some end-to-end tests which launch the whole program (unlike the component-level focused integration tests) and connect a real FTP server. End-to-end tests can't cover all corner cases (unlike unit tests), but they can help to solve integration issues.

Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
  • Yeah, I am already mocking my ftp class so I dont always need to connect to the ftp server. – Rafael Colucci Mar 27 '12 at 19:23
  • Not only should you mock your FTP class, you should mock it as a Transport layer. The unit test shouldn't know if you're using FTP, HTTP, or Carrier-Pigeons. – Warren P Mar 27 '12 at 20:15