SitePoint Sponsor |
|
User Tag List
Results 1 to 23 of 23
Thread: SimpleTest MockObject question?
-
Aug 27, 2004, 17:08 #1
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
SimpleTest MockObject question?
Sorry if this isn't the right place for this question.
I'm trying to use a mock object for database results and im not sure how to go about this. My problem is that $subscriberBase->findAllSubscribers() returns an array of Subscriber objects. the first call to execute() is "SELECT * FROM subscribers", then it takes the resulting array and makes Subscriber objects which contains another call to execute() to get the Subscribers "subscriptions" . The problem im having is i get an error saying my call to getRow() is on a non-object when trying to get the subscriptions.
If i remove the nested call to execute(), i get that lovely green bar
So basically, how do i handle nested calls to this mock object?
PHP Code:function testFindAllSubscribers()
{
$dao =& new MockDao($this);
$results =& new MockDataAccessResult($this);
$results->setReturnValue('getRow', false);
$results->setReturnValueAt(0, 'getRow', array("name"=>"Mike"));
$results->setReturnValueAt(1, 'getRow', array("name"=>"Joe"));
$dao->setReturnReference('execute', $results, array('SELECT * FROM subscribers'));
$subscriberBase = new SubscriberBase($dao);
$subscribers = $subscriberBase->findAllSubscribers();
$this->assertEqual($subscribers[0]->get('name'), 'Mike');
$this->assertEqual($subscribers[1]->get('name'), 'Joe');
}
Thanks
-
Aug 28, 2004, 02:41 #2
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Looks basically right to me. Is this php4? Are all objects being passed and returned by reference?
Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.
-
Aug 28, 2004, 05:10 #3
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Im testing this with php5, so by default they are getting passed by reference, right?
-
Aug 28, 2004, 05:33 #4
- Join Date
- Jan 2004
- Location
- Planet Earth
- Posts
- 1,764
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Yep, by reference
-
Aug 28, 2004, 05:49 #5
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
here is my test
PHP Code:function testFindAllSubscribers()
{
$dao =& new MockDao($this);
$results =& new MockDataAccessResult($this);
$results->setReturnValue('getRow', false);
$results->setReturnValueAt(0, 'getRow', array("id"=>1, "name"=>"Mike"));
$results->setReturnValueAt(1, 'getRow', array("id"=>2, "name"=>"Joe"));
$dao->setReturnReference('execute', $results, array('SELECT * FROM subscribers'));
$subscriberBase = new SubscriberBase($dao);
$subscribers = $subscriberBase->findAllSubscribers();
$this->assertEqual($subscribers[0]->get('name'), 'Mike');
$this->assertEqual($subscribers[1]->get('name'), 'Joe');
}
here is what SubscriberBase looks like:
PHP Code:class SubscriberBase{
var $dao;
function SubscriberBase($dao)
{
$this->dao = $dao;
}
function findAllSubscribers()
{
$subscribers = $this->dao->execute( "SELECT * FROM subscribers" );
while($subscriber = $subscribers->getRow())
{
$objArray[] = $this->_getObject($subscriber);
}
return $objArray;
}
function _getObject($row)
{
$subscriber = new Subscriber();
$subscriber->setFirstName($row['name']);
$subscriptions = $this->getSubscriptions($row['id']);
while($subscription = $subscriptions->getRow())
{
$subscriber->addSubscription($subscription['id']);
}
return $subscriber;
}
function getSubscriptions($id)
{
$subscriptions = $this->dao->execute( "SELECT id, name FROM newsletters AS N, subscriptions AS S WHERE S.subscriber_id=$id AND N.id=S.newsletter_id" );
while($subscription = $subscriptions->getRow())
{
$subscriber->addSubscription($subscription['id']);
}
}
}
Code:Fatal error: Call to a member function getRow() on a non-object in class.SubscriberBase.php on line 98
Last edited by MikeShank; Aug 28, 2004 at 08:14.
-
Aug 28, 2004, 06:14 #6
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by Widow Maker
-
Aug 28, 2004, 06:21 #7
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
MikeShank, can we see some more of your actual code to see if we can spot any possible issues? And, what version of SimpleTest are you using? We just located some php5 issues that were corrected for RC1
-
Aug 28, 2004, 06:46 #8
- Join Date
- Jan 2004
- Location
- Planet Earth
- Posts
- 1,764
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
though in practice they end up nearly the same
-
Aug 28, 2004, 06:56 #9
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
im using version 1.0beta6
I posted the code i am using above
Thanks for your input
-
Aug 28, 2004, 07:57 #10
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Since I know there have been php5 specific fixes in SimpleTest, please upgrade to 1.0RC1 and try that.
Also, you have not shown enough of the SubscriberBase class, specifically the $this->dao assignment.
-
Aug 28, 2004, 08:14 #11
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
sorry about that, i edited my code above to show the assignment
-
Aug 28, 2004, 09:31 #12
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by MikeShank
.
Originally Posted by MikeShank
PHP Code:function testFindAllSubscribers() {
...
$rows = $dao->execute('SELECT * FROM subscribers');
$this->dump($rows->getRow());
$this->dump($rows->getRow());
}
Also you have a mixture of PHP4 code and PHP5 with regard to references. When getting the last SimpleTest release out it broke between PHP5 beta4 and PHP5.0.1 on precisely this issue. I suspect that PHP5 has some issues with hybrid code, where you pass an object handle by reference for example. Just guessing, but you could try dropping the ampersands from the "new" statements.
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Aug 28, 2004, 11:42 #13
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by lastcraft
PHP Code:function doSomething($one, &$two) {
$one->x = 'new';
$one = false;
$two->x = 'new';
$two = false;
}
class Bar { public $x = 'default'; }
$foo = new Bar;
$blah = new Bar;
doSomething($foo, $blah);
var_dump($foo, $blah);
Code:object(Bar)#1 (1) { ["x"]=> string(3) "new" } bool(false)
-
Aug 28, 2004, 16:03 #14
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
if i comment out
PHP Code:subscriptions = $this->getSubscriptions($row['id']);
while($subscription = $subscriptions->getRow())
{
$subscriber->addSubscription($subscription['id']);
}
Also i did remove the & and it does not seem to have an effect, it still chokes on the second call to execute() in getSubscriptions()
-
Aug 28, 2004, 22:07 #15
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Look at the dao->execute above where your test case is failing:
PHP Code:$subscriptions = $this->dao->execute(
"SELECT id, name FROM newsletters AS N, subscriptions AS S WHERE S.subscriber_id=$id AND N.id=S.newsletter_id" );
-
Aug 29, 2004, 06:28 #16
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
yes, I know the execute calls are different. That was my question, how do i handle nested call to execute().
Basicallly how would you test what i am testing here. you want execute( "SELECT * FROM subscribers" ) to return an array of subscribers and you want execute(
****"SELECT id, name FROM newsletters AS N, subscriptions AS S WHERE S.subscriber_id=$id AND N.id=S.newsletter_id" ) to return an array of newsletters.
-
Aug 29, 2004, 06:50 #17
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
something along the lines of :
PHP Code:$dao = new MockDao($this);
$results = new MockDataAccessResult($this);
$results->setReturnValue('getRow', false);
$results->setReturnValueAt(0, 'getRow', array("name"=>"Mike", 'id'=>1));
$results->setReturnValueAt(1, 'getRow', array("name"=>"Joe", 'id'=>5));
$dao->setReturnReference('execute', $results, array('SELECT * FROM subscribers'));
$nullset = new MockDataAccessResult($this);
$nullset->setReturnValue('getRow', false);
$resultsSub1 = new MockDataAccessResult($this);
$resultsSub1->setReturnReference('getRow', $nullset);
$dao->setReturnReference('execute', $resultsSub1, array(
new WantedPatternExpectation('/SELECT.*newsletters.*_id=1/i')));
$resultsSub5 = new MockDataAccessResult($this);
$resultsSub5->setReturnReference('getRow', $nullset);
$dao->setReturnReference('execute', $resultsSub5, array(
new WantedPatternExpectation('/SELECT.*newsletters.*_id=5/i')));
-
Aug 29, 2004, 11:08 #18
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by sweatje
.
Now a confession. In using the database as an example of mock objects I think I made a big mistake. It can be done, but it's really not appropriate for testing SQL statements. After all, you are really testing not just the mechanics of your iterator, but the connection between the data and the SQL statement. I've been down this road and here is the problem I eventually ran into.
It all goes great at first, your tests run fast and (given the mental agility of someone like Jason) quick and easy to write. The problem is that the tests are brittle. If you change the table name, say, you will have to go back and edit every single test. When mocks span a translation layer, and you are acting as a human compiler trying to translate things by hand, I'd get suspicious. By all means make a test of the iterator mechanics this way (mocks are good at that), but for anything depending on query text I'd use a real database. You can place these tests in a separate test suite so the unite tests don't depend on installed components and run quicker. I now place the database mapping tests into integration tests.
Which makes my old PHP|Architect article on the subject a little misleading. What can I say? You live and learn.
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Aug 29, 2004, 13:32 #19
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Thanks guys,
It did seem to me that it is more work to mock the db, then it would be to just use an actual db. But then again I'm just beginning with testing an don't fully understand the consequences of doing that. Thanks for your help.
-
Aug 29, 2004, 16:30 #20
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
after going thru my code, the issue was with my _getObject() method trying to iterate thru the returned results from getSubscriptions() when I didn't have anything being returned from that method. A stupid mistake on my part, sorry about that, but I did learn some info in using mocks. Thanks again.
-
Aug 29, 2004, 19:06 #21
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by MikeShank
You can run into similar problems with parsers. Suppose you have a lexer that splits the incoming stream into tokens and a parser that produces events from this. It is likely that you will make changes to the lexer and parser together. Changing the lexer means changing the token boundaries. If you have mocked token streams then this is going to be tedious to change in the all those tests of the parser. It is easier to refactor if they are tested together.
The catch is that you need to break the problem down initially, especially if you are using a fine grain TDD cycle. You have to start with one object and test that. So far you have done fine in that you know the basic iterator mechanics are working and so doing the first few tests with mocks hasn't wasted. It's just that it is time to switch to end to end testing once you start getting such fiddly assertions on implementation details such as table names.
You can still test everything with mocks and it will work well. Too well, to the point of being seductive. Refactoring code means refactoring the tests as well. It's just that it pays to keep an eye on both to avoid future hassle.
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Aug 30, 2004, 18:10 #22
- Join Date
- May 2001
- Posts
- 193
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Alright, alright! I tried to take the easy way out.
I now see your point about controlling the rows coming back without worrying about the sql. I remeber reading some posts about TDD and people saying once you do it you wont code any other way, I have to admit it is addictive. btw SimpleTest is a great tool, thanks.
-
Aug 30, 2004, 23:19 #23
- Join Date
- Jan 2004
- Location
- Oslo, Norway
- Posts
- 894
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by sweatje
If you have a working PHP 4 application that uses "references", you can obviously keep them, but I would phase them out gradually.Dagfinn Reiersøl
PHP in Action / Blog / Twitter
"Making the impossible possible, the possible easy,
and the easy elegant" -- Moshe Feldenkrais
Bookmarks