thefuzzy0ne
thefuzzy0ne

Reputation: 481

TDD with an MP3 library

I'm trying to learn TDD, and my first project is a PHP-based project to help me organise my somewhat small MP3 collection. I'm aware that there are many other, much better solutions out there, but this is simply for the sake of getting to grips with TDD.

I require a method that will accept a filename, and return the duration of the MP3 using a command line call to ffmpeg. Is it possible to test this method short of pointing it to a real MP3? Should I just test the simple things, such as whether the file even exist? Should I bother testing it at all?

I'd apprecate your thoughts.

Many thanks in advance.

EDIT: I'm sorry for not mentioning that the call to ffmpeg is not via an class or API as such, but via the CLI.

$output = shell_exec("{$ffmpeg_exe} -i \"{$file_path}\" 2>&1");

This is where I'm having trouble testing. I don't think there's any way to mock this without using Runkit, which I'd like to avoid, since it can't be installed via Composer, and I'd like to avoid dependencies that need to be compiled.

Upvotes: 0

Views: 125

Answers (2)

carols10cents
carols10cents

Reputation: 7014

It depends on what you want to call a "unit test" :)

In my opinion, a unit test should not depend on anything outside of the class under test. It definitely should not be making network requests, database calls, or touching the file system.

The test you describe is a test I would call an integration or acceptance test-- and writing a failing acceptance test is often the start to my personal TDD cycle. Then, if appropriate, I break that down into smaller pieces and write a failing unit test, and cycle red-green-refactor on multiple unit tests until the acceptance test is passing.

Another way to make your tests more isolated is to use test doubles (the general term for mocks, stubs, fakes, spies, etc). This might be the best approach in your case-- have a mock object that acts like an object would that interacts with the file system, but that you can control to act in a certain way. I would have an argument to pass this in, and if nothing is passed in for this, use the built in file interaction libraries.

I haven't done PHP in a while, but here's some pseudocode:

function duration(filename, filesystem)
  filesystem = PHP File interacting object if filesystem isn't passed in
  file = filesystem.find(filename)
  return file.duration

That way, in your tests you can do something like:

test that the duration function returns the duration of the file whose name is passed in
  fake_file = stub that returns 3:08 when we call duration on it
  fake_filesystem = stub that returns fake_file when we call find on it with a filename
  assert_equal duration("some_filename.mp3", fake_filesystem), 3:08

Again, super pseudocode!!! Best of luck!!


Response to your edit:

Aaahhh I see. I assume you're then parsing the $output to extract the information you want and to handle any errors that might be returned by running this command. That, IMO, is the interesting part of your code that should be tested. So I'd probably put the shell_exec within a function that I would then not bother testing, but in the tests for your parsing logic, I would stub the return value of this function to be able to test what you do when you get various output.

I'd also have at least one integration or acceptance test that would actually call shell_exec and actually need a file present at the $file_path.

This shell_exec line is basically doing string concatenation, so it's not really worth testing. TDD is not so much a rule as a guideline, and sometimes there are times you have to decide not to test :)

Upvotes: 1

freemanoid
freemanoid

Reputation: 14770

You can stub ffmpeg to not use the real files.

Upvotes: 0

Related Questions