TDD workshop. Anybody welcome; Data storage abstraction.

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

User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post 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 :?
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

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

Post 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!
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post by arjan.top »

I think it's ok ...
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

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

Post 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? :)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

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

Post 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 :)
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post 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 :)
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post 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?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

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

Post 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()

?
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post 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?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

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

Post 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.
Attachments
data_store.zip
(135.83 KiB) Downloaded 200 times
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

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

Post 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?
Last edited by matthijs on Tue Apr 22, 2008 5:24 am, edited 1 time in total.
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post 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 :)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

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

Post 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 ..)
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

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

Post by arjan.top »

ok not how are we going to implement it but what the interface and responsibility of StorageModel will be ...
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

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

Post 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
Post Reply