SitePoint Sponsor |
|
User Tag List
Results 1 to 25 of 171
-
Feb 12, 2006, 09:41 #1
Feedback on current O/R Mapping Unittests
First of all, Marcus - thanks once more for SimpleTest
.
Second, here are my current testcases for my ORM-implementation:
Notice: In the testcases I use the syntax:
$this->worker->mapper("Type")->findByPk(1);
But it's equally good/shorter to use:
$this->worker->findTypeByPk(1);
It's just that I wrote the testcases before I added most of the syntacitals suger thingies.
PHP Code:<?php
require('Dev/PDOBench.php');
require('Dev/DevFuncs.php');
ra('../ORM/');
require('simpletest/unit_tester.php');
require('simpletest/reporter.php');
require('simpletest/reporter_showpasses.php');
// Host, Username, Password and Database for tests:
define('DBHOST','localhost');
define('DBUSER','dev');
define('DBPASS','devnull');
define('DBNAME','development');
/* NOTICE: These tests will drop/create the tables named
"Groups_test","Persons_test","Memeberships_test" and "Posts_test" on the
database the tests are pointed at - these tests should
NOT be run on anything else then a database used for
testing purposes. If you don't know what something of the above
means - don't run the tests
NOTICE: These test will currently only work on a mysql database.
To get the tests to run, set define RUN_TESTS to true
*/
define('RUN_TESTS',true);
if(RUN_TESTS === true){
class TestObjectRelationalMapping extends UnitTestCase{
function TestObjectRelationalMapping(){
$this->UnitTestCase("O/R Mapping test");
$this->pdoBench = new PDOBench("mysql:host=".DBHOST.";dbname=".DBNAME,DBUSER,DBPASS);
$this->worker = new UnitOfWork($this->pdoBench,'./Classes');
}
function setUp(){
$this->pdo = new PDO("mysql:host=".DBHOST.";dbname=".DBNAME,DBUSER,DBPASS);
$groups = "CREATE TABLE `Groups_test` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(25) NOT NULL,
`comment` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;";
$this->pdo->exec($groups);
$groupsData = "INSERT INTO `Groups_test` (`id`, `name`, `comment`)
VALUES (1, 'Admin', 'Administrators....'), (2, 'Users', 'Normal users...');";
$this->pdo->exec($groupsData);
$memberships = "CREATE TABLE `Memberships_test` (
`id` int(11) NOT NULL auto_increment,
`person` int(11) NOT NULL,
`group` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;";
$this->pdo->exec($memberships);
$persons = "CREATE TABLE `Persons_test` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`age` int(11) NOT NULL,
`email` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;";
$this->pdo->exec($persons);
$personsData = "INSERT INTO `Persons_test` (`id`, `name`, `age`, `email`) VALUES
(1, 'Fredrik', 20, 'thrthr@gmail.com'), (2, 'Madeleine', 19, 'unkown');";
$this->pdo->exec($personsData);
$posts = "CREATE TABLE `Posts_test` (
`id` int(11) NOT NULL auto_increment,
`author` int(11) default NULL,
`text` text NOT NULL,
`subject` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;";
$this->pdo->exec($posts);
$this->worker->unregisterAll();
}
function tearDown(){
$this->pdo->exec('DROP TABLE `Groups_test`, `Memberships_test`, `Persons_test`, `Posts_test`;');
}
function testFind(){
$result = $this->worker->mapper("Person")->find(array('age' => 20));
$this->assertEqual(1,count($result));
$this->assertIsA($result[0],'Person');
}
function testFindByPk(){
$person = $this->worker->mapper("Person")->findByPk(1);
$this->assertIsA($person,'Person');
}
function testFindBySql(){
$result = $this->worker->mapper("Person")->findBySql("select * from `Persons_test`");
$this->assertEqual(2,count($result));
$this->assertIsA($result[0],'Person');
$this->assertIsA($result[1],'Person');
$this->assertNotEqual($result[0]->id,$result[1]->id);
}
function testFindOne(){
$result = $this->worker->mapper("Person")->findFirst(array('age' => 20));
$this->assertIsA($result,'Person');
}
function testCreate(){
$person = $this->worker->create("Person");
$this->assertIsA($person,"Person");
$this->assertNull($person->id);
}
function testCreationsAreSaved(){
$person = $this->worker->create("Person");
$person->name = "Dummy";
$person->age = 1;
$person->email = "Unkown...";
$this->assertIsA($person,"Person");
$this->assertNull($person->id);
$this->worker->commit();
$this->assertEqual($person->id,3);
}
function testJoinedCreationsAreSaved(){
$person = $this->worker->create("Person");
$person->name = "Dummy";
$person->age = 2;
$person->email = "Unkown...";
$post1 = $person->posts->create();
$post2 = $person->posts->create();
$post3 = $person->posts->create();
$this->assertIsA($post1,"Post");
$this->assertIsA($post2,"Post");
$this->assertIsA($post3,"Post");
$post1->text = "I ";
$post2->text = "<3 ";
$post3->text = " you";
$this->worker->commit();
$this->assertNotNull($person->id);
$this->assertNotNull($post1->id);
$this->assertNotNull($post2->id);
$this->assertNotNull($post3->id);
}
function testRegisterDirty(){
$person = $this->worker->mapper("Person")->findByPk(1);
$person->name = "thr";
$this->worker->registerDirty($person);
$this->worker->commit();
$newWorker = new UnitOfWork($this->pdoBench,'./Classes');
$personCopy = $newWorker->mapper("Person")->findByPk(1);
$this->assertEqual($person->name,$personCopy->name);
}
function testRegisterDelete(){
$person = $this->worker->mapper("Person")->findByPk(1);
$this->worker->registerDelete($person);
$this->worker->commit();
$newWorker = new UnitOfWork($this->pdoBench,'./Classes');
$personCopy = $newWorker->mapper("Person")->findByPk(1);
$this->assertNull($personCopy);
}
function testLinkedCreationsAreSavedToDb(){
$person = $this->worker->create("Person");
$person->name = "Dummy";
$person->age = 2;
$person->email = "Unkown...";
$post1 = $person->posts->create();
$post2 = $person->posts->create();
$post3 = $person->posts->create();
$post1->text = "I ";
$post2->text = "<3 ";
$post3->text = " you";
$this->worker->commit();
$newWorker = new UnitOfWork($this->pdoBench,'./Classes');
$person = $newWorker->mapper("Person")->findByPk(3);
foreach($person->posts as $post){
$this->assertIsA($post,"Post");
}
}
function testCollectionCreationsAutomaticlyAssigned(){
$person = $this->worker->mapper("Person")->findByPk(1);
$group = $this->worker->mapper("Group")->findByPk(1);
$membership = $person->memberships->create();
$membership->group = $group;
$this->assertEqual($person->id,$membership->person->id);
$this->worker->Commit();
$newWorker = new UnitOfWork($this->pdoBench,'./Classes');
$membership = $newWorker->mapper("Membership")->findByPk(1);
$this->assertEqual($person->id,$membership->person->id);
$this->assertEqual($group->id,$membership->group->id);
}
function testThresholdAutoLoad(){
$person = $this->worker->mapper("Person")->findByPk(1);
for($i = 0; $i < 25;$i++){
$post = $person->posts->create();
$post->text = $i;
}
$this->worker->commit();
$newWorker = new UnitOfWork($this->pdoBench,'./Classes');
$person = $newWorker->mapper("Person")->findByPk(1);
$person->posts->setThreshold(5);
foreach($person->posts as $post){
$this->assertIsA($post,"Post");
}
}
// "aliasing" is actually a normal php reference
function testAliasing(){
$person = $this->worker->mapper("Person")->findByPk(1);
$person->mail = "this is a new email";
$this->worker->registerDirty($person);
$this->worker->commit();
// Person::$mail is a reference to Person::$email (sql field)
// That's defined in Person::__construct();
$this->assertEqual($person->mail,$person->email);
$newWorker = new UnitOfWork($this->pdoBench,'./Classes');
$person2 = $newWorker->mapper("Person")->findByPk(1);
$this->assertEqual($person->mail,$person2->email);
}
function testSyntaticalSugar(){
$person = $this->worker->findPersonByPk(1);
$this->assertIsA($person,"Person");
$personList = $this->worker->findPerson(array('name' => 'Fredrik'));
$this->assertEqual(1,count($personList));
$this->worker->deletePersonByPk(1);
}
function testDisplayPDO(){
$this->pdoBench->display();
}
}
$testORM = new TestObjectRelationalMapping;
$testORM->run(new ShowPasses());
}
?>
-
Feb 12, 2006, 09:46 #2
Oh, and here's the generated API documentation(phpDocumentor): http://mirkk.nu/Manual
-
Feb 12, 2006, 10:58 #3
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Sorry for going off topic, but instead of actually creating the database tables via PHP script, wouldn't it be more convienent, and more pratical just to import from an sql dump?
I know it's just a test, but all the same...
-
Feb 12, 2006, 11:07 #4
Originally Posted by Dr Livingston
-
Feb 12, 2006, 11:36 #5
Originally Posted by thr
-
Feb 12, 2006, 11:46 #6
Originally Posted by Luke Redpath
$sql = file_get_contents("sql.sql");
$this->pdo->exec($sql);
-
Feb 12, 2006, 13:24 #7
- Join Date
- Oct 2004
- Location
- Worcester
- Posts
- 138
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Is it possible for TearDown() to not actually run when a given test fails?
Clearly I'm missing something, but what's the problem with having the SQL that the test depends on being in the test itself?
-
Feb 12, 2006, 13:32 #8
Could we skip the whole "sql-in-tests-or-not"-debate? ;p
-
Feb 12, 2006, 13:48 #9
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I wonder. Why does the Memberships_test table have a column "id" ? person+group make a good primarykey candidate.
-
Feb 12, 2006, 14:00 #10
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
More nitpicking :
Would you be able to handle anything but a single integer primarykey properly ?
UnitOfWork seems to be the central element in the API. I find that a bit odd. Wouldn't it be more natural to have a Registry as the central component, and let the uow be a member of the registry ?
I don't see any tests for cyclic dependencies - Have you though of that issue ?
-
Feb 12, 2006, 14:12 #11
Originally Posted by kyberfabrikken
-
Feb 12, 2006, 14:14 #12
Originally Posted by thr
... well couldn't resist atleast one..
Code:function testMultipleTablesWithReferences() { $sql1 = 'CREATE TABLE parent(id INT PRIMARY KEY, name NVARCHAR(30)); CREATE TABLE child(id INT PRIMARY KEY, parent_id INT REFERENCES parent(id), name NVARCHAR(40))'; $sql2 = 'CREATE TABLE parent(id INT, name NVARCHAR(30), PRIMARY KEY(id)); CREATE TABLE child(id INT, parent_id INT, name NVARCHAR(40), PRIMARY KEY(id), FOREIGN KEY (parent_id) REFERENCES parent(id))'; $e = new SqlSchemaEqualExpectation($this->parser, $this->conicaliser, $sql1); $this->assert($e, $sql2); }
-
Feb 12, 2006, 18:35 #13
Originally Posted by thr
I was mostly making a point about treating your test code the same as you would any other.
-
Feb 12, 2006, 23:18 #14
Originally Posted by kyberfabrikken
Edit #2: Ok, so I've played around with implementing 100% joined/composite key - and yes it's more then possible to do it, my question is tho - is it realy needed? Yes I know it's a bit bloating of the normalization model to keep an int auto_inc pk in the Membership_tests table in my UnitTests above - but realy, is it THAT bad?
The reason I ask is because well, there's so many things that need to take the composite keys into consideration when doing it with just one pkfield is not just faster, but gives easier to read and maintain code - worth the tradeoff?
Originally Posted by kyberfabrikken
Edit #1: Gave it some more thought and played around with a small API - this seems more clearer, I'll post some testcode later today.
Originally Posted by kyberfabrikken
Originally Posted by Luke Redpath
Last edited by thr; Feb 13, 2006 at 00:53.
-
Feb 13, 2006, 02:26 #15
- Join Date
- Oct 2004
- Location
- Worcester
- Posts
- 138
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
To me a cyclic dependency is situations like:
- a person's parent is also their child.
- a person's grandparent is himself.
Though this issue may actually have another name!
-
Feb 13, 2006, 02:52 #16
Originally Posted by akrabat
-
Feb 13, 2006, 07:57 #17
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by thr
Originally Posted by thr
Originally Posted by thr
PHP Code:$person = $this->worker->create("Person");
$person->parent = $person;
$this->worker->commit();
-
Feb 13, 2006, 08:32 #18
Originally Posted by kyberfabrikken
Originally Posted by kyberfabrikken
-
Feb 15, 2006, 05:14 #19
Originally Posted by kyberfabrikken
I also did some reading in PoEAA on DataMappers/Unit of Work/Metadata Mapping and I found that fowler also uses the UoW as main object holding the mappers.
And I got support for cyclic dependecies, composite keys, 1 > 1, 1 < 1, 1 > * and * >< * with named intermediate(the intermediate table is an object), Value Objects. Edit #1: Hopefully I will get support for "CompositeMappers" in - which means you can aggregate two mappers for different types into one single mapper and do complex joins such as: SELECT Person.*, Group.* FROM Person, Group WHERE <Complex Condition> and get both the Person and Group object out with one query. Also support for autoloading in collections and recursion-depth.
Edit #2:I haven't tested on anything exception PostGresql and MySQL but it SHOULD run on all databases supported by PDO atm.
Edit #3: Currently the only thing required to use the implementation is for your domain objects to implement the interface "DomainObject", if you want more functionallity you can extend "GenericMapper" and specify relations, table, value objects and such. Or you can implement the "Snapshotable"-interface which might be important for object with large fields(only the changed fields will be written upon commit, but at the overhead of holding a clone of the object in memory for comparsion).
There's also a AutoInitializing interface for a __initializeField(Array $row = array()) method that will be called when an object is created to populate the fields + do initialization. If that interface is not implemented the object fields will be set from "outside" the object one by one($obj->field1 = val, etc.)
I'll post the code + 500-600 lines of testcases later today or tomorrow, cheers.
-
Feb 15, 2006, 05:37 #20
Nice.
Are you going to support table/entity inheritance?
Postgres supports it directly CREATE TABLE a (fields); CREATE TABLE b(fields) INHERITS(a);
Or with other rdbms the two tables have the same primary keys, with the base referencing the subclass.
-
Feb 15, 2006, 05:42 #21
Ah yes I know inheritance ;/ I've read all in PoEAA about it (well I've read all of PoEAA like 2 times but hey ;p) articles on the webb, etc. - I just can't decide if it's worth it or not. Currently I can say no, but maybee in the future.
-
Feb 15, 2006, 08:41 #22
Ok I just got the basic layout of the Composite Mapper down - I'm thinking about using something like this for Composite Mappers.
This would look something like this when it's a use:PHP Code:<?php
$mapper = $reg->newCompositeMapper("Person","Group");
list($persons,$groups) = $mapper->findBySql("select person.*, group.* from person, group where <somecondition>");
?>
-
Feb 15, 2006, 08:55 #23
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
multitable inheritance is something you eventually will need if you have a large domain model.
-
Feb 15, 2006, 10:08 #24
Originally Posted by thr
Can a mapper handle multiple resultsets from a batch of queries, using PDO::nextRowset() ?
PHP Code:<?php
list($person, $orders) = $mapper->findBySQL('SELECT * FROM Person WHERE personId = :personId; SELECT * FROM Orders WHERE personId = :personId');
?>
-
Feb 15, 2006, 10:23 #25
Originally Posted by Ren
Originally Posted by Ren
Originally Posted by kyberfabrikken
Bookmarks