|
From: Marcus B. <ma...@la...> - 2003-09-28 22:36:09
|
Hi. This release deprecates quite a few things that will later disappear come the Beta version. Mainly just name changes. 1) When including SimpleTest the following filenames have changed... simple_html_test.php --> reporter.php simple_mock.php --> mock_objects.php simple_unit.php --> unit_tester.php simple_web.php --> web_tester.php 2) The observer pattern is on the way out... $test = &new MyTestCase(); $test->attachObserver(new TestHtmlDisplay()); $test->run(); ...should be replaced with... $test = &new MyTestCase(); $test->run(new HtmlReporter()); 3) The basic HTML display class is now called HtmlReporter. 4) When setting options you now have to use the common class SimpleTestOptions... GroupTest::ignore --> SimpleTestOptions::ignore() Mock::setMockBaseClass() --> SimpleTestOptions::setMockBaseclass() Sorry about all the hassle this will cause, but I figure it was better to get it over with. Both interfaces are supported in Alpha8 and this acts as an interim release until Beta1. The rest of the release is just small improvements: Error trapping and testing, page redirects and extra mock object expectations. yours, Marcus. -- Marcus Baker, ma...@la..., no...@ap... |
|
From: Jason S. <jsw...@ya...> - 2003-10-29 12:55:19
|
Just a check to make sure I am headed in the right direction:
TestDataCacheDaoFile.php:
<?php
/**
* test harness for DataCacheDaoFile class tests
*
* @author Jason E. Sweat
* @since 2003-10-28
* @package DataCache
* @subpackage tests
* @version
* <pre>
* $Log: $
* </pre>
*/
error_reporting(E_ALL);
/**
* relative path to SimpleTest
*/
@define('SIMPLE_TEST', '../simpletest/');
/**
* SimpleTest base class
*/
require_once SIMPLE_TEST.'unit_tester.php';
/**
* SimpleTest reporter class
*/
require_once SIMPLE_TEST.'reporter.php';
/**
* DataCache class to be tested
*/
require_once '../DataCacheDaoFile.php';
/**
* Application Identifier to use for the tests
*/
@define('TEST_APPLICATION', 'SimpleTests');
/**
*
*/
define('TEST_DAOFILE_BLANK', 'empty');
/**
* still not working
* with funky chars ~!@#$%^&*()_+`-={}|\][:"<>?';/.,
*
*/
define('TEST_DAOFILE_STRING', <<<EOS
has both '
and " in it
on multiple lines
EOS
);
/**
*
* @package DataCache
* @subpackage tests
*/
class TestDataCacheDaoFile extends UnitTestCase
{
/**
* @var object $c the data cache object
*/
var $c;
/**
* constructor
*
* @return void
*/
function TestDataCacheDaoFile() {
$this->UnitTestCase();
}
/**
* initialization of test function
*
* @return void
*/
function Setup() {
$this->c =& new DataCacheDaoFile(TEST_APPLICATION);
$id = TEST_DAOFILE_BLANK;
if (file_exists(DATACACHE_PATH.TEST_APPLICATION."/$id.php")) {
unlink(DATACACHE_PATH.TEST_APPLICATION."/$id.php");
}
}
/**
* test php environment is setup correctly
*
* @return void
*/
function TestEnv() {
$this->AssertTrue(defined('DATACACHE_PATH'));
$this->AssertTrue(is_dir(DATACACHE_PATH));
$this->AssertEqual(substr(DATACACHE_PATH,-1,1), '/');
}
/**
* test creation of a directory when instanciated
*
* @return void
*/
function TestDirCreation() {
$id = 'mkdir';
$this->AssertFalse(is_dir(DATACACHE_PATH.TEST_APPLICATION.$id));
$o =& new DataCacheDaoFile(TEST_APPLICATION.$id);
$this->AssertTrue(is_dir(DATACACHE_PATH.TEST_APPLICATION.$id));
$this->AssertTrue(rmdir(DATACACHE_PATH.TEST_APPLICATION.$id));
}
/**
* test when data not already cached
*
* @return void
*/
function TestEmptyCache() {
$id = TEST_DAOFILE_BLANK;
$this->AssertFalse($this->c->IsCached($id));
$r = $this->c->GetCache($id);
$this->AssertError("identifier '$id' does not have a valid cache");
}
/**
* test timestamp
*
* @return void
*/
function TestTimeStamp() {
$id = 'time';
$ts = mktime();
$this->AssertTrue($this->c->SetCache($id,$ts.$ts));
$this->AssertTrue(1 >= abs($this->c->GetCacheTime($id) - $ts));
$this->AssertEqual($this->c->GetCache($id), $ts.$ts);
}
/**
* test caching different types
*
* @return void
*/
function TestCache() {
$data = array(
'str1' => 'a simple string'
,'str2' => "a multi-\nline string"
,'str3' => 'a "double quote" string'
,'str4' => "a 'single quote' string"
,'str5' => TEST_DAOFILE_STRING
,'int' => mktime()
,'array' => array('one', 'two', 'three')
,'object' => new DataCacheDaoFile(TEST_APPLICATION)
);
$this->AssertTrue($this->c->SetCache('data', $data));
$this->AssertIdentical($this->c->GetCache('data'), $data);
$this->AssertTrue($this->c->SetCache('data', $data, false));
$this->AssertIdentical($this->c->GetCache('data'), $data);
}
/**
* test file compression
*
* @return void
*/
function TestZip() {
ob_start();
phpinfo();
$data = ob_get_contents();
ob_end_clean();
$this->AssertTrue($this->c->SetCache('zip', $data));
$this->AssertTrue($this->c->SetCache('nozip', $data, false));
$this->AssertTrue( filesize(DATACACHE_PATH.TEST_APPLICATION.'/nozip.php') >
filesize(DATACACHE_PATH.TEST_APPLICATION.'/zip.php'));
}
}
//run if stand alone
if (!isset($this)) {
$test = &new TestDataCacheDaoFile();
$test->run(new HtmlReporter());
}
#?>
DataCacheDaoFile.php:
<?php
/**
* DataCacheDaoFile class definiton
*
* concrete file based DAO for the DataCache class
*
* A PHP Class to implement server side data caching
* Copyright (C) 2003 Jason E. Sweat
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @author Jason E. Sweat
* @since 2003-10-26
* @package DataCache
* @subpackage DAO
* @version
* <pre>
* $Log: $
* </pre>
*/
/**
* location where cache files are kept
*
* assumed in the rest of the code to be full path with a trailing slash
*/
define('DATACACHE_PATH', '/home/sweatje/phpc/cache/data/');
/**
* template for caching data as readable php code
*/
define('DATACACHE_TEMPLATE', <<<EOS
<?php
\$m_cache_data = unserialize(stripslashes('%s'));
#?>
EOS
);
/**
* template for caching data as zipped data
*/
define('DATACACHE_ZIP_TEMPLATE', <<<EOS
<?php
\$m_cache_data = unserialize(gzuncompress(base64_decode('%s')));
#?>
EOS
);
/**
* concrete file based DAO for the DataCache class
*
* @package DataCache
* @subpackage DAO
*/
class DataCacheDaoFile
{
/**
* @private
* @var string _msAppl the name of the application
*/
var $_msAppl;
/**
* constructor
*
* @param string $psAppl the application identifier
* @return void
*/
function DataCacheDaoFile($psAppl)
{
if (!is_dir(DATACACHE_PATH.$psAppl)) {
umask(0007);
mkdir(DATACACHE_PATH.$psAppl);
}
$this->_msAppl = $psAppl;
}
/**
* return the full path and file name for a cache file
*
* @private
* @param string $psId identifier for the data being cached
* @return string the file path and name
*/
function _GetFileName($psId)
{
return DATACACHE_PATH.$this->_msAppl.'/'.$psId.'.php';
}
/**
* cache a particular piece of data
*
* @param string $psId identifier for the data being cached
* @param mixed $pmValue value to cache
* @param boolean $pbZip optional - compress the data, defaults to true
* @return boolean sucess
*/
function SetCache($psId, $pmValue, $pbZip=true)
{
$s_file = $this->_GetFileName($psId);
$s_template = ($pbZip) ? DATACACHE_ZIP_TEMPLATE : DATACACHE_TEMPLATE;
$s_data = ($pbZip) ? base64_encode(gzcompress(serialize($pmValue)))
: addslashes(serialize($pmValue));
$r_fh = fopen($s_file, 'w');
flock($r_fh, LOCK_EX);
$b_ret = fwrite($r_fh,
sprintf($s_template,
$s_data
)
);
flock($r_fh, LOCK_UN);
fclose($r_fh);
return $b_ret;
}
/**
* check to see if a cached data is valid
*
* @param string $psId identifier for the data being cached
* @return boolean result of check
*/
function IsCached($psId)
{
return file_exists($this->_GetFileName($psId));
}
/**
* retrieve some cached data
*
* @param string $psId identifier for the data being cached
* @return mixed the data cached
*/
function GetCache($psId)
{
if ($this->IsCached($psId)) {
require $this->_GetFileName($psId);
return $m_cache_data;
} else {
trigger_error("identifier '$psId' does not have a valid cache");
}
}
/**
* retrieve time when data was cached
*
* @param string $psId identifier for the data being cached
* @return mixed the data cached
*/
function GetCacheTime($psId)
{
if ($this->IsCached($psId)) {
return filemtime($this->_GetFileName($psId));
} else {
trigger_error("identifier '$psId' does not have a valid cache");
}
}
}
#?>
__________________________________
Do you Yahoo!?
Exclusive Video Premiere - Britney Spears
http://launch.yahoo.com/promos/britneyspears/
|
|
From: Marcus B. <ma...@la...> - 2003-10-29 14:05:10
|
Hi... Jason Sweat wrote: > Just a check to make sure I am headed in the right direction: I haven't got time to look at it right now. I'll return to it in a couple of days if that's OK. yours, Marcus -- Marcus Baker, ma...@la..., no...@ap... |
|
From: Jason S. <jsw...@ya...> - 2003-10-29 18:21:16
|
--- Marcus Baker <ma...@la...> wrote: > I haven't got time to look at it right now. I'll return to it in a > couple of days if that's OK. Sounds good, comments welcome when available ;) __________________________________ Do you Yahoo!? Exclusive Video Premiere - Britney Spears http://launch.yahoo.com/promos/britneyspears/ |
|
From: Marcus B. <ma...@la...> - 2003-11-13 04:12:10
|
Hi...
Jason Sweat wrote:
> Just a check to make sure I am headed in the right direction:
You certainly seem to be.
> /**
> *
> * @package DataCache
> * @subpackage tests
> */
Surprising. Do you find it useful to PHPDoc your test cases?
> function Setup() {
> $this->c =& new DataCacheDaoFile(TEST_APPLICATION);
> $id = TEST_DAOFILE_BLANK;
> if (file_exists(DATACACHE_PATH.TEST_APPLICATION."/$id.php")) {
> unlink(DATACACHE_PATH.TEST_APPLICATION."/$id.php");
> }
> }
This is certainly the right way to use setUp().
> function TestTimeStamp() {
> $id = 'time';
> $ts = mktime();
> $this->AssertTrue($this->c->SetCache($id,$ts.$ts));
> $this->AssertTrue(1 >= abs($this->c->GetCacheTime($id) - $ts));
> $this->AssertEqual($this->c->GetCache($id), $ts.$ts);
> }
You could create a separate assert method for this, such as...
function assertNow($timing, $message = '%s') {
$message = sprintf($message, 'Within one second of now');
$this->assertTrue(1 >= abs($timing - mktime()), $message);
}
function testTimeStamp() {
$id = 'time';
$this->AssertTrue($this->c->SetCache($id,$ts.$ts));
$this->AssertNow($this->c->GetCacheTime($id));
$this->AssertEqual($this->c->GetCache($id), $ts.$ts);
}
>
> //run if stand alone
> if (!isset($this)) {
> $test = &new TestDataCacheDaoFile();
> $test->run(new HtmlReporter());
> }
Sneaky :).
yours, Marcus
--
Marcus Baker, ma...@la..., no...@ap...
|
|
From: Jason S. <jsw...@ya...> - 2003-11-13 05:37:26
|
Thanks for getting back to me.
> > /**
> > *
> > * @package DataCache
> > * @subpackage tests
> > */
>
> Surprising. Do you find it useful to PHPDoc your test cases?
More like force of habit, enforced by the tools. I set up my text editor to
automatically stub them in when I paste in a class, function or var.
> You could create a separate assert method for this, such as...
>
> function assertNow($timing, $message = '%s') {
> $message = sprintf($message, 'Within one second of now');
> $this->assertTrue(1 >= abs($timing - mktime()), $message);
> }
That looks good. Where would you define it in a test suite that was able to
run each test file standalone? Seems like you would have to hack it into
simpletest itself to make it universally accessable, or is there a more
reasonable hook?
Regards,
Jason
__________________________________
Do you Yahoo!?
Protect your identity with Yahoo! Mail AddressGuard
http://antispam.yahoo.com/whatsnewfree
|
|
From: Marcus B. <ma...@la...> - 2003-11-13 14:56:04
|
Hi...
Jason Sweat wrote:
> That looks good. Where would you define it in a test suite that was able to
> run each test file standalone? Seems like you would have to hack it into
> simpletest itself to make it universally accessable, or is there a more
> reasonable hook?
If you just want it for one test case, then just add it to that test
case. Only methods that start with the name "test" are actually run. You
can add as many other helper methods case as you want.
If you want to use it in more than one test case then do the following...
class TimerTest extends UnitTestCase {
function TimerTest() {
$this->UnitTestCase();
}
function assertNow( .. ) { ... }
}
SimpleTestOptions::ignore('TimerTest');
class SomeTests extends TimerTest {
function SomeTests() {
$this->TimerTest();
}
function testStuff() {
$this->assertNow( ... );
}
}
The ignore option tells SimpleTest not to run the test case so that it
doesn't interfere with the rest of the test suite. It would increase the
test case count for example.
> Jason
yours, Marcus
--
Marcus Baker, ma...@la..., no...@ap...
|
|
From: Jason S. <jsw...@ya...> - 2003-11-19 20:31:30
|
First of all, please do not mock me (pun intentional) for the use of a global.
It is force of habit and thus far I have failed to see the value of
implementing a singleton or passing around a tramp object or creating many
instances of the database connection when they all go to the same spot anyway.
Hrumph.
BTW: this was an attempt at test driven development, and it did already catch
some boneheadded coding mistakes :)
Here is the essence of the test case:
require_once '../models/ListOfValues.php';
define('TEST_LOV_LIST', 'TEST');
class TestListOfValues extends UnitTestCase
{
function TestListOfValues()
{
$this->UnitTestCase();
}
function SetUp()
{
Mock::Generate('ADOConnection');
Mock::Generate('ADORecordSet');
$GLOBALS['go_conn'] =& new MockADOConnection($this);
$this->lov =& new ListOfValues;
}
function TestGetValueSk()
{
//create a result set object
$o_rs =& new MockADORecordSet($this);
$s_ret = '1';
$o_rs->setReturnValue('FetchRow', array('SK' => $s_ret));
$o_rs->setReturnValue('RowCount', 1);
//have the global connection return it
global $go_conn;
$go_conn->SetReturnReference('Execute', $o_rs);
$a_bind = array(
'LIST' => TEST_LOV_LIST
,'CODE' => 'A'
);
$go_conn->ExpectOnce('Execute', array(LOV_SEL_SK, $a_bind));
//test
$this->AssertEqual($s_ret, $this->lov->GetValueSk(TEST_LOV_LIST, 'A',
'desc'));
$go_conn->Tally();
}
}
with relavant bits of the target code:
define('LOV_SEL_SK', <<<EOS
SELECT
*
FROM
cost_card_lov
WHERE
lov_attrbt_name = :LIST
AND lov_code = :CODE
EOS
);
class ListOfValues
{
var $_aoConn;
function ListOfValues()
{
global $go_conn;
$this->_aoConn =& $go_conn;
}
/**
*
* @param string psList the list identifier
* @param string psCode the code value
* @param string psDesc description the code value references
* @return integer surrogate key for the list value
*/
function GetValueSk($psList, $psCode, $psDesc)
{
$a_bind = array(
'LIST' => $psList
,'CODE' => $psCode
);
$o_rs =& $this->_aoConn->Execute(LOV_SEL_SK, $a_bind);
if ($o_rs) {
if (1 == $o_rs->RowCount()) {
$a_row = $o_rs->FetchRow();
return $a_row['SK'];
} else {
$this->_SetListVal($psList, $psCode, $psDesc);
return $this->GetValueSk($psList, $psCode, $psDesc);
}
} else {
trigger_error('Database Error: '.$go_conn->ErrorMsg());
}
}
}
The question I have it that at first blush, it looks like I am coding the
database code twice, once for the expectation, and again for the
implementation. Am I doing this wrong? Is there an easier approach?
Thanks.
Jason
__________________________________
Do you Yahoo!?
Protect your identity with Yahoo! Mail AddressGuard
http://antispam.yahoo.com/whatsnewfree
|
|
From: Marcus B. <ma...@la...> - 2003-11-19 22:34:51
|
Hi...
Jason Sweat wrote:
> First of all, please do not mock me (pun intentional) for the use of a global.
Ok, I'll bite my lip... :) Actually I don't think they are all bad, it
is just that the downsides are usually much larger than first appear.
They are a real pain for testing, but anyway...
> BTW: this was an attempt at test driven development, and it did already catch
> some boneheadded coding mistakes :)
I'd still be the worst programer on the planet if it wasn't for this.
> The question I have it that at first blush, it looks like I am coding the
> database code twice, once for the expectation, and again for the
> implementation. Am I doing this wrong?
If you removed the intermediate variables and just hard code things I
find that the tests get clearer. I would probably do the same for the
query as well, but here is small step in that direction.
function TestGetValueSk()
{
$o_rs =& new MockADORecordSet($this);
$o_rs->setReturnValue('FetchRow', array('SK' => 1));
$o_rs->setReturnValue('RowCount', 1);
global $go_conn;
$go_conn->setReturnReference('Execute', $o_rs);
$go_conn->expectOnce('Execute', array(
LOV_SEL_SK,
array('LIST' => 'test', 'CODE' => 'A')));
$this->assertEqual(1, $this->lov->GetValueSk('test', 'A', 'desc'));
$go_conn->tally();
}
I think this is a good test. As someone used to reading tests with mock
objects you have just taught me a little bit of how the AdoDB library
works, which is cool. Nothing wrong here.
> Is there an easier approach?
It's not that bad is it? This is only the first test method right? They
are pretty quick to write once you see that basic pattern. I think you
are on course.
I believe you have gained a lot. The mocks stress the interactions and
so make the test clearer. Also you don't have to create any test data in
a real database. You can test failure conditions more easily as well.
Pretty soon you will win I am sure. If you create another class like
this one, you will then be refactoring some of the code into a
superclass or separate query object. That fixed query will likely get
more compicated in the implementation (the test will stay the same)
perhaps adding other bindings. Then the fixed test code will be really
useful to make sure the original version does not get broken.
I do end up writing more code, including the test stuff, overall. I find
that I write it at a more constant speed and so win easily. I am more
confident in the code as well.
>
> Thanks.
>
> Jason
I am not sure I have been very helpful.
yours, Marcus
--
Marcus Baker, ma...@la..., no...@ap...
|
|
From: Jason S. <jsw...@ya...> - 2003-11-20 17:01:33
|
--- Marcus Baker <ma...@la...> wrote:
> Jason Sweat wrote:
> > The question I have it that at first blush, it looks like I am coding the
> > database code twice, once for the expectation, and again for the
> > implementation. Am I doing this wrong?
>
> If you removed the intermediate variables and just hard code things I
> find that the tests get clearer. I would probably do the same for the
> query as well, but here is small step in that direction.
>
Okay, I added some caching to the object, so the queries changed a bit. Here
is a new test:
function TestGetValueSkWhenAdding()
{
// test io vars
$a_rs = $this->_aaOrigRs;
$a_rs[] = array(
'SK' => 10
,'LOV_ATTRBT_NAME' => TEST_LOV_LIST
,'LOV_CODE' => 'foobar'
,'LOV_DESC' => 'zaazen'
);
$a_add = $a_rs[count($a_rs)-1];
//create the result set objects
$o_rs_ins =& new MockADORecordSet($this);
$o_rs_sel =& new MockADORecordSet($this);
$o_rs_sel->setReturnValue('GetArray', $a_rs);
//reset global connection set expected returns
global $go_conn;
$go_conn->ClearHistory();
$go_conn->SetReturnReferenceAt(0, 'Execute', $o_rs_ins);
$go_conn->SetReturnReferenceAt(1, 'Execute', $o_rs_sel);
$go_conn->ExpectCallCount('Execute', 2);
$go_conn->ExpectArgumentsAt(0, 'Execute'
, array(LOV_INS_SQL, array(
'LIST' => TEST_LOV_LIST
,'CODE' => $a_add['LOV_CODE']
,'DESC' => $a_add['LOV_DESC']
)));
$go_conn->ExpectArgumentsAt(1,'Execute', array(LOV_SEL_SQL, array('LIST' =>
TEST_LOV_LIST)));
//test
$this->AssertEqual($a_add['SK'], $this->lov->GetValueSk(TEST_LOV_LIST
,$a_add['LOV_CODE']
,$a_add['LOV_DESC']));
//check database expectations
$go_conn->Tally();
}
> I believe you have gained a lot. The mocks stress the interactions and
> so make the test clearer. Also you don't have to create any test data in
> a real database. You can test failure conditions more easily as well.
I agree with the failure conditions observation. This certainly will be easer
than taking down the database in the middle of a test!!!
I guess the central question I am wrestling with relates to my earlier post
regarding testing of private methods, and what could be more private to an
object than it's database traffic ;) I guess in an ideal world, you can just
test the public methods as a black box, but Mocks end up being the best
compromise in order to disconnect from the database and just test the remaining
logic.
Thanks for putting up with my floundering around.
> I am not sure I have been very helpful.
I am sure you have. Encouragement is always welcome :)
Regards,
Jason
__________________________________
Do you Yahoo!?
Free Pop-Up Blocker - Get it now
http://companion.yahoo.com/
|
|
From: Marcus B. <ma...@la...> - 2003-11-20 18:20:38
|
Hi... Jason Sweat wrote: > I guess the central question I am wrestling with relates to my earlier post > regarding testing of private methods, and what could be more private to an > object than it's database traffic ;) I am not sure that it should be private. What if you want to swap your database connection for one that does logging of all of the queries? Suddenly you want to swap it in and out. It is another form of interface after all. Have a look at the registry pattern in the phppatterns site (blatant self publicity I am afraid) for a way to manage singletons/globals. Here is another option, but more complicated. Move the database object creation into it's own factory method within your list thingy. Then you can subclass your main class to use a different connection object, such as a logging or mock version. It means that the class under test is slightly modified, but only for one method. The partial mocks in SimpleTest automate this somewhat, but I guess one pattern at a time. > I guess in an ideal world, you can just > test the public methods as a black box, but Mocks end up being the best > compromise in order to disconnect from the database and just test the remaining > logic. For hardcore testing all interfaces with the outside world (database, templates, network) should be replaceable. But then, I never get to the "ideal" world either so I can't talk. > > Thanks for putting up with my floundering around. > You seem to be doing extemely well. > Jason yours, Marcus -- Marcus Baker, ma...@la..., no...@ap... |
|
From: Jason S. <jsw...@ya...> - 2003-10-29 12:59:13
|
This project that I am working on is a DataCache. I wanted to be able to implement it either using the file system or using a database, so I decided to make a DAO to support either of these options, and have a DataCache class that decides which of the DAOs to use. When constructing a test for this kind of a system, I assume it is fine to write unit test cases for each of the DAOs, however when it comes to the DataCache class itself, is it better to test the DAOs first, then the DataCache as written, or is it better to mock the DAOs and test the outer class by itself? Any guidlines or rules of thumb that you use in these cases? Thanks. Jason __________________________________ Do you Yahoo!? Exclusive Video Premiere - Britney Spears http://launch.yahoo.com/promos/britneyspears/ |
|
From: Jason S. <jsw...@ya...> - 2003-10-29 13:35:56
|
One more thought I would like to have clarified on this subject: You should never test private methods, only the public API for an object. That way you are free to refactor the private methods without altering your test case. Or am I mistaken? Thanks, Jason --- Jason Sweat <jsw...@ya...> wrote: > This project that I am working on is a DataCache. I wanted to be able to > implement it either using the file system or using a database, so I decided > to > make a DAO to support either of these options, and have a DataCache class > that > decides which of the DAOs to use. > > When constructing a test for this kind of a system, I assume it is fine to > write unit test cases for each of the DAOs, however when it comes to the > DataCache class itself, is it better to test the DAOs first, then the > DataCache > as written, or is it better to mock the DAOs and test the outer class by > itself? > > Any guidlines or rules of thumb that you use in these cases? > > Thanks. > > Jason > > __________________________________ > Do you Yahoo!? > Exclusive Video Premiere - Britney Spears > http://launch.yahoo.com/promos/britneyspears/ > > > ------------------------------------------------------- > This SF.net email is sponsored by: SF.net Giveback Program. > Does SourceForge.net help you be more productive? Does it > help you create better code? SHARE THE LOVE, and help us help > YOU! Click Here: http://sourceforge.net/donate/ > _______________________________________________ > Simpletest-support mailing list > Sim...@li... > https://lists.sourceforge.net/lists/listinfo/simpletest-support __________________________________ Do you Yahoo!? Exclusive Video Premiere - Britney Spears http://launch.yahoo.com/promos/britneyspears/ |
|
From: Marcus B. <ma...@la...> - 2003-10-29 14:22:50
|
Hi... Jason Sweat wrote: > One more thought I would like to have clarified on this subject: > You should never test private methods, only the public API for an object. That > way you are free to refactor the private methods without altering your test > case. Good question and a hot topic of debate. When you have finished a module I would say definitely leave out the private method tests. The same with protected ones unless you are specifically testing a subclass. When I am actually coding and am getting confused, I will write quick private method tests, just to see what is going on. > Jason yours, Marcus -- Marcus Baker, ma...@la..., no...@ap... |
|
From: Marcus B. <ma...@la...> - 2003-10-29 14:20:26
|
Hi... Jason Sweat wrote: > When constructing a test for this kind of a system, I assume it is fine to > write unit test cases for each of the DAOs, however when it comes to the > DataCache class itself, is it better to test the DAOs first, then the DataCache > as written, or is it better to mock the DAOs and test the outer class by > itself? > > Any guidlines or rules of thumb that you use in these cases? Whilst I am unsure of the design there are just no rules. Usually I will mock at least the database connection and the system configuration file simply for speed. I don't want to mess with real datbases when coding. This is a real hidden benefit of stubs/mocks. They can be a useful prototyping tool in themselves. Once things start to settle down I tend to start at the top level, mocking everything below. The mocks I am forced to create drive teh interfaces of the lowest level classes, effectively creating a spec. It is more iterative than this of course, but this is a common trick. I'll even use the partial mocks to stub out methods of a class I have not written yet. In short I will mock too much at the start (because it's quicker and I am lazy), gradually replacing any mocks that are really the job of the system I am testing and so should be part of the test. After this process I have a few classes with tests that do not overlap too much. At this point I start putting in the real end to end system tests. At this point a few glitches will show up, but it is rare that it takes more than an hour to get the real "live" tests working including any dummy data. Because these type of tests run rahter slower, I put them in their own test suite. The catch in this wonderful scheme is that I am extremely familiar with mock objects now, having used continually for over 18 months. If you haven't used them a great deal you probably won't have things so light and easy. > > Thanks. > > Jason If the individual DAOs are inherited from the superclass, I would create a particular DAO first, then another, then refactor the commonality (using just the other two test sets). Finally you can move the tests into the superclass test and partial mock those parts that were in the original subclass test. Slightly "by the book" refactoring wise, but safe. I'll try and show an example when I next get time. Perhaps I should start a mock/test refactoring pattern page on the lastcraft site? yours, Marcus -- Marcus Baker, ma...@la..., no...@ap... |
|
From: Jochen B. <jb...@bu...> - 2003-10-29 14:35:52
|
Marcus Baker wrote: > Perhaps I should start a mock/test refactoring pattern page on the > lastcraft site? I gather that this was a retorical question? ;-) I wish a productive day, Jochen (Bunny) |
|
From: Marcus B. <ma...@la...> - 2003-10-30 19:46:05
|
Hi... Jochen Buennagel wrote: >> Perhaps I should start a mock/test refactoring pattern page on the >> lastcraft site? > > > I gather that this was a retorical question? ;-) Sadly no. I can barely find time to keep to the release schedule, let alone produce more supporting material :(. > Jochen > (Bunny) yours, Marcus -- Marcus Baker, ma...@la..., no...@ap... |
|
From: <jb...@bu...> - 2003-10-30 19:53:33
|
Marcus Baker wrote: > Sadly no. I can barely find time to keep to the release schedule, let > alone produce more supporting material :(. Well, another site about patterns fared pretty well using a wiki, so maybe that could work? Jochen (Bunny) |