Page 3 of 15

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 2:52 am
by arjan.top
Well, that's sort of the point. It's test-driven so the failing test is our specification for what we have to code. If we're going down the DataStore as a simple container delegating to a persistence layer then what matthijs posted is right.
Yes it fails, but you have to implement two things to pass the test, as I understand TDD you should implement one thing at a time :?

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 3:02 am
by matthijs
Chris Corbyn wrote:We are missing something though matthijs. We never discussed instantiating the DataStore without a constructor. We have a dependency on StorageModel right? (Sorry, I keep changing its name... can we pick one? :P).
That's true.

Let's pick Datastore as the name for the general datastore class.

Then lets pick, ... StorageModel for the specific storage model :)

So we get

Code: Select all

 
interface StorageModel {};
class XMLModel implements StorageModel {}
class Datastore {}
 
$data = new DataStore(new XMLModel);
 
That's ok? Please correct me!

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 3:21 am
by arjan.top
I think it's ok ...

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 4:35 am
by Chris Corbyn
arjan.top wrote:Yes it fails, but you have to implement two things to pass the test, as I understand TDD you should implement one thing at a time :?
We're getting to that. You always write a test first which expresses your requirements. Then you implement the code. Even in small steps, doing it back-to-front would not be TDD ;) Failing test first. The failing test tells you what to do.

StorageModel it is! But let's not jump the gun and create XmlModel just yet ;) We don't want to have any dependency on a *specific* model, so all we need right now is the interface.

Here's something to think about. If we're not going to implement StorageModel just yet, but we need it for our tests, what do we do? :)

PS: The method name "testAddSomethingToDataStore" -- can we rename it to anything clearer? What are *actually* testing in that method? :)

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 4:38 am
by Chris Corbyn
arjan.top wrote:Yes it fails, but you have to implement two things to pass the test, as I understand TDD you should implement one thing at a time :?
Sorry, I see what you're saying. You're right, but set/get are a bit of a special case since they put the SUT (the system under test) in a different state. You can't test a get() before you've called set(). Some may argue it's wrong but I'm happy to write a test of this nature. If we all think we should test set() and then go on an test get() then we can do that. It's just that set() doesn't have any expected behaviour without get() being called :)

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 4:44 am
by arjan.top
Chris Corbyn wrote:
arjan.top wrote:Yes it fails, but you have to implement two things to pass the test, as I understand TDD you should implement one thing at a time :?
Sorry, I see what you're saying. You're right, but set/get are a bit of a special case since they put the SUT (the system under test) in a different state. You can't test a get() before you've called set(). Some may argue it's wrong but I'm happy to write a test of this nature. If we all think we should test set() and then go on an test get() then we can do that. It's just that set() doesn't have any expected behaviour without get() being called :)
oops, yes did not think of that hehe :)

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 4:47 am
by arjan.top
Here's something to think about. If we're not going to implement StorageModel just yet, but we need it for our tests, what do we do? :)
We mock it?

So we start with DataStorage?
PS: The method name "testAddSomethingToDataStore" -- can we rename it to anything clearer? What are *actually* testing in that method? :)
testAddFooWithValueBar?

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 4:57 am
by Chris Corbyn
arjan.top wrote:
Here's something to think about. If we're not going to implement StorageModel just yet, but we need it for our tests, what do we do?
We mock it?
Correct! :D
arjan.top wrote:So we start with DataStorage?
Yeah, I think we've already started testing DataStore.
arjan.top wrote:
PS: The method name "testAddSomethingToDataStore" -- can we rename it to anything clearer? What are *actually* testing in that method? :)
testAddFooWithValueBar?
Not really what I was thinking off. Foo and Bar cause confusion in a method name. I was thinking of:

testSetAndGetSingleValue()

?

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:02 am
by arjan.top
So we will use simpletest mock or yaymock?
Not really what I was thinking off. Foo and Bar cause confusion in a method name. I was thinking of:
testSetAndGetSingleValue()?
ok I just took a shot :)

EDIT:
we should test DataStore constructor first?

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:10 am
by Chris Corbyn
arjan.top wrote:So we will use simpletest mock or yaymock?
SimpleTest. I'm not doing this as a self-promotion :) We can look at Yay! Mock if we want to?
arjan.top wrote:ok I just took a shot :)
Good :D
arjan.top wrote:EDIT:
we should test DataStore constructor first?
Not really anything to test. Instantiating an object is too trivial. If the contructor has parameters which change the object's behaviour then yes, we'd test that those parameters do affect the behaviour. But in the case, we don't have anything to test just yet (do we?).

Here's how my test code looks now (still failing... just improved slightly). There's one tiny little thing I want to bring up before we implement any code (setting up the test).

My code so far:

classes/StorageModel.php

Code: Select all

<?php
 
interface StorageModel {
}
classes/DataStore.php

Code: Select all

<?php
 
require_once dirname(__FILE__) . '/StorageModel.php';
 
class DataStore {
 
  public function __construct(StorageModel $storageModel) {
  }
  
  public function set($key, $value) {
  }
  
  public function get($key) {
  }
 
}
tests/unit/DataStoreTest.php

Code: Select all

<?php
 
require_once dirname(__FILE__) . '/../configure.php';
require_once CLASS_BASE . '/DataStore.php';
require_once CLASS_BASE . '/StorageModel.php';
 
Mock::generate('StorageModel', 'MockStorageModel');
 
class DataStoreTest extends UnitTestCase {
  
  public function testSetAndGetSingleValue() {
    $data = new DataStore(new MockStorageModel());
    $data->set('foo', 'bar');
    $this->assertEqual($data->get('foo'), 'bar');
  }
  
}
I also updated the configure.php to define a class path to save a bit of typing:

tests/configure.php

Code: Select all

<?php
 
define('TEST_BASE', dirname(__FILE__));
define('SIMPLETEST_BASE', TEST_BASE . '/lib/simpletest');
define('CLASS_BASE', TEST_BASE . '/../classes');
require_once SIMPLETEST_BASE . '/unit_tester.php';
require_once SIMPLETEST_BASE . '/autorun.php';
Code is attached.

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:16 am
by matthijs
arjan.top wrote:So we will use simpletest mock or yaymock?
EDIT:
we should test DataStore constructor first?
Why should we test the constructor first? Shouldn't we just start to describe in Plain English what we expect our Datastore to do? And translate that in an expected behavior (test). Maybe the constructor is part of that, haven't thought about that. yet.

Funny how something that seems so simple at first sight can lead to so much discussion already. Maybe that just shows how important those first steps are. How you name stuff, etc. I do like how we are slowly progressing though.

[edit: good post Chris. You were faster then me]

[edit2:]
chris wrote:There's one tiny little thing I want to bring up before we implement any code (setting up the test).
So what was it that you wanted to bring up?

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:24 am
by arjan.top
Construstor in DataStore would load values from StorageModel

Code: Select all

 
<?php

interface StorageModel {
public function load();
 
public function save();
}
 

Code: Select all

 
<?php
 
require_once dirname(__FILE__) . '/StorageModel.php';
 
class DataStore {
 
  private $values;
 
  public function __construct(StorageModel $storageModel) {
  }
 
  public function set($key, $value) {
  }
 
  public function get($key) {
  }
 
}
 
The problem is that I am still not shure how are we going to implement it :)

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:28 am
by matthijs
arjan.top wrote:The problem is that I am still not shure how are we going to implement it :)
I think that's the whole point. We don't need to know how to implement it. Yet. First write down what we want. write tests for that. watch them fail and then write just enough code to pass. Doesn't matter if it's butt ugly code.

Once we have good test coverage, we can refactor everything behind the scenes. if needed.

(edit: sounds like I know what I'm talking about but no worries I'm just beginning with this as well ..)

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:35 am
by arjan.top
ok not how are we going to implement it but what the interface and responsibility of StorageModel will be ...

Re: TDD workshop. Anybody welcome; Data storage abstraction.

Posted: Tue Apr 22, 2008 5:35 am
by Chris Corbyn
~arjan.top, I think we'll look at that later (for the exact reason you said).

The thing I wanted to bring up was just about test fragility. The more tightly you couple your test code to your SUT, the more work you have to do if you do some major refactoring such as changing the name of an interface or the name of a class.

When you can get away with it, create an instance of the SUT in the setUp() method. Anything it depends on will need to be created here. This means you're only instantiating it in one place, so if we do change it's name we only need to change it one place. Test code is one place where you do often have to think ahead; but if your read the book linked in my signature it's not all bad if you don't get it right first time :)

Sometimes it's not possible to create the SUT in the setUp() method. Such is the case if you want to instantiate it with different constructor parameters for each test method. In this case, you're better off writing a factory method for it inside your test case (i.e. getDataStore($args)).

So I'd set our test up like this:

Code: Select all

<?php
 
require_once dirname(__FILE__) . '/../configure.php';
require_once CLASS_BASE . '/DataStore.php';
require_once CLASS_BASE . '/StorageModel.php';
 
Mock::generate('StorageModel', 'MockStorageModel');
 
class DataStoreTest extends UnitTestCase {
 
  private $_dataStore;
  private $_storageModel;
  
  public function setUp() {
    $this->_storageModel = new MockStorageModel();
    $this->_dataStore = new DataStore($this->_storageModel);
  }
  
  public function testSetAndGetSingleValue() {
    $this->_dataStore->set('foo', 'bar');
    $this->assertEqual($this->_dataStore->get('foo'), 'bar');
  }
  
}
I won't attach the code, since that class is all I've changed. I'm just running out for groceries, feel free to make the test pass without pulling out hairs and thinking further ahead than this test. At the same time, use your common sense and don't so something silly like "function get($key) { return 'bar'; }" :P