I've read a lot lately about TDD, but I've only seen small examples.
Now I have to start with a (not large, but more than just a single class) file impo开发者_JS百科rter which has to be robust, so it has to check whether the file is present (on the FTP server), if it isn't reschedule itself, don't crash when the file is corrupted, is re-runnable etc.: a importer that can be started and hasn't to be checked for the log files every day.
So when starting to build such a program, where do i start with TDD?
Is it at the lowest level, like implementing the GetFile() or the Reschedule() and then working my way up, or do I create the 'Controller' first and let that one fail first because I did not set any test file and the drill down in all the functionalities?
Since the main focus of a file importer is to import a file (yes?) that ought to be your first port of call. Write a test to ensure that you are able to retrieve a file.
Personally, I would sketch out what you want to implement using UML before getting down to coding. It will help you identify what you need to write.
Well - start at the start. The first nicely testable bit of functionality is "it has to check whether the file is present on the FTP server". I can think of at least two test cases for that: the file is present, and the file isn't present. That's two tests. What about the server isn't present? What' supposed to happen then? Well, test that, too. As you describe what your unit is supposed to do, in English as you've done in the question, translate the description into tests.
There's no bit of functionality you don't want to work, is there? Then test all of it. The order in which you write the tests doesn't (much) matter - in the end you will want every bit tested (and if you're really following TDD, every bit will have been tested before that bit was even written).
I guess I would start with what I was trying to accomplish. So, for example, if I were writing some sort of personal finance software I would start with something like:
@Test public void importsTransactionsFromQuicken() { List transactions = new QuickenImporter().importFrom("filename.qfx"); assertSomeStuffAbout(transactions); }
Now, once you have that working, you could refactor and mock out the file handling to avoid some I/O. This will probably drive out an abstraction for reading the file.
Next, start looking for some other scenarios. For example, you give corrupt files as an example. Well, what should happen in the event of a corrupt file?
@Test public void logsAndRemovesCorruptFiles() { File cf = new CorruptFile(); Logger ls = new LogSpy(); // Note, this might be the refactored interface for after mocking out I/O QuickenImporter qi = new QuickenImporter(cf, ls); List transactions = qi.import(); assertEmptyList(transactions); assertFileWasDeleted(cf); assertCorruptLogEntryWasWritten(ls); }
You can see I did some refactoring, including some constructor injection, etc. but the tests really drove out the next step. As far as the "Reschedule" function, well it seems to violate the Single Responsibility Principle so it may belong in another class, for instance in an ImportScheduler
class. If that's the case I know what I want the importer behavior to be when it can't find the file, so I'll make another test:
@Test public void doesntReturnAnyTransactionsWhenFileNotPresent() { QuickenImporter qi = new QuickenImporter(new NonExistentFile(), NULL_LOGGER); List transactions = qi.import(); assertEmptyList(transactions); }
Now, in order to test the scheduling component I can write an ImportSchedulerTest
with test cases for both conditions (when I have a file and when I do not).
Hope that helps!
精彩评论