Testing models

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

Post Reply
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Testing models

Post by alex.barylski »

I have spent the better part of the day studying automated testing and it's various methods and read countless articles on how people hate fixtures and testing databases and mock objects should be used...blah blah...

When I applied each of these techniques to my own application...verious problems popped up...namely testing a model which is dependent on a database access object.

Code: Select all

class MyModel{

  function selectAllListings()
  {
    $db->query('SELECT * FROM things');
    $arr = $db->execute();
  
    $results = array();
    foreach($arr as $item){
      // TODO: Do stuff to resultset before returning
    }

    $results;
  }

}
Testing a model like the above is next to impossible without using an actual RDBMS...why???

When I mocked the database object, obviously calls to it return nothing...and when notihng is returned from the execute() above, guess what happens to the foreach? Chokes because it expects an array. Additionally, I need the for look to fire to properly test the behavior of the method.

The only solution I can see, is mocking everything *but* the database object.

The horrow stories I've read about tests taking 15 minutes...well thats either a requirement or I have to find a more efficient approach. So I thought. Why not group your tests into categories?

When I'm working on a project, the interface usually dictates the categorization into modules like:
1) Dashboard
2) Reports
3) Contacts
4) Templates
5) Admin

Each of these modules usually are composed of various classes, like models, views, controllers, etc...

If I grouped my tests like the above and only ran tests which are needed...I would save a ton of processing time and likely avoid the 15 minute horor stories all the while repeaing the benefit of a properly tested model.

How do you deal with it? How do you test your models? I've read a lot of people in forums,articles, etc suggest mocking the database abstraction layer, data access objects, etc, but I have yet to see any concrete examples...do you know of any or care to share your experiences?

Cheers :)
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Re: Testing models

Post by Jenk »

Hockey wrote:Testing a model like the above is next to impossible without using an actual RDBMS...why???
Not impossible, the reason you are having difficulty is answered below.
When I mocked the database object, obviously calls to it return nothing...and when notihng is returned from the execute() above, guess what happens to the foreach? Chokes because it expects an array. Additionally, I need the for look to fire to properly test the behavior of the method.

The only solution I can see, is mocking everything *but* the database object.
Your "mock" object is not mocking, infact it's stubbing. Mock objects are objects that fit an interface and have a predetermined behaviour. I.e. they do what the object you are testing expects it to do. So in your case, you would script a mock like so (using SimpleTest's SimpleMock) :

Code: Select all

class TestSomething extends TestCase {
  public function testSomethingDoesSomething () {
    Mock::generate('Database');
    $mock = new MockDatabase($this);
    $model = new Something();

    // scripting the mock 
    $mock->expectOnce('query', array('SELECT * FROM `things`'));
    $mock->expectOnce('execute');
    $mock->setReturnValue('execute', array(/* your array of things goes here */));

    $model->setDb($mock);

    // do whatever.. assert whatever
}
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

That I will agree with...

Thanks :)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Another question:

Following BDD practices, how would one test code such as this:

Code: Select all

public function create($email)
{
	$db = $this->getDatabaseObject();

	if(!$check->isValid($email)){
		return EMAIL_INVALID;
	}

	if($this->_isExists($email)){
		return EMAIL_EXISTS;
	}
}
Having just watched that video on Google with Dave Astel...BDD promotes a contextual testing approach rather than a 1:1 test case class per real class relationship that I have typically observed in TDD and classical testing.

I would have personally tested the above something like:

Code: Select all

function testModelCreate()
{

  $ret = $model->create('invalid-email-address.com');
  $this->assertTrue($ret != EMAIL_INVALID);

  $ret = $model->create('email@exists.com');
  $this->assertTrue($ret != EMAIL_EXISTS);
}
Using this technique there is the 1:1 relationship between test method and real method...BDD seems to promote the idea of context rather than method...

So what would the BDD contexts be for a method like the above? Would it be something like:

Code: Select all

class DescribeModelCreate{

  function shouldReturnEmailInvalid();  
  function shouldReturnEmailExists();  

}
DescribeModelCreate sets a more specific context than a typical unit test case which might look something like:

Code: Select all

class TestingModel{
  
  function testCreate()
  {
    $this->assertTrue(/*Test if email valid*/);
    $this->assertTrue(/*Test if email exists*/);
  }  

}
Does my observation or comparison of the two sound accurate? Does my BDD attempt meet the contextual requirements rather than the interface? Not sure how else to say that. :P
Post Reply