Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Thu Apr 24, 2008 6:44 pm
Great
Anybody care to write the test for a TRUE return value on successful save()? 
A community of PHP developers offering assistance, advice, discussion, and friendship.
http://forums.devnetwork.net/
Code: Select all
public function save() {
$this->_storageModel->save();
}
arjan.top wrote:DataStoreTest.phpCode: Select all
// ... snip ... public function testSaveDelegatesToStorageModel() { $this->_storageModel->expectOnce('save'); $this->_dataStore->save(); } // ... snip ...Code: Select all
<?php require_once dirname(__FILE__) . '/StorageModel.php'; class DataStore { private $_store = array(); private $_storageModel; public function __construct(StorageModel $storageModel) { $this->_storageModel = $storageModel; } public function set($key, $value) { $this->_store[$key] = $value; } public function get($key) { return $this->_store[$key]; } public function save() { $this->_storageModel->save($this->_store); } }
Code: Select all
// class DataStoreTest:
public function testSaveValueReturnsTrueOnSuccess(){
$this->_storageModel->setReturnValue('save', true);
//$this->_dataStore->set('foo', 'bar');
$rs = $this->_dataStore->save();
$this->assertTrue($rs);
}
// class dataStore:
public function save() {
$rs = $this->_storageModel->save($this->_store);
return $rs;
}
It won't pass under E_ALLgeorgeoc wrote:@arjan.top - You've added a test to check that save() delegates to the storage model, and set up the expectation in the mock. That's great. But are you sure your implementation is only enough code to make the test pass, and no more?
I can make the test pass with this alone:Code: Select all
public function save() { $this->_storageModel->save(); }
Looks good, but I would do replace temp with querymatthijs wrote:What aboutit passes but it feels kind of weird. Have to read the mock docs some better.Code: Select all
// class DataStoreTest: public function testSaveValueReturnsTrueOnSuccess(){ $this->_storageModel->setReturnValue('save', true); //$this->_dataStore->set('foo', 'bar'); $rs = $this->_dataStore->save(); $this->assertTrue($rs); } // class dataStore: public function save() { $rs = $this->_storageModel->save($this->_store); return $rs; }
Code: Select all
// class DataStoreTest:
public function testSaveValueReturnsTrueOnSuccess(){
$this->_storageModel->setReturnValue('save', true);
//$this->_dataStore->set('foo', 'bar');
$this->assertTrue($this->_dataStore->save(););
}
// class dataStore:
public function save() {
return $this->_storageModel->save($this->_store);
}
That's true, and great use of terminologyarjan.top wrote:Looks good, but I would do replace temp with query
Code: Select all
public function testSaveValueReturnsFalseOnFail(){
$this->_storageModel->setReturnValue('save', false);
$this->assertFalse($this->_dataStore->save());
}
You are, but that's correctmatthijs wrote:It still feels like I'm cheating ..Code: Select all
public function testSaveValueReturnsFalseOnFail(){ $this->_storageModel->setReturnValue('save', false); $this->assertFalse($this->_dataStore->save()); }
Code: Select all
// class DataStoreTest
public function testRemoveValueFromStoreReturnsTrueOnSuccess(){
$this->_dataStore->set('foo', 'bar');
$this->_storageModel->setReturnValue('remove', true);
$this->assertTrue($this->_dataStore->remove('foo'));
}
public function testRemoveValueFromStoreReturnsFalseOnFail(){
$this->_dataStore->set('foo', 'bar');
$this->_storageModel->setReturnValue('remove', false);
$this->assertFalse($this->_dataStore->remove('foo'));
}
// class DataStore
public function remove($key){
return $this->_storageModel->remove($this->_store[$key]);
}
// StorageModel
interface StorageModel {
public function save($data);
public function remove($data);
public function load();
}
Code: Select all
...
public function testDataStoreKnowsItHasSingleValue() {
$this->_dataStore->set('foo', 'bar');
$this->assertTrue($this->_dataStore->has('foo'));
}
public function testDataStoreKnowsWhenItDoesntHaveAValue() {
$this->assertFalse($this->_dataStore->has('zip'));
}Code: Select all
// class DataStore
public function has($key){
return array_key_exists($key, $this->_store);
}
Thinking too far aheadmatthijs wrote:But how are we supposed to remove something if the Datastore class has no method to remove?![]()
ExcellentAnyway, I wrote this to make your test passCode: Select all
// class DataStore public function has($key){ return array_key_exists($key, $this->_store); }
First I need to know: are we removing from the dataStore or from the StorageModel?So now it should be easy to test remove() by simply setting a value, calling remove() and then asserting that has() returns false. This is another one of those stateful tests where you're calling a sequence of methods to put the SUT into a wanted state.
Code: Select all
// test
public function testRemoveValueRemovesValue(){
$this->_dataStore->set('foo', 'bar');
$this->assertTrue($this->_dataStore->has('foo'));
$this->_dataStore->remove('foo');
$this->assertFalse($this->_dataStore->has('foo'));
}
// datastore
public function remove($key){
unset($this->_store[$key]);
}
Removing from the DataStore. StorageModel is just the persistence layer; it won't be removed from that persistent storage until save() is called.matthijs wrote:First I need to know: are we removing from the dataStore or from the StorageModel?So now it should be easy to test remove() by simply setting a value, calling remove() and then asserting that has() returns false. This is another one of those stateful tests where you're calling a sequence of methods to put the SUT into a wanted state.
If it is the first, this makes the test passCode: Select all
// test public function testRemoveValueRemovesValue(){ $this->_dataStore->set('foo', 'bar'); $this->assertTrue($this->_dataStore->has('foo')); $this->_dataStore->remove('foo'); $this->assertFalse($this->_dataStore->has('foo')); } // datastore public function remove($key){ unset($this->_store[$key]); }
Ok, now I understand what the purpose is.Removing from the DataStore. StorageModel is just the persistence layer; it won't be removed from that persistent storage until save() is called.
Good to know. These little things are good to know, because the reason why I stopped testing some time ago after a few days trying was that my tests became one big, horrible, complicated mess of dependencies. At some point I didn't have a clue anymore what I was testing. If anything. That's what happens when you try to run before you can crawl..This whole "one assertion per test method" idea some people have is a good rule-of-thumb, but not a hard fact.
Code: Select all
public function testInstantiateDatastoreLoadsStorageModel(){
$this->_storageModel->expectOnce('load');
}