Reading up and picking up on unit testing, trying to make sense of the following post on that explains the hardships of static function calls.
I don't clearly understand this issue. I have always assumed static functions were a nice way of rounding up utility functions in a class. For example, I often use static functions calls to initialise, ie:
Init::loadConfig('settings.php'); Init::setErrorHandler(APP_MODE); Init::loggingMode(APP_MODE); // start loading app related objects .. $app = new App();
// After reading the post, I now aim for this instead ...
$init = new Init(); $init->loadConfig('settings.php'); $init->loggingMode(APP_MODE); // etc ...
But, the few dozen tests I had written for this class are the same. I changed nothing and they still all pass. Am I doing something wrong?
The author of the post states the following:
The basic issue with static methods is they are procedural code. I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in isolation. During the instantiation I wire the dependencies with mocks/friendlies which replace the real dependencies. With procedural programing there is nothing to “wire” since there are no objects, the code and data are separate.
Now, I understand from the post that static methods create dependencies, but don't grasp intuitively why one cannot test the return value of a static method just as easily as a regular method?
I will be avoiding static methods, but I would of liked having an idea of WHEN static methods are useful, if at all. It seems from this post static methods are just about as evil as global variables and should be avoided as much as possible.
Any additional information or links on the subject would be greatly appreciated.
Why static code is difficult to test? Because the logic is assigned to the class definition itself not to instances. Considering the fact that you can't overwrite a static method in Java, you can not overwrite a static behavior in a dummy implementation of the class for your unit tests.
If you need to truly mock static methods, you need to use a commercial tool like Microsoft Fakes (part of Visual Studio Enterprise) or Typemock Isolator. Or, you can simply create a new class to wrap the static method calls, which itself can be mocked.
In computer programming, unit testing is a software testing method by which individual units of source code—sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures—are tested to determine whether they are fit for use.
Static methods themselves aren't harder to test than instance methods. The trouble arises when a method--static or otherwise--calls other static methods because you cannot isolate the method being tested. Here is a typical example method that can be difficult to test:
public function findUser($id) { Assert::validIdentifier($id); Log::debug("Looking for user $id"); // writes to a file Database::connect(); // needs user, password, database info and a database return Database::query(...); // needs a user table with data }
What might you want to test with this method?
InvalidIdentifierException
.Database::query()
receives the correct identifier.null
when not.These requirements are simple, but you must also setup logging, connect to a database, load it with data, etc. The Database
class should be solely responsible for testing that it can connect and query. The Log
class should do the same for logging. findUser()
should not have to deal with any of this, but it must because it depends on them.
If instead the method above made calls to instance methods on Database
and Log
instances, the test could pass in mock objects with scripted return values specific to the test at hand.
function testFindUserReturnsNullWhenNotFound() { $log = $this->getMock('Log'); // ignore all logging calls $database = $this->getMock('Database', array('connect', 'query'); $database->expects($this->once())->method('connect'); $database->expects($this->once())->method('query') ->with('<query string>', 5) ->will($this->returnValue(null)); $dao = new UserDao($log, $database); self::assertNull($dao->findUser(5)); }
The above test will fail if findUser()
neglects to call connect()
, passes the wrong value for $id
(5
above), or returns anything other than null
. The beauty is that no database is involved, making the test quick and robust, meaning it won't fail for reasons unrelated to the test like network failure or bad sample data. It allows you to focus on what really matters: the functionality contained within findUser()
.
Sebastian Bergmann agrees with Misko Hevery and quotes him frequently:
Unit-Testing needs seams, seams is where we prevent the execution of normal code path and is how we achieve isolation of the class under test. Seams work through polymorphism, we override/implement class/interface and then wire the class under test differently in order to take control of the execution flow. With static methods there is nothing to override. Yes, static methods are easy to call, but if the static method calls another static method there is no way to override the called method dependency.
The main issue with static methods is that they introduce coupling, usually by hardcoding the dependency into your consuming code, making it difficult to replace them with stubs or mocks in your Unit-Tests. This violates the Open/Closed Principle and the Dependency Inversion Principle, two of the SOLID principles.
You are absolutely right that statics are considered harmful. Avoid them.
Check the links for additional information please.
Update: note that while statics are still considered harmful, the capability to stub and mock static methods has been removed as of PHPUnit 4.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With