2

In the process of learning test-driven development, I've been introduced to dependency injection and the use of interfaces, and have started using these concepts in my own PHP code in order to make it more testable.

There have been times when I've needed to test code that was doing things like calling the PHP time() function. In order to make these tests predictable, it seemed logical to create an interface to the standard PHP functions I use so that I can mock them out in my tests.

Is this good software design? What are the pros and cons of doing this? I've found myself groaning at how quickly my PHP interface can stick its fingers into everything I do. Is there a better way to make code that relies on PHP-accessed state and functions more testable?

asked Jun 11, 2014 at 17:01
3
  • 1
    Martin Fowler's take: martinfowler.com/articles/nonDeterminism.html#Time Commented Jun 11, 2014 at 18:08
  • @MikePartridge: Excellent read. So would a good strategy, then, be to only wrap those PHP functions which introduce non-deterministic global state? Commented Jun 11, 2014 at 18:19
  • I believe so, yes. Commented Jun 11, 2014 at 18:23

2 Answers 2

5

You should abstract away calls to time() like you should abstract away calls to rand() and its ilk. The reason for doing that is because functions like that are forms of global, non-deterministic state. Global, non-deterministic state makes testing much more difficult because you cannot isolate your tests and test all conditions at once. Imagine if I had to wait until after noon to see if my code said "Good afternoon" instead of "Good morning" or "Good evening"!

If you see your interface starting to permeate your application, this may not be a bad thing. If anything, it is telling you how much you are using these built-in functions. You can depend on you to maintain your code and your interfaces within your code base, so you should not have as many worries about a shifting interface causing shotgun surgery. Abstracting away time() should be a pretty stable interface and probably should not see any changes for the most part.

The alternative to an interface is to simply inject the result of a call to time() rather than calling it in the method. So write getGreeting(time()); rather than writing getGreeting($timeUtil); (which in the method does $time = $timeUtil->getTime()). This adheres more closely to the Law of Demeter as you are not reaching for one thing through another, but it also means that the time you want must be the time at which the method is invoked.

An alternative alternative is to pass in a function reference rather than generating a separate interface. In PHP, you could do something like:

function getTime($timeFunc) {
 return call_user_func($timeFunc);
}
echo getTime('time')."\n"; # Call the built-in time function
echo getTime(function() { # Call a custom function
 return 1;
})."\n";

This has less overhead than the full-blown interface while still allowing the function to be lazily called and allowing dependency injection.

answered Jun 11, 2014 at 18:21
3
  • Very clear explanation, +1. I only have a general knowledge of the Law of Demeter. If wrapping time() violates the Law of Demeter, wouldn't every interface do the same? Commented Jun 11, 2014 at 18:31
  • Wrapping time() (or generally using an interface) doesn't necessarily violate the Law of Demeter. Demeter is about getting what you need while avoiding unnecessary indirection (which creates dependencies). If I need the time at the moment getTime is called, then it makes the most sense just pass in the actual time. No additional dependencies. If I need multiple times, or times requested lazily, I really need the interface (or function reference) to call at my leisure. I have a dependency on the functionality I need, but nothing more. Commented Jun 11, 2014 at 18:41
  • 1
    Thanks, again for the great answer! Accepted. I ended up taking a more critical look at my PHP interface and removing all the PHP function-wrapping methods that weren't contributing to testability. This had the net effect of both reducing my dependence on the interface while maintaining the ability to control non-deterministic global state during testing. Commented Jun 12, 2014 at 13:25
2

You can either

  • create interfaces to system state methods like date or the content of session/cookie as you suggested or,
  • put these infos into business-method as additional parameters.

Example:

  • the invoice module can get the invoice date itself through an interface or,
  • the caller of the invoice module can provide the invoice date.

I prefer the 2nd method although this means that every time the module needs more state infos you have to change the method signature. On the other side in testing it is much easier to provide parameter than to mock methods.

I've found myself groaning at how quickly my PHP interface can stick its fingers into everything I do.

If your module needs too many parameters or too many interfaces, this might be an indicator that your module is doing too much and violates "separations of concerns."

cbojar
4,2611 gold badge20 silver badges19 bronze badges
answered Jun 11, 2014 at 17:37
1
  • Very practical tips. +1 Commented Jun 11, 2014 at 18:18

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.