Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")
Moderator: General Moderators
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Sun Apr 27, 2008 7:54 am
Aha, so what you're saying is that
Code: Select all
$dataStore = new DataStore(new XmlModel('file.xml'));
$dataStore->set('whatever', 'xyz');
$dataStore->save();
Will save the whatever/xyz back to the file.xml
and
Code: Select all
$dataStore = new DataStore(new XmlModel('file.xml'));
$dataStore->set('whatever', 'xyz');
$dataStore->save(new YamlModel('file.yml'));
will
1) read the file.xml and load it's data in as an array
2) set the whatever to xyz
3a) write the data to a new file file.yml
3b) or write the data to an existing file.yml
arjan.top
Forum Contributor
Posts: 305 Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia
Post
by arjan.top » Sun Apr 27, 2008 7:56 am
I was just writing test for such interface, so I'm for it
Code: Select all
public function testSavingWithModelPassedAsArgument() {
$storageModel = $this->_createStorageModel();
$storageModel->expectOnce('save');
$this->_dataStore->save($storageModel);
}
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098 Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia
Post
by Chris Corbyn » Sun Apr 27, 2008 8:58 am
~matthijs, you're correct, it would:
a) Load data from XmlModel
b) Modify it by setting 'whatever' to 'xyz'
c) Save the new modified data to YamlStore in just the same way it would save to XmlStore if no argument was passed
~arjan.top, that looks like a good first step
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098 Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia
Post
by Chris Corbyn » Sun Apr 27, 2008 9:03 am
Code to pass your test ~arjan.top:
Code: Select all
public function save(StorageModel $storageModel = null) {
if (!isset($storageModel)) {
$storageModel = $this->_storageModel;
}
return $storageModel->save($this->_store);
}
I'd also like to get some test coverage to ensure that the DataStore doesn't try saving to the StorageModel it was passed in the constructor if an optional one is given. i.e.
Code: Select all
public function testSavingModelPassedAsArgumentDoesntSaveOriginal() {
$this->_storageModel->expectNever('save');
$this->_dataStore->save($this->_createStorageModel());
}
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098 Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia
Post
by Chris Corbyn » Sun Apr 27, 2008 9:31 pm
Does anybody wish to write the test to prove that we're passing a correct array to this optional StorageModel? I think once we're sure about that we'll move onto a new class entirely. We'll limit ourselves to string data types for now for the sake of brevity. We can always expand our options later
Two tests:
1. Ensure a simple 1-dimensional array passed to the optional second StorageModel
2. Ensure a multidimensional array is passed to the optional second StorageModel
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 12:54 am
Maybe for the first?
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->expectOnce('load', array( array('foo' => 'bar') ));
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar', 'zip'=>'button') ));
$dataStore->save($secondStorageModel);
}
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 2:06 am
Ok, that was not right. This one passes;
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->setReturnValue('load', array('foo' => 'bar') );
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar','zip'=>'button' ) ));
$dataStore->save($secondStorageModel);
}
Somehow these arrays keep giving me headaches
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098 Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia
Post
by Chris Corbyn » Mon Apr 28, 2008 2:09 am
Almost right, but your first expectOnce() should actually just be setReturnValue().
Your expectOnce() there is saying that you expect load() to be called with array('foo' => 'bar') as an argument. What you want is that when load() is called, it will return array('foo' => 'bar') as a value
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->setReturnValue('load', array('foo' => 'bar'));
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar', 'zip'=>'button') ));
$dataStore->save($secondStorageModel);
}
We could also write this test without loading any existing data for brevity:
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData() {
$this->_dataStore->set('foo', 'bar');
$storageModel = $this->_createStorageModel();
$storageModel->expectOnce('save', array( array('foo' => 'bar') ));
$this->_dataStore->save($storageModel);
}
EDIT | Damn, I'm too slow
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 2:11 am
Yeah I discovered that
([edit]meaning I discovered my error, not that you are slow!)
Setting deeper also works, it seems:
Code: Select all
public function testSetDataAndPassComplexArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->setReturnValue('load', array('foo' => 'bar') );
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('bar/zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar', 'bar'=>array('zip'=>'button') ) ));
$dataStore->save($secondStorageModel);
}
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098 Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia
Post
by Chris Corbyn » Mon Apr 28, 2008 3:25 am
Brilliant, let's document this class and move onto the first StorageModel.
I've attached the files. Anybody wanna write the Doc comments for the DataStore class?
This is also important; although not really anything to with TDD!
Attachments
data_store.zip
(137.17 KiB) Downloaded 251 times
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 3:46 am
Cool!
I'd like to do the doc comments. Is there any official documentation on which style we use?
Edit: the one from phpdoc.org?
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 4:19 am
Something like
Code: Select all
<?php
/**
* DataStore package
*
* @package DataStore
*/
/**
* Class DataStore
*
* @package DataStore
*/
require_once dirname(__FILE__) . '/StorageModel.php';
class DataStore {
/**
* The storage model
* @access private
* @var object
*/
private $_storageModel;
/**
* The private store of data
* @access private
* @var array
*/
private $_store = array();
/**
* Constructor sets up {@link $_storageModel} and {@link $_store}
*/
public function __construct(StorageModel $storageModel) {
$this->_storageModel = $storageModel;
$this->_store = (array) $this->_storageModel->load();
}
/**
* Sets a value based on input $key and $value
* @param $key
* @param $value
*/
public function set($key, $value) {
$container =& $this->_getContainer($key, true);
$container[$this->_getEndKey($key)] = $value;
}
/**
* Gets a value based on input $key
* @param $key
* @return $value
*/
public function get($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
return $container[$this->_getEndKey($key)];
}
}
/**
* Returns true or false if input $key exists
* @param $key
* @return boolean
*/
public function has($key) {
$container =& $this->_getContainer($key);
return (is_array($container)
&& array_key_exists($this->_getEndKey($key), $container));
}
/**
* Removes a $value from the store based on input $key
* @param $key
*/
public function remove($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
unset($container[$this->_getEndKey($key)]);
}
}
/**
* Save the current state of the store
* @param $storageModel
* @return boolean
*/
public function save(StorageModel $storageModel = null) {
if (!isset($storageModel)) {
$storageModel = $this->_storageModel;
}
return $storageModel->save($this->_store);
}
// -- Private methods
/**
* @access private
* @param $key
* @return ?
*/
private function _getEndKey($key) {
$keys = explode('/', $key);
return array_pop($keys);
}
/**
* @access private
* @param $key
* @param $creatIfNotFound
* @return $container
*/
private function &_getContainer($key, $createIfNotFound = false) {
$container =& $this->_store;
foreach (array_slice(explode('/', $key), 0, -1) as $nextKey) {
if (!array_key_exists($nextKey, $container)) {
if (!$createIfNotFound) {
return false;
}
$container[$nextKey] = array();
}
$container =& $container[$nextKey];
}
return $container;
}
}
I quickly put this together, there's probably errors in it. I haven't finished the - may I say not so well documented - documentation on phpdocs yet
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098 Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia
Post
by Chris Corbyn » Mon Apr 28, 2008 4:34 am
Very good
I'd throw the types in there too:
i.e.
@param string $key
or
@param StorageModel $storageModel
I don't believe {@link} will work for things that are declared private too so I'd probably not mention the private members in the documentation for the constructor
I'm a picky bugger eh? Nah, but if you're reading an API you don't really want to know about private members. I'd change the constructor comment to:
Code: Select all
/**
* Constructor takes a {@link StorageModel} for reading and writing.
* @param StorageModel $storageModel
*/
public function __construct(StorageModel $storageModel) {
$this->_storageModel = $storageModel;
$this->_store = (array) $this->_storageModel->load();
}
_getEndKey() always returns a string (the segment after the last "/").
&_getContainer() always returns an array
get() returns mixed
Okie doke. Where do we want to go from here?
XmlStorageModel, YamlStorageModel or ArrayStorageModel?
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 5:05 am
What would be the easiest step up? ArrayStorageModel? We basically already have that isn't it?
matthijs
DevNet Master
Posts: 3360 Joined: Thu Oct 06, 2005 3:57 pm
Post
by matthijs » Mon Apr 28, 2008 5:09 am
Here's the file after suggested changes
Code: Select all
<?php
/**
* DataStore package
*
* @package DataStore
*/
/**
* Class DataStore
*
* @package DataStore
*/
require_once dirname(__FILE__) . '/StorageModel.php';
class DataStore {
/**
* The storage model
* @access private
* @var object
*/
private $_storageModel;
/**
* The private store of data
* @access private
* @var array
*/
private $_store = array();
/**
* Constructor takes a {@link StorageModel} for reading and writing.
* @param StorageModel $storageModel
*/
public function __construct(StorageModel $storageModel) {
$this->_storageModel = $storageModel;
$this->_store = (array) $this->_storageModel->load();
}
/**
* Sets a value based on input $key and $value
* @param string $key
* @param string $value
*/
public function set($key, $value) {
$container =& $this->_getContainer($key, true);
$container[$this->_getEndKey($key)] = $value;
}
/**
* Gets a value based on input $key
* @param string $key
* @return mixed $value
*/
public function get($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
return $container[$this->_getEndKey($key)];
}
}
/**
* Returns true or false if input $key exists
* @param string $key
* @return boolean
*/
public function has($key) {
$container =& $this->_getContainer($key);
return (is_array($container)
&& array_key_exists($this->_getEndKey($key), $container));
}
/**
* Removes a $value from the store based on input $key
* @param string $key
*/
public function remove($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
unset($container[$this->_getEndKey($key)]);
}
}
/**
* Save the current state of the store
* @param StorageModel $storageModel
* @return boolean
*/
public function save(StorageModel $storageModel = null) {
if (!isset($storageModel)) {
$storageModel = $this->_storageModel;
}
return $storageModel->save($this->_store);
}
// -- Private methods
/**
* @access private
* @param string $key
* @return string
*/
private function _getEndKey($key) {
$keys = explode('/', $key);
return array_pop($keys);
}
/**
* @access private
* @param $key
* @param $creatIfNotFound
* @return array
*/
private function &_getContainer($key, $createIfNotFound = false) {
$container =& $this->_store;
foreach (array_slice(explode('/', $key), 0, -1) as $nextKey) {
if (!array_key_exists($nextKey, $container)) {
if (!$createIfNotFound) {
return false;
}
$container[$nextKey] = array();
}
$container =& $container[$nextKey];
}
return $container;
}
}