SitePoint Sponsor

User Tag List

Results 1 to 10 of 10

Thread: TDD Experiment

  1. #1
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    TDD Experiment

    hi,

    reading this forum for a long time (and some rare times contributing as well) i noticed that many interesting threads stuck in endless diskussions about how this and that pattern is meant or what patterns must be used. there was no real consens on how things have to work or how the code language should look like.

    thats where this thread comes into play. it serves me as kind of a community blog where i will post specifications, tests, implementations, refactorings, etc for a orm layer the tdd way. everyone is welcome to comment or much better to contribute to the thread. i am in no way sure where this will end but it could be fun and teaching.

    cheers
    Christian

  2. #2
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Specifications

    i think a good start would be some rather blury specs which then get refined and extended or completly removed
    so here we go:

    - requirements for a object to be persist should be as low as possible (none would be nice)
    - persist related objects (Collections, Sets)
    - support inheritance
    - provide flexpoints for adding things like versioning
    - provide flexpoints for working directly with the underlying database. this will mainly help generating reports and stuff
    - support different databases. xml could be useful but it has no priority atm
    - support different ways of describing the metadata. xml and inline comments like hybernate would be a start

    i am going to sleep now as ist late here in germany. lets see how i feel about the specs tomorow morning

    cheers
    Christian

  3. #3
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You say TDD and then get right off writing specs instead of tests

    Write some tests when you wake up
    Hello World

  4. #4
    SitePoint Addict
    Join Date
    Apr 2004
    Location
    Melbourne
    Posts
    362
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    How can you write a test if you have no idea what you're going to be testing?

  5. #5
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by DougBTX
    You say TDD and then get right off writing specs instead of tests

    Write some tests when you wake up
    maybe i should call them requirements?
    i thought tests are written to proove that our code satisfy the customers expectations / requirements ?

  6. #6
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sike
    maybe i should call them requirements?
    i thought tests are written to proove that our code satisfy the customers expectations / requirements ?
    This is basically in agreement with Kent Becks Test Driven Development by Example. There he writes down a feature list, pick the top priority off the list and writes a test for it. As he thinks of new features to add during the writing of the tests or the code, he adds them to the feature list to be ticked off in turn.
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  7. #7
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sweatje
    This is basically in agreement with Kent Becks Test Driven Development by Example. There he writes down a feature list, pick the top priority off the list and writes a test for it. As he thinks of new features to add during the writing of the tests or the code, he adds them to the feature list to be ticked off in turn.
    good to see that i am on the right track (;

  8. #8
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    alright lets pick the first requirement we will write a test for :
    Quote Originally Posted by sike
    - requirements for a object to be persist should be as low as possible (none would be nice)
    so this is a bit vague to begin writing tests. lets see how i could refine this :

    - support object properties
    - support set/get methods

    that should be enough to write my first test. to avoid naming conflicts i'll prefix all classes with OPL (Object Persistence Libary).
    i think i will name the class ObjectAdapter. Maybe ObjectReader is a better name but i will stick with Adapter for now.

    UnitTest
    PHP Code:
        /**
        * Test Objects
        */
        
    class OPLObjectAdapter_PropertyObject
        
    {
          public 
    $name '';
          public 
    $age 0;
        }

        
    /**
        */
        
    class TestOfOPLObjectAdapter extends UnitTestCase
        
    {
          function 
    __construct()
          {
            
    $this->UnitTestCase();
          }


          function 
    testReadingAProperty()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertEqual($adapter->getField('name'), $object->name);
          }
        } 
    Skelton ObjectAdapter
    PHP Code:
      /**
      * @author   Christian
      * @version  $Revision$
      * @package  OPL
      */
      
    class OPLObjectAdapter
      
    {
        protected 
    $object false;

        public function 
    __construct(&$object)
        {
          
    $this->object $object;
        }

        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          return 
    null;
        }
      } 
    ok thats it for now. will make the test pass in a couple of hours.

    cheers
    Christian
    Attached Files Attached Files

  9. #9
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    time to make the first test pass

    PHP Code:

        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          if (isset(
    $this->object->$name))
          {
            return 
    $this->object->$name;
          }
          return 
    null;
        } 
    fine, the test passes. but what about case sensitivity and non existing properties?
    i think i will rename the first test to testReadingExistingProperty and add two new ones
    PHP Code:

        
    class TestOfOPLObjectAdapter extends UnitTestCase
        
    {
          function 
    __construct()
          {
            
    $this->UnitTestCase();
          }


          function 
    testReadingExistingProperty()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;        

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertEqual($adapter->getField('name'), $object->name);
            
    $this->assertEqual($adapter->getField('age'), $object->age);        
          }

          function 
    testReadingNotExistingProperty()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;        

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertNull($adapter->getField('gender'));
          }
          
          function 
    testReadingPropertyCaseSensivity()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertNull($adapter->getField('Name'));
            
    $this->assertEqual($adapter->getField('name'), $object->name);        
          }
        } 
    looks good - everything green (man i like that color (; )
    next on my list is reading the typical getter / setter pair. lets write the test for that :

    PHP Code:

          
    function testReadingExistingMethod()
          {
            
    $object = new OPLObjectAdapter_MethodObject();
            
    $object->setName('Kent Beck');
            
    $object->setAge(83);

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertEqual($adapter->getField('name'), $object->getName());
            
    $this->assertEqual($adapter->getField('age'), $object->getAge());
          } 
    and here is the corresponding test object ;
    PHP Code:

        
    class OPLObjectAdapter_MethodObject
        
    {
          protected 
    $name '';
          protected 
    $age 0;

          public function 
    getName()
          {
            return 
    $this->name;
          }

          public function 
    setName($value)
          {
            return 
    $this->name $value;
          }

          public function 
    getAge()
          {
            return 
    $this->age;
          }

          public function 
    setAge($value)
          {
            return 
    $this->age $value;
          }
        } 
    yeah right - test fails
    so how i am going to implement this? the first thing coming to my mind is a simple try and error strategy. first i test for getters / setters and if that fails i test for the coresponding property.
    here is my first try
    PHP Code:

        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          
    //try getter
          
    $methods get_class_methods(get_class($this->object));
          
    $getter 'get'.ucfirst($name);
          if (
    in_array($getter$methods))
          {
            return 
    $this->object->$getter();
          }
          
    //try property
          
    if (isset($this->object->$name))
          {
            return 
    $this->object->$name;
          }
          return 
    null;
        } 
    great it works. lets add the other tests for the getters / setters

    PHP Code:

          
    function testReadingNotExistingMethod()
          {
            
    $object = new OPLObjectAdapter_MethodObject();
            
    $object->setName('Kent Beck');
            
    $object->setAge(83);

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertNull($adapter->getField('gender'));
          }


          function 
    testReadingMethodCaseSensivity()
          {
            
    $object = new OPLObjectAdapter_MethodObject();
            
    $object->setName('Kent Beck');
            
    $object->setAge(83);

            
    $adapter = new OPLObjectAdapter($object);

            
    $this->assertNull($adapter->getField('Name'));
            
    $this->assertEqual($adapter->getField('name'), $object->getName());

            
    $this->assertNull($adapter->getField('Age'));
            
    $this->assertEqual($adapter->getField('age'), $object->getAge());
          } 
    testReadingMethodCaseSensivity fails. i expected this to happen because of the camelcase methodnames [$getter = 'get'.ucfirst($name);]. to maintain consistency i should really make them behave exactly the same. but should i remove the case sensitivity from the property part or i should i add it to the methods part? think i will go with the former and remove case sensitivity altogether.
    PHP Code:

        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          
    //try getter
          
    $method 'get'.strtolower($name);
          
    $methods = array();
          
    $methodsRaw get_class_methods(get_class($this->object));
          foreach(
    $methodsRaw as $item)
          {
            
    $methods[strtolower($item)] = true;
          }
          if (isset(
    $methods[$method]))
          {
            return 
    $this->object->$method();
          }
          
    //try property
          
    $prop strtolower($name);
          
    $props get_object_vars($this->object);
          
    array_change_key_case($propsCASE_LOWER);
          if (isset(
    $props[$prop]))
          {
            return 
    $props[$prop];
          }
          return 
    null;
        } 
    feels good. all tests pass. but i have a feeling that reading the two arrays and lowercasing them on each request for a field will cost a bit too much performance. but wait - premature optimisation and the devil and so on.
    think i will refactor this tomorrow and leave it for now


    cheers
    Christian
    Attached Files Attached Files

  10. #10
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    after reviewing the code it became obvious to me that the Adapter should allow me to change the underlying object via a setObject method. while wiring the test i thought it would be better to remove the object from the constructor. so first i will refactor the tests

    Tests
    PHP Code:
        /**
        */
        
    class TestOfOPLObjectAdapter extends UnitTestCase
        
    {
          function 
    __construct()
          {
            
    $this->UnitTestCase();
          }

          function 
    testReadPropertyWithoutObject()
          {
            
    $adapter = new OPLObjectAdapter();

            
    $this->assertNull($adapter->getField('name'));
          }


          function 
    testSetObject()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;

            
    $adapter = new OPLObjectAdapter();

            
    $this->assertTrue($adapter->setObject($object));
          }


          function 
    testReadingExistingProperty()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;

            
    $adapter = new OPLObjectAdapter();
            
    $adapter->setObject($object);

            
    $this->assertEqual($adapter->getField('name'), $object->name);
            
    $this->assertEqual($adapter->getField('age'), $object->age);
          }


          function 
    testReadingNotExistingProperty()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;

            
    $adapter = new OPLObjectAdapter();
            
    $adapter->setObject($object);

            
    $this->assertNull($adapter->getField('gender'));
          }


          function 
    testReadingPropertyCaseSensivity()
          {
            
    $object = new OPLObjectAdapter_PropertyObject();
            
    $object->name 'Kent Beck';
            
    $object->age 38;

            
    $adapter = new OPLObjectAdapter();
            
    $adapter->setObject($object);

            
    $this->assertEqual($adapter->getField('NamE'), $object->name);
            
    $this->assertEqual($adapter->getField('name'), $object->name);

            
    $this->assertEqual($adapter->getField('aGe'), $object->age);
            
    $this->assertEqual($adapter->getField('age'), $object->age);
          }


          function 
    testReadingExistingMethod()
          {
            
    $object = new OPLObjectAdapter_MethodObject();
            
    $object->setName('Kent Beck');
            
    $object->setAge(83);

            
    $adapter = new OPLObjectAdapter();
            
    $adapter->setObject($object);

            
    $this->assertEqual($adapter->getField('name'), $object->getName());
            
    $this->assertEqual($adapter->getField('age'), $object->getAge());
          }


          function 
    testReadingNotExistingMethod()
          {
            
    $object = new OPLObjectAdapter_MethodObject();
            
    $object->setName('Kent Beck');
            
    $object->setAge(83);

            
    $adapter = new OPLObjectAdapter();
            
    $adapter->setObject($object);

            
    $this->assertNull($adapter->getField('gender'));
          }


          function 
    testReadingMethodCaseSensivity()
          {
            
    $object = new OPLObjectAdapter_MethodObject();
            
    $object->setName('Kent Beck');
            
    $object->setAge(83);

            
    $adapter = new OPLObjectAdapter();
            
    $adapter->setObject($object);

            
    $this->assertEqual($adapter->getField('NamE'), $object->getName());
            
    $this->assertEqual($adapter->getField('name'), $object->getName());

            
    $this->assertEqual($adapter->getField('aGe'), $object->getAge());
            
    $this->assertEqual($adapter->getField('age'), $object->getAge());
          }
        } 
    ObjectAdapter
    PHP Code:
      /**
      * @author   Christian
      * @version  $Revision$
      * @package  OPL
      */
      
    class OPLObjectAdapter
      
    {
        protected 
    $object false;

        
    /*
        * sets the internal object
        */
        
    public function setObject(&$object)
        {
          return 
    $this->object $object;
        }

        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          if (
    $this->object != false)
          {
            
    //try getter
            
    $method 'get'.strtolower($name);
            
    $methods = array();
            
    $methodsRaw get_class_methods(get_class($this->object));
            foreach(
    $methodsRaw as $item)
            {
              
    $methods[strtolower($item)] = true;
            }
            if (isset(
    $methods[$method]))
            {
              return 
    $this->object->$method();
            }
            
    //try property
            
    $prop strtolower($name);
            
    $props get_object_vars($this->object);
            
    array_change_key_case($propsCASE_LOWER);
            if (isset(
    $props[$prop]))
            {
              return 
    $props[$prop];
            }
          }
          return 
    null;
        }
      } 
    all tests pass in reallity it took me a couple of minutes to change the getField method and remove the contructor as it was a empty method.

    now the getField method looks a bit untidy to me. lets see how we can refactor this :
    ObjectAdapter
    PHP Code:
      /**
      * @author   Christian
      * @version  $Revision$
      * @package  OPL
      */
      
    class OPLObjectAdapter
      
    {
        protected 
    $object false;

        
    /*
        * returns null or the field value
        */
        
    protected function readProperty($name)
        {
          if (
    $this->object != false)
          {
            
    $prop strtolower($name);
            
    $props get_object_vars($this->object);
            
    array_change_key_case($propsCASE_LOWER);
            if (isset(
    $props[$prop]))
            {
              return 
    $props[$prop];
            }
          }
          return 
    null;
        }
        
        
    /*
        * sets the internal object
        */
        
    public function setObject(&$object)
        {
          return 
    $this->object $object;
        }

        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          if (
    $this->object != false)
          {
            
    //try getter
            
    $method 'get'.strtolower($name);
            
    $methods = array();
            
    $methodsRaw get_class_methods(get_class($this->object));
            foreach(
    $methodsRaw as $item)
            {
              
    $methods[strtolower($item)] = true;
            }
            if (isset(
    $methods[$method]))
            {
              return 
    $this->object->$method();
            }
        return 
    $this->readProperty($name);
          }
          return 
    null;
        }
      } 
    test still pass. lets go a step further.
    PHP Code:
      /**
      * @author   Christian
      * @version  $Revision$
      * @package  OPL
      */
      
    class OPLObjectAdapter
      
    {
        protected 
    $object false;

        
    /*
        * returns null or the property value
        */
        
    protected function readProperty($name)
        {
          if (
    $this->object != false)
          {
            
    $prop strtolower($name);
            
    $props get_object_vars($this->object);
            
    array_change_key_case($propsCASE_LOWER);
            if (isset(
    $props[$prop]))
            {
              return 
    $props[$prop];
            }
          }
          return 
    null;
        }


        
    /*
        * returns null or the property value
        */
        
    protected function readGetter($name)
        {
          if (
    $this->object != false)
          {
            
    $method 'get'.strtolower($name);
            
    $methods = array();
            
    $methodsRaw get_class_methods(get_class($this->object));
            foreach(
    $methodsRaw as $item)
            {
              
    $methods[strtolower($item)] = true;
            }
            if (isset(
    $methods[$method]))
            {
              return 
    $this->object->$method();
            }
          }
          return 
    null;
        }


        
    /*
        * sets the internal object
        */
        
    public function setObject(&$object)
        {
          return 
    $this->object $object;
        }


        
    /*
        * returns null or the field value
        */
        
    public function getField($name)
        {
          if (
    $this->object != false)
          {
            
    $result $this->readGetter($name);
            if (!
    is_null($result))
            {
              return 
    $result;
            }
            return 
    $this->readProperty($name);
          }
          return 
    null;
        }
      } 
    tests pass but i think i could do a little bit more. as i expect the class methods stay the same through a request i will move the retrival of them into the setObject method.
    PHP Code:
        /*
        * returns null or the property value
        */
        
    protected function readGetter($name)
        {
          if (
    $this->object != false)
          {
            
    $method 'get'.strtolower($name);
            if (isset(
    $this->methods[$method]))
            {
              return 
    $this->object->$method();
            }
          }
          return 
    null;
        }


        
    /*
        * sets the internal object
        */
        
    public function setObject(&$object)
        {
          
    $this->object $object;
          
    $this->methods = array();
          
    $methodsRaw get_class_methods(get_class($this->object));
          foreach(
    $methodsRaw as $item)
          {
            
    $this->methods[strtolower($item)] = true;
          }
          return 
    true;
        } 
    still green

    cheers
    Christian


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •