diff options
author | wei <> | 2006-07-14 06:56:16 +0000 |
---|---|---|
committer | wei <> | 2006-07-14 06:56:16 +0000 |
commit | 143980b6dab8ad87c44518e5b7befb614fb83b85 (patch) | |
tree | 6afadfb80ca6e4d36443f83bd5a6cc2cfe73032d /demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page | |
parent | c004bbdf4f0e824e5ccbaef8f98ca4a3d44d3b49 (diff) |
Add time-tracker sample and docs. (Incomplete)
Diffstat (limited to 'demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page')
-rw-r--r-- | demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page b/demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page new file mode 100644 index 00000000..f85e00be --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page @@ -0,0 +1,202 @@ +<com:TContent ID="body">
+<h1>More complete ProjectTestCase</h1>
+<p>
+In creating a new project, we need to check the following:
+<ul>
+ <li>that the project does not already exists, i.e. no duplicate project name</li>
+ <li>that the project creator and project manager exists when a new project is created</li>
+</ul>
+So to perform this task, we will modified the test code.
+<com:TTextHighlighter Language="php" CssClass="source">
+function testProjectDaoCanCreateNewProject()
+{
+ $project = new Project();
+ $project->ID = 1;
+ $project->Name = "Project 1";
+ $project->CreatorUserName = "Customer A";
+ $project->ManagerUserName = "Manager A";
+
+ $customer = new TimeTrackerUser();
+ $customer->ID = 1;
+ $customer->Name = "Customer A";
+
+ $manager = new TimeTrackerUser();
+ $manager->ID = 2;
+ $manager->Name = "Manager A";
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //return the customer and manager
+ $conn->setReturnValue('queryForObject',
+ $customer, array('GetUserByName', 'Customer A'));
+ $conn->setReturnValue('queryForObject',
+ $manager, array('GetUserByName', 'Manager A'));
+
+ //project does not exist
+ $conn->setReturnValue('queryForObject',
+ null, array('GetProjectByName', 'Project 1'));
+
+ $param['project'] = $project;
+ $param['creator'] = $customer->ID;
+ $param['manager'] = $manager->ID;
+
+ $conn->setReturnValue('insert', true,
+ array('CreateNewProject', $param));
+ $conn->setReturnReference('queryForObject',
+ $project, array('GetProjectByID', 1));
+
+ //we expect queryForObject to be called 3 times
+ $conn->expectMinimumCallCount('queryForObject', 3);
+ $conn->expectAtLeastOnce('insert');
+ }
+
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+}
+</com:TTextHighlighter>
+</p>
+<p>It may seem very weird that there is so much code in the tests
+and why we even bother to write all these test codes. Well, using the
+above test code we have the following advantages.</p>
+<div class="info"><b class="tip">Advantages of Mock</b>
+<ol>
+ <li>we don't need a real database base connection to test the code,
+ this means we can start relying on tested code ealier</li>
+ <li>when a test fails we know that problem is not part of the database</li>
+ <li>when a test fail, we can quickly pin point the problem</li>
+ <li>the test suite gives us the confidence to refactor our code</li>
+</ol>
+</div>
+
+<p>Of couse, the test will not be able to cover the higher interactions, such as
+the user interface, so intergration or functional web test will be used later on.
+</p>
+
+<p>So how did we come up with the above tests? We started simple, then we
+ask what sort of things it should handle. We assume that the connection object
+work as expect or known to be unexpected and see how the method we want to test handle
+these situations.</p>
+
+<p>If we run the above test, we will be faced with numerous errors. First will be
+that the <tt>TimeTrackerUser</tt> can not be found.</p>
+
+<h1>Creating a new User Class</h1>
+<p>Notice that the <tt>Project</tt> class contains <tt>CreatorUserName</tt>
+and <tt>ManagerUserName</tt> properties. So at some point we
+are going to need at least one <tt>User</tt> class. We shall name the class as
+<tt>TimeTrackerUser</tt> and save it as <tt>APP_CODE/TimeTrackerUser.php</tt>
+<com:TTextHighlighter Language="php" CssClass="source">
+<?php
+Prado::using('System.Security.TUser');
+Prado::using('System.Security.TUserManager');
+class TimeTrackerUser extends TUser
+{
+ private $_ID;
+
+ public function __construct()
+ {
+ parent::__construct(new TUserManager());
+ }
+
+ public function getID(){ return $this->_ID; }
+
+ public function setID($value)
+ {
+ if(is_null($this->_ID))
+ $this->_ID = $value;
+ else
+ throw new TimeTrackerUserException(
+ 'timetracker_user_readonly_id');
+ }
+}
+?>
+</com:TTextHighlighter>
+
+<h1>Custom Exceptions</h1>
+<p>We enforce that the ID of the user to be read-only once it has been
+set by throwing a custom exception. Prado's exception classes
+uses a string key to find a localized exception string containing more
+detailed description of the exception. The default exception messages
+are stored in the <tt>framework/Exceptions/messages.txt</tt>. This
+file location can be changed by overriding the <tt>getErrorMessageFile()</tt>
+method of <tt>TException</tt> class. We define a custom exception class
+for all Time Tracker application exceptions as <tt>TimeTrackerException</tt>
+and save the class as <tt>APP_CODE/TimeTrackerException.php</tt>.</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+<?php
+class TimeTrackerException extends TException
+{
+ /**
+ * @return string path to the error message file
+ */
+ protected function getErrorMessageFile()
+ {
+ return dirname(__FILE__).'/exceptions.txt';
+ }
+}
+?>
+</com:TTextHighlighter>
+
+<p>We then create a <tt>exceptions.txt</tt> file in the <tt>APP_CODE</tt>
+directory with the following content.</p>
+
+<com:TTextHighlighter Language="text" CssClass="source">
+timetracker_user_readonly_id = Time tracker user ID is read-only.
+</com:TTextHighlighter>
+
+<p>Additional parameters passed in the exception constructor can be
+added the message string using <tt>{0}</tt> as the first additional parameter,
+and <tt>{1}</tt> as the second additional parameter, and so on.
+For example, suppose we want to raise the follow exception.
+</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+throw new TimeTrackerException('project_exists', $projectName);
+</com:TTextHighlighter>
+
+<p>The exception error message in <tt>exceptions.txt</tt> may contain something like:</p>
+<com:TTextHighlighter Language="text" CssClass="source">
+project_exists = Time tracker project '{0}' already exists.
+</com:TTextHighlighter>
+
+<h1>Completing the test case</h1>
+<p>From the unit test code, we can pretty much see what the implementation
+for <tt>createNewProject()</tt> will look like.</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+public function createNewProject($project)
+{
+ $sqlmap = $this->getConnection();
+ $creator = $sqlmap->queryForObject('GetUserByName', $project->CreatorUserName);
+ $manager = $sqlmap->queryForObject('GetUserByName', $project->ManagerUserName);
+ $exists = $sqlmap->queryForObject('GetProjectByName', $project->Name);
+ if($exists)
+ {
+ throw new TimeTrackerException(
+ 'project_exists', $project->Name);
+ }
+ else if(!$creator || !$manager)
+ {
+ throw new TimeTrackerException(
+ 'invalid_creator_and_manager',
+ $project->Name, $project->CreatorUserName,
+ $project->ManagerUserName);
+ }
+ else
+ {
+ $param['project'] = $project;
+ $param['creator'] = $creator->ID;
+ $param['manager'] = $manager->ID;
+ return $sqlmap->insert('CreateNewProject', $param);
+ }
+}
+</com:TTextHighlighter>
+
+<div class="tip"><b class="tip">Tip:</b>
+A hierachy of exception class can be used to have fine exception handling.
+Since this is a small project and for simplicity, we shall use the application level
+<tt>TimeTrackerException</tt> exception class for most exception cases.
+</div>
+
+</com:TContent>
\ No newline at end of file |