vfsStream A better approach for file system dependent tests Frank Kleine, 1&1 Internet AG
 
 
 
 
 
 
T-Shirt available at zazzle.de (no, I'm not paid for this)
 
(Obligatory Zen-Style image)
 
AND NOW FOR SOMETHING COMPLETELY DIFFERENT
Unit tests <ul><li>I assume you do unit tests </li></ul><ul><li>Testing file system related functions/methods can be hard ...
Basic example: a method to test class Example {   public function __construct($id) {    $this->id = $id;   }  public funct...
Basic example: traditional test $DIR = dirname(__FILE__); public function setUp() {   if (file_exists($DIR . '/id')) {    ...
Basic example: vfsStream test public function setUp() {   vfsStreamWrapper::register();   $root = new vfsStreamDirectory('...
Advantages <ul><li>Cleaner tests </li></ul><ul><li>Nothing happens on disc, all operations are memory only </li></ul><ul><...
How does this work? <ul><li>PHP stream wrapper </li></ul><ul><ul><li>Allows you to invent your own url protocol </li></ul>...
What does not work? <ul><li>File system functions working with pure file names only: </li></ul><ul><ul><li>realpath() </li...
Example with file mode class Example {   public function __construct($id,  $mode = 0700) {    $this->id   = $id;    $this-...
Example with file mode, cont. $DIR = dirname(__FILE__);   public function testDirDefaultFilePermissions() {   $example = n...
Example with file mode, cont. public function setUp() {   vfsStreamWrapper::register();   $this->root = new vfsStreamDirec...
Advantages <ul><li>Independent of operating system a test is running on </li></ul><ul><li>Intentions of test cases become ...
Different config files class RssFeedController { public function __construct($configPath) { $feeds = Properties::fromFile(...
Different config files, cont. public function setUp() {   vfsStreamWrapper::register();   $root = new vfsStreamDirectory('...
Different config files, cont. 2 /** * @test * @expectedException  ConfigurationException **/ public function noFeedsSectio...
Different config files, cont. 3 /** * @test * @expectedException  ConfigurationException **/ public function noFeedsConfig...
Different config files, cont. 4 /** * @test **/ public function selectsFirstFeedIfNoneGivenWithRequestValue() { $this->con...
Different config files, cont. 5 /** * @test **/ public function selectsOtherFeedBasedOnRequestValue() { $this->configFile-...
Advantages <ul><li>Concentrate on the test, not on config files </li></ul><ul><li>Everything is in the test, no test relat...
vfsStream 0.7.0 <ul><li>Considers file permissions correctly </li></ul><ul><li>Allows to write tests to check how your app...
File permissions class Example {   public function writeConfig($config, $configFile) {   file_put_contents($configFile, se...
File permissions, the tests /** * @test */ public function normalTest() { vfsStreamWrapper::setRoot(vfsStream::newDirector...
File permissions, another test /** * @test */ public function directoryNotWritable() { vfsStreamWrapper::setRoot( vfsStrea...
Advantages <ul><li>Allows simple tests you would not do otherwise because they are too hard to do </li></ul><ul><li>Helps ...
 
Ressources <ul><li>http://vfs.bovigo.org/ </li></ul><ul><li>http://php.net/class.streamwrapper </li></ul><ul><li>Twitter <...
 
 
Upcoming SlideShare
Loading in …5
×

vfsStream - a better approach for file system dependent tests

5,734 views

Published on

Have you ever been annoyed by testing classes or functions operating on the file system? Be it tests that rely on presence of physical files, the problem of not cleaning up correctly after the test run or checking that your algorithm creates the correct directories and files with correct file permissions. Then this is for you: vfsStream to the rescue!

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,734
On SlideShare
0
From Embeds
0
Number of Embeds
225
Actions
Shares
0
Downloads
0
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

vfsStream - a better approach for file system dependent tests

  1. 1. vfsStream A better approach for file system dependent tests Frank Kleine, 1&1 Internet AG
  2. 8. T-Shirt available at zazzle.de (no, I'm not paid for this)
  3. 10. (Obligatory Zen-Style image)
  4. 12. AND NOW FOR SOMETHING COMPLETELY DIFFERENT
  5. 13. Unit tests <ul><li>I assume you do unit tests </li></ul><ul><li>Testing file system related functions/methods can be hard </li></ul><ul><li>setup(), teardown(), and unusual scenarios </li></ul>
  6. 14. Basic example: a method to test class Example { public function __construct($id) {   $this->id = $id; } public function setDirectory($dir) { $this->dir = $dir . '/' . $this->id;     if (file_exists($this->dir) === false) {         mkdir($this->dir, 0700, true);     } } … }
  7. 15. Basic example: traditional test $DIR = dirname(__FILE__); public function setUp() { if (file_exists($DIR . '/id')) {       rmdir($DIR . '/id');   } } public function tearDown() { if (file_exists($DIR . '/id')) {       rmdir($DIR . '/id');   } } public function testDirectoryIsCreated() { $example = new Example('id');   $this->assertFalse(file_exists($DIR . '/id'));   $example->setDirectory($DIR);   $this->assertTrue(file_exists($DIR . '/id')); }
  8. 16. Basic example: vfsStream test public function setUp() { vfsStreamWrapper::register(); $root = new vfsStreamDirectory('aDir');   vfsStreamWrapper::setRoot($root); } public function testDirectoryIsCreated() { $url = vfsStream::url('aDir/id');   $example = new Example('id');   $this->assertFalse(file_exists($url));   $example->setDirectory(vfsStream::url('aDir'));   $this->assertTrue(file_exists($url)); }
  9. 17. Advantages <ul><li>Cleaner tests </li></ul><ul><li>Nothing happens on disc, all operations are memory only </li></ul><ul><li>More control over file system environment </li></ul>
  10. 18. How does this work? <ul><li>PHP stream wrapper </li></ul><ul><ul><li>Allows you to invent your own url protocol </li></ul></ul><ul><li>Whenever a vfs:// url is given to a file system function PHP calls back the registered stream implementation for this protocol </li></ul><ul><li>Works well for most of file system functions </li></ul>
  11. 19. What does not work? <ul><li>File system functions working with pure file names only: </li></ul><ul><ul><li>realpath() </li></ul></ul><ul><ul><li>touch() </li></ul></ul><ul><li>File system function without stream wrapper support: </li></ul><ul><ul><li>chmod() </li></ul></ul><ul><ul><li>chown() </li></ul></ul><ul><ul><li>chgrp() </li></ul></ul><ul><li>ext/zip does not support userland stream wrappers </li></ul>
  12. 20. Example with file mode class Example { public function __construct($id,  $mode = 0700) {   $this->id   = $id;   $this->mode = $mode; } public function setDirectory($dir) { $this->dir = $dir . '/' . $this->id;     if (file_exists($this->dir) === false) {         mkdir($this->directory, $this->mode, true);     } } … }
  13. 21. Example with file mode, cont. $DIR = dirname(__FILE__); public function testDirDefaultFilePermissions() { $example = new Example('id');   $example->setDirectory($DIR);   if (DIRECTORY_SEPARATOR === 'apos;) {     $this->assertEquals(40777, decoct(fileperms($DIR . '/id')));   } else {     $this->assertEquals(40700, decoct(fileperms($DIR . '/id')));   } } public function testDirDifferentFilePermissions() {   $example = new Example('id', 0755);   $example->setDirectory($DIR);   if (DIRECTORY_SEPARATOR === 'apos;) {     $this->assertEquals(40777, decoct(fileperms($DIR . '/id')));   } else {     $this->assertEquals(40755, decoct(fileperms($DIR . '/id')));   } }
  14. 22. Example with file mode, cont. public function setUp() { vfsStreamWrapper::register(); $this->root = new vfsStreamDirectory('aDir');   vfsStreamWrapper::setRoot($this->root); } public function testDirDefaultFilePermissions() { $example = new Example('id');   $example->setDirectory(vfsStream::url('aDir'));   $this->assertEquals(0700, $this->root->getChild('id')->getPermissions()); } public function testDirDifferentFilePermissions() {   $example = new Example('id', 0755);   $example->setDirectory(vfsStream::url('aDir'));   $this->assertEquals(0755, $this->root->getChild('id')->getPermissions()); }
  15. 23. Advantages <ul><li>Independent of operating system a test is running on </li></ul><ul><li>Intentions of test cases become more clear </li></ul><ul><li>Easier to understand </li></ul>
  16. 24. Different config files class RssFeedController { public function __construct($configPath) { $feeds = Properties::fromFile($configPath . '/rss-feeds.ini') ->getSection('feeds', array()); if (count($feeds) === 0) { throw new ConfigurationException(); } $this->routeName = valueFromRequest(); if (null === $this->routeName) { // no special feed requested, use first configured one reset($feeds); $this->routeName = key($feeds); } } … }
  17. 25. Different config files, cont. public function setUp() { vfsStreamWrapper::register(); $root = new vfsStreamDirectory('config');   vfsStreamWrapper::setRoot($root); $this->configFile = vfsStream::newFile('rss-feeds.ini') ->at($root); } /** * @test * @expectedException FileNotFoundException **/ public function loadFeedsFailsIfFeedConfigFileDoesNotExist() { $example = new RssFeedController(vfsStream::url('doesNotExist')); }
  18. 26. Different config files, cont. 2 /** * @test * @expectedException ConfigurationException **/ public function noFeedsSectionConfiguredThrowsException() { $this->configFile->setContent(''); $example = new RssFeedController(vfsStream::url('config')); }
  19. 27. Different config files, cont. 3 /** * @test * @expectedException ConfigurationException **/ public function noFeedsConfiguredThrowsException() { $this->configFile->setContent('[feeds]'); $example = new RssFeedController(vfsStream::url('config')); }
  20. 28. Different config files, cont. 4 /** * @test **/ public function selectsFirstFeedIfNoneGivenWithRequestValue() { $this->configFile->setContent('[feeds] default = &quot;org::stubbles::test::xml::rss::DefaultFeed&quot;'); $example = new RssFeedController(vfsStream::url('config')); // assertions that the default feed was selected … }
  21. 29. Different config files, cont. 5 /** * @test **/ public function selectsOtherFeedBasedOnRequestValue() { $this->configFile->setContent(&quot;[feeds] default = &quot;org::stubbles::test::xml::rss::DefaultFeed&quot; other = &quot;org::stubbles::test::xml::rss::OtherFeed&quot; &quot;); $example = new RssFeedController(vfsStream::url('config')); // assertions that the other feed was selected … }
  22. 30. Advantages <ul><li>Concentrate on the test, not on config files </li></ul><ul><li>Everything is in the test, no test related information in external files </li></ul><ul><li>Simple to set up </li></ul><ul><li>Create different test scenarios on the fly </li></ul>
  23. 31. vfsStream 0.7.0 <ul><li>Considers file permissions correctly </li></ul><ul><li>Allows to write tests to check how your application responds to incorrect file permissions </li></ul><ul><li>To be released in the next days </li></ul><ul><li>Probably buggy :-> </li></ul>
  24. 32. File permissions class Example { public function writeConfig($config, $configFile) { file_put_contents($configFile, serialize($config)); } … }
  25. 33. File permissions, the tests /** * @test */ public function normalTest() { vfsStreamWrapper::setRoot(vfsStream::newDirectory('exampleDir')); $example = new FilePermissionsExample(); $example->writeConfig(array('foo' => 'bar'), vfsStream::url('exampleDir/writable.ini') ); // assertions here }
  26. 34. File permissions, another test /** * @test */ public function directoryNotWritable() { vfsStreamWrapper::setRoot( vfsStream::newDirectory('exampleDir', 0444) ); $example = new FilePermissionsExample(); $example->writeConfig(array('foo' => 'bar'), vfsStream::url('exampleDir/config.ini') ); }
  27. 35. Advantages <ul><li>Allows simple tests you would not do otherwise because they are too hard to do </li></ul><ul><li>Helps to find probably buggy code or code which behaves not user friendly </li></ul><ul><li>Makes your application more failsafe </li></ul>
  28. 37. Ressources <ul><li>http://vfs.bovigo.org/ </li></ul><ul><li>http://php.net/class.streamwrapper </li></ul><ul><li>Twitter </li></ul><ul><ul><li>@bovigo </li></ul></ul><ul><ul><li>@mikey179 </li></ul></ul>

×