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 | |
parent | c004bbdf4f0e824e5ccbaef8f98ca4a3d44d3b49 (diff) |
Add time-tracker sample and docs. (Incomplete)
Diffstat (limited to 'demos/time-tracker/protected/pages/Docs')
18 files changed, 959 insertions, 0 deletions
diff --git a/demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page b/demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page new file mode 100644 index 00000000..e5afa572 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page @@ -0,0 +1,127 @@ +<com:TContent ID="body">
+<h1>Create Business Code</h1>
+<p>We start the design with the database, the entity relationships are shown
+in the diagram below.</p>
+
+<img src=<%~ db.png %> class="figure" />
+
+<p>Now we can begin to create and test some business code. Let us begin
+with the <tt>Project</tt> defintions. First, we add some properties or fields.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+<?php
+class Project
+{
+ public $ActualDuration = 0;
+ public $CreatorUserName = '';
+ public $CompletionDate = 0;
+ public $DateCreated = 0;
+ public $Description = '';
+ public $EstimateDuration = 0;
+ public $ID = 0;
+ public $ManagerUserName = '';
+ public $Name = '';
+}
+?>
+</com:TTextHighlighter>
+
+<p>All the fields should be self explainatory. The <tt>ManagerUserName</tt>
+is the user name of the project manager. Notice that the fields
+are public, later on, we can change some or all of them to be private and
+provide some accessor and mutators (i.e. getters and setters). If we want
+we can let the <tt>Project</tt> class inherit <tt>TComponent</tt> such
+that the getters and setters can be used like properties, such as those
+found in Prado.</p>
+
+<h2>Business Services, test case first</h2>
+<p>Next we want to add users to the project. For this, we start with some
+unit tests. We are going to design the business logic around the concept of
+<a href="java">Data Access Objects</a> (DAO).</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+<?php
+Prado::using('Application.APP_CODE.*');
+class ProjectDaoTestCase extends UnitTestCase
+{
+ function testProjectDaoCanCreateNewProject()
+ {
+ $project = new Project();
+ $project->Name = "Project 1";
+
+ $dao = new ProjectDao();
+
+ $this->assertTrue($dao->createNewProject($project));
+ $this->assertEqual($dao->getProjectByID(1), $project);
+ }
+}
+?>
+</com:TTextHighlighter>
+<p>So what are we doing here? First we create a new <tt>Project</tt> named
+"Project 1". Then we create a new <tt>ProjectDao</tt> so we can insert new projects
+and retrieve it. We assert that a project will be create sucessfully using
+<tt>assertTrue($do->createNewProject(...))</tt>. We also assert that
+<tt>getProjectByID(1)</tt> will return an instance of <tt>Project</tt> class
+with same data (the reference may be different).</p>
+
+<p>If we run the above unit test case, nothing is going to pass since we have
+not even defined the <tt>ProjectDao</tt> class. So lets do that first and import
+the class in the tests as well.</p>
+
+<p>We will create a base Dao class as follows, and we save as <tt>BaseDao.php</tt>
+in our <tt>APP_CODE</tt> directory.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+<?php
+class BaseDao
+{
+ private $_connection;
+
+ public function setConnection($connection)
+ {
+ $this->_connection = $connection;
+ }
+
+ protected function getConnection()
+ {
+ return $this->_connection;
+ }
+}
+?>
+</com:TTextHighlighter>
+<p>And finally our <tt>ProjectDao</tt> class.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+<?php
+Prado::using('Application.APP_CODE.BaseDao');
+class ProjectDao extends BaseDao
+{
+}
+?>
+</com:TTextHighlighter>
+<p>If we run the unit test again, we get an error message something like
+"<i>Fatal error: Call to undefined method ProjectDao::createNewProject() in ...</i>".
+So let us, fix this.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+class ProjectDao extends BaseDao
+{
+ public function createNewProject($project)
+ {
+ }
+
+ public function getProjectByID($projectID)
+ {
+ }
+}
+</com:TTextHighlighter>
+<p>Run the unit test again, we see a different type of error now. This time
+the unit test runner complians that the test fails.</p>
+<h2>Write Test, Run Test, Repeat</h2>
+<div class="info">
+<b>At this point, you may notice a repetition</b>
+<ol>
+ <li>write some test code,</li>
+ <li>run the test, test gives an error,</li>
+ <li>write some actual code to only remove the previous error,</li>
+ <li>goto step 1 to repeat.</li>
+</ol>
+</div>
+<p>We shall see how we can make this test pass in the next section.</p>
+</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/GettingStarted.page b/demos/time-tracker/protected/pages/Docs/GettingStarted.page new file mode 100644 index 00000000..8b8e8e5e --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/GettingStarted.page @@ -0,0 +1,67 @@ +<com:TContent ID="body"> +<h1>Installation</h1> +<p>There are a few pieces of tools and software needed throughout the development +of the application. First we shall setup the environment for coding and testing. We +shall from here on assume that you have access to a working installation of a web server with +PHP and possibilly a MySQL database server. The first thing to do is install Prado and some +testing tools.</p> + +<h2>Download and Install Prado</h2> +<p>The minimum requirement by PRADO is that the Web server support PHP 5. + For the Time Tracker sample application, you need Prado version 3.1 or greater.</p> +<p>Installation of PRADO mainly involves downloading and unpacking.</p> +<ol> + <li>Go to <a href="http://www.pradosoft.com/download/">pradosoft.com</a> to grab the latest version of PRADO.</li> + <li>Unpack the PRADO release file to a Web-accessible directory.</li> +</ol> + +<p>You should at least first check that the demos bundled with the Prado distribution are + working. See the <a href="../quickstart/index.php?page=GettingStarted.Installation"> + quickstart tutorial for further instructions</a> on running the demos. +</p> + +<h2>Help! Nothing is working!</h2> +<p>If you encounter problems in downloading, unpacking and running the demo applications, please + visit the <a href="http://www.pradosoft.com/forum/">forum to seek help</a>. Please + do a search on the forum first or <a href="http://www.pradosoft.com/demos/quickstart/prado3_quick_start.pdf">read the PDF version</a> of the quickstart. + The friendly and wonderful people at the forum can better assist you if you can provide as much detail regarding your problem. + You should include in your post your server configuration details, the steps you took to reproduce your problem, and any error messages encountered.</p> + +<h2>Create a new Prado application</h2> +<p>Prado is bundled with a command line tool to create the necessary directory structure to run a hello world application. +The command tool <tt>prado-cli.php</tt> is a php script and can be found in the <tt>prado/framework/</tt> directory. +To create a new application, go to your document root and type the following command in console. +</p> +<com:TTextHighlighter Language="none" CssClass="source"> +php prado/framework/prado-cli.php -t -c time-tracker +</com:TTextHighlighter> + +<div class="tip"><b class="tip">Tip:</b> +For linux and OS X environments, you can <tt>chmod u+x prado-cli.php</tt> +and change the first line of <tt>prado-cli.php</tt> to the location of your +PHP command line interpreter. Now, you may call the script from command line +like an executable. +</div> + +<p>A directory named <tt>time-tracker</tt> will be created containing the following. +<com:TTextHighlighter Language="none" CssClass="source"> +document_root/time-tracker/assets/ +document_root/time-tracker/index.php +document_root/time-tracker/protected/ +document_root/time-tracker/protected/.htaccess +document_root/time-tracker/protected/pages/ +document_root/time-tracker/protected/pages/Home.page +document_root/time-tracker/protected/runtime/ +</com:TTextHighlighter> +<p>The <tt>time-tracker</tt> directory is where we are going to put all our code for the Time Tracker application. +Since <tt>-t</tt> was passed into the <tt>prado-cli.php</tt> script, unit and functional test + skeletons are also created.</p> +<com:TTextHighlighter Language="none" CssClass="source"> +document_root/time-tracker/tests/ +document_root/time-tracker/tests/unit/ +document_root/time-tracker/tests/functional/ +document_root/time-tracker/tests/unit.php +document_root/time-tracker/tests/functional.php +</com:TTextHighlighter> + +</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/Home.page b/demos/time-tracker/protected/pages/Docs/Home.page new file mode 100644 index 00000000..5c7a486b --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/Home.page @@ -0,0 +1,10 @@ +<com:TContent ID="body"> +<h1>Prado Time Tracker Implementation Guide</h1> +<ul> + <li><a href="?page=Introduction">Introduction</a></li> + <li><a href="?page=GettingStarted">Getting Started</a></li> + <li><a href="?page=WritingUnitTest">Writing a Unit Test</a></li> + <li><a href="?page=WritingFunctionalTest">Writing a Functional Web Test</a></li> + <li><a href="?page=CreateBusinessCode ">Create Business Code</a></li> +</ul> +</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/Introduction.page b/demos/time-tracker/protected/pages/Docs/Introduction.page new file mode 100644 index 00000000..baa305f5 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/Introduction.page @@ -0,0 +1,64 @@ +<com:TContent ID="body"> +<h1>Introduction</h1> + +<p>The Time Tracker is a fully functional sample application designed to + introduce you to many of Prado's features. This guide is a step by step + walkthrough starting from installation to deployment. The Time Tracker + application is based on the + <a href="http://www.asp.net/downloads/starterkits/">ASP.NET's Time Tracker Starter Kit</a>. +</p> + +<h1>Time Tracker Overview</h1> +<p>The Time Tracker is a business web application for keeping track of hours spent +on a project, with ability to handle multiple resources as well as multiple projects.</p> +<h2>Basic Application Requirements</h2> +<p>The functional requirements of the Time Tracker is based on the + <a href="http://www.asp.net/downloads/starterkits/">ASP.NET's Time Tracker Starter Kit</a> + description.</p> +<p class="requirements"> +<h3>Create projects</h3> +<ul> + <li>Define projection information like due dates, hours to complete, project + resources, and more.</li> + <li>Break down projects into tasks and track work on per-task basis.</li> +</ul> +<h3>Create and track tasks</h3> +<ul> + <li>Track time spent each day by category and project.</li> +</ul> +<h3>Use reports to track progress</h3> +<ul> + <li>Track overall progress across multiple projects, including estimated and actual work.</li> + <li>Track total work for team resources across multiple projects.</li> +</ul> +</p> + +<h2>Technologies and Design Approached Demonstrated</h2> +<ul> + <li>Prado event-driven component framework</li> + <li>Separation of concerns: persistent storage, business logic, presentation</li> + <li>Object-Relational mapping using SQLMap</li> + <li>Unit testing and functional testing</li> + <li>Promote code reuse</li> +</ul> + +<h1>Requirements</h1> +<p>The Time Tracker web application requires the following software and knowledge.</p> +<h2>Software requirements</h2> +<p>It is assumed that you are able to obtain and install the following pieces of software.</p> +<ol> + <li>PHP version 5.0.4 or greater</li> + <li>A web server, such as Apache, able to run PHP scripts</li> + <li>MySQL database server version 4.1 or greater (alternatively SQLite or Postgres)</li> + <li>Prado version 3.1 or later</li> +</ol> + +<p>In addition to software requirements, we assumed that you have some of the following knowledge.</p> +<ul> + <li>Access the internet to download packaged code, and unzip these code.</li> + <li>Understand the basic concepts of Object-Oriented programming in PHP such as + classes, objects, methods, etc.</li> + <li>Basic understanding of relational databases and Structured Query Language (SQL).</li> + <li>Knows how to have fun, this is mandatory.</li> +</ul> +</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/MoreTests.page b/demos/time-tracker/protected/pages/Docs/MoreTests.page new file mode 100644 index 00000000..5e598982 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/MoreTests.page @@ -0,0 +1,140 @@ +<com:TContent ID="body">
+
+<h1>Finishing up test case</h1>
+<p>Up to this point, you should have the follow pieces of files and code.
+<img src=<%~ project1.png %> class="figure" />
+</p>
+
+<p>So what else can we test for at this point? A few reasonable
+tests are to see what happens if the project already exists, and what if
+the username does not exists.</p>
+
+<p>First we shall refactor our test code since much of the setup code
+for the mocked connection will need to be repeated in other test assertions.
+We change the test case to have these three new methods.
+</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+function setupMockConnectionFor($project)
+{
+ $customer = new TimeTrackerUser();
+ $customer->ID = 1;
+ $customer->Name = "Customer A";
+
+ $manager = new TimeTrackerUser();
+ $manager->ID = 2;
+ $manager->Name = "Manager A";
+
+ $conn = $this->connection;
+
+ //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));
+}
+
+function createNewTestProject()
+{
+ $project = new Project();
+ $project->Name = "Project 1";
+ $project->CreatorUserName = "Customer A";
+ $project->ManagerUserName = "Manager A";
+
+ return $project;
+}
+
+function assertProjectCreated($project)
+{
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+}
+</com:TTextHighlighter>
+
+<p>Our refactored test method <tt>testProjectDaoCanCreateNewProject()</tt>
+is as follows.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+function testProjectDaoCanCreateNewProject()
+{
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $this->setupMockConnectionFor($project);
+ $conn->expectMinimumCallCount('queryForObject', 3);
+ $conn->expectAtLeastOnce('insert');
+ }
+
+ $this->assertProjectCreated($project);
+}
+</com:TTextHighlighter>
+
+<p>To test that the project already exists, we modify the mock
+connection and test for an exception.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+function testProjectExistsException()
+{
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //make the project exist
+ $conn->setReturnValue('queryForObject',
+ $project, array('GetProjectByName', 'Project 1'));
+ $this->setupMockConnectionFor($project);
+ }
+
+ try
+ {
+ $this->assertProjectCreated($project);
+ $this->fail();
+ }
+ catch(TimeTrackerException $e)
+ {
+ $this->pass();
+ }
+}
+</com:TTextHighlighter>
+
+<p>Other test method for testing missing customer and manager users
+are done similarly. At this point, the test case file looks quite large.
+We shall not add more tests to this case and we should rename the file
+from <tt>ProjectDaoTestCase.php</tt> to <tt>CreateNewProjectTestCase.php</tt>.
+
+<div class="note"><b class="tip">Note:</b>
+A heirachical exception class may be more useful in testing for specific
+exceptions. For simplicity, we decided to use only <tt>TimeTrackerException</tt>
+for this small project.
+</div>
+
+<div class="tip"><b class="tip">Tip:</b>Class, method and file naming is very
+important to any project, the name should inform you at first glance what
+is to be expected from the class, method or file. In addition, the naming
+scheme should be uniform over the project.
+</div>
+
+<div class="info"><b class="tip">Comment:</b>
+These test may be too ridget as any changes to the implementation may
+actually cause the tests to fail. This is the case with grey-box/white-box
+testing, you are actually testing the implementation. Black box tests may
+be more preferable, as it should only test the class interface (the method or
+function details such as parameters and may be return values). However,
+with the use of database connection to retrive data within the objects under test,
+it may be more suitable to do intergration tests.
+</div>
+
+</com:TContent>
+
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 diff --git a/demos/time-tracker/protected/pages/Docs/UsingSQLMap.page b/demos/time-tracker/protected/pages/Docs/UsingSQLMap.page new file mode 100644 index 00000000..cc2abf4f --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/UsingSQLMap.page @@ -0,0 +1,210 @@ +<com:TContent ID="body">
+<h1>Using SQLMap Data Mapper</h1>
+<p>Before proceeding with write the code to pass the test in the previous section, we shall
+make a design decision. We shall use SQLMap as our data access layer. Note
+that <b>SQLMap is only available</b> offically in <b>Prado 3.1</b> or later.</p>
+
+<p>SQLMap is an PHP implemenation of an Object/Relational
+data mapper. SQLMap has the following basic features:
+<ol>
+ <li>collects all the Structured Query Language
+(SQL) statements in an external XML file</li>
+ <li>Maps data return from database queries into PHP objects</li>
+ <li>Takes PHP objects as parameters in SQL queries</li>
+</ol>
+<p>SQLMap can be seen as a generic data mapper, rather than an
+Object Relational Mapping (ORM) solution.</p>
+
+<p>The SQLMap API consists of the following methods. See the <a href="sqlmap">SQLMap manual</a>
+for further details.</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+/* Query API */
+public function queryForObject($statementName, $parameter=null, $result=null);
+public function queryForList($statementName, $parameter=null, $result=null,
+ $skip=-1, $max=-1);
+public function queryForPagedList($statementName, $parameter=null, $pageSize=10);
+public function queryForMap($statementName, $parameter=null,
+ $keyProperty=null, $valueProperty=null);
+public function insert($statementName, $parameter=null)
+public function update($statementName, $parameter=null)
+public function delete($statementName, $parameter=null)
+
+/* Connection API */
+public function openConnection()
+public function closeConnection()
+
+/* Transaction API */
+public function beginTransaction()
+public function commitTransaction()
+public function rollBackTransaction()
+</com:TTextHighlighter>
+
+<h2>Fetch and Inserting Data Using SQLMap</h2>
+<p>Back to our <tt>ProjectDao</tt> class, for testing we shall write the code
+assuming we have an SQLMap client instance. The SQLMap client or connection
+will be mocked or faked using a proxy. Later on, we shall extend our tests,
+to become an intergration test, to include
+a test database connection.</p>
+
+<h2>Creating a SQLMap connection</h2>
+<p>We can test the <tt>ProjectDao</tt> using mock objects (see SimpleTest documentation
+regarding mock objects) or using a real database connection. In PHP version
+5.0.x, the sqlite database client is bundled by default. For version 5.1 or later
+the sqlite database client is available as an extension. We shall use a sqlite
+database to conduct our unit tests because the database can be restored to
+the orginal state by just reloading the orginal database file.</p>
+
+<div class="info"><b class="tip">Comment:</b>
+Unit tests using mocked objects may be too ridget as we need to mock
+the connection object and thus the test is of the grey-box/white-box variaity.
+That is, you are actually testing the implementation of the object under test
+consideration. Thus, any changes to the implementation of the objects
+under tests may actually cause the tests to fail. Black box tests may
+be more preferable, as it should only test the class interface (the method or
+function details such as parameters and may be return values). A further
+disadvantage when using mocks for complex database connection is the complexity
+required in setting the mocks for testing.
+</div>
+
+<p>First, let us define the database table for projects.</p>
+<com:TTextHighlighter Language="sql" CssClass="source">
+Field Type Null
+----------------------------------------
+ProjectID INTEGER Yes
+Name VARCHAR(255) No
+Description VARCHAR(255) No
+CreationDate INT No
+Disabled INT(1) No
+EstimateDuration INT No
+CompletionDate INT No
+CreatorID INTEGER No
+ManagerID INTEGER Yes
+</com:TTextHighlighter>
+
+<p>The corresponding SQLite query to create the table is given below.</p>
+
+<com:TTextHighlighter Language="sql" CssClass="source">
+CREATE TABLE projects (
+ ProjectID INTEGER PRIMARY KEY,
+ Name VARCHAR(255) NOT NULL,
+ Description VARCHAR(255) NOT NULL,
+ CreationDate INT NOT NULL,
+ Disabled INT(1) NOT NULL,
+ EstimateDuration INT NOT NULL,
+ CompletionDate INT NOT NULL,
+ CreatorID INTEGER NOT NULL,
+ ManagerID INTEGER
+);
+CREATE UNIQUE INDEX projects_Name ON projects(Name);
+CREATE INDEX project_name_index ON projects(Name);
+</com:TTextHighlighter>
+
+<h2>Testing with Mocked Database Connection</h2>
+<p>At this point, we have said nothing about databases. To create some unit tests
+for the <tt>ProjectDao</tt> class, we are not going to use real database connections
+but using a Mocked <tt>TSqlMapper</tt>. We modifiy the test as follows.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+Prado::using('Application.APP_CODE.*');
+Prado::using('System.DataAccess.SQLMap.TSqlMapper');
+
+Mock::generate('TSqlMapper');
+
+class ProjectDaoTestCase extends UnitTestCase
+{
+ protected $dao;
+ protected $connection;
+
+ function setup()
+ {
+ $this->dao= new ProjectDao();
+ $this->connection = new MockTSqlMapper($this);
+ $this->dao->setConnection($this->connection);
+ }
+
+ function testProjectDaoCanCreateNewProject()
+ {
+ $project = new Project();
+ $project->Name = "Project 1";
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $conn->expectOnce('insert', array('CreateNewProject', $project));
+ $conn->setReturnValue('insert', true);
+
+ $conn->expectOnce('queryForObject', array('GetProjectByID', 1));
+ $conn->setReturnReference('queryForObject', $project);
+ }
+
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+ }
+}
+</com:TTextHighlighter>
+<p>The test code looks slight more complicated because later on we want to
+test the same assertions against some real data.</p>
+<p>
+In the first two lines above, we simply import the business code and the <tt>TSqlMapper</tt> class.
+We generate a <tt>MockTSqlMapper</tt>, a mock class or <tt>TSqlMapper</tt> using
+<tt>Mock::generate('TSqlMapper')</tt>. The method <tt>Mock::generate()</tt> is available from
+the SimpleTest unit testing framework. An instance of <tt>MockTSqlMapper</tt>
+will be used as our test connection for the DAO objects. This allows us to interogate the
+internal workings of our <tt>ProjectDao</tt> class.
+</p>
+
+<p>In the <tt>setup()</tt> (this method is called before every test method), we create an instance of
+<tt>ProjectDao</tt> and set the connection to an instance of <tt>MockTSqlMapper</tt>.
+</p>
+
+<h2>Testing internal workings of ProjectDao</h2>
+
+<p>So how do we test the internal workings of the ProjectDao <tt>createNewProject</tt> method?
+First, we assume that the connection object will perform the correct database insertion and queries.
+Then, we set the return value of the MockTSqlMapper instance to return what we have assumed.
+In addition, we expect that the TSqlMapper method <tt>queryForObject</tt> is called only once with
+the parameters we have assumed (e.g. <tt>$project</tt>). See the SimpleTest tutorial for further details regarding
+unit testing with Mocks.
+</p>
+
+<p>In our assertions and expectations, we have
+<com:TTextHighlighter Language="php" CssClass="source">
+$conn->expectOnce('insert', array('CreateNewProject', $project));
+$conn->setReturnValue('insert', true);
+
+$this->assertTrue($this->dao->createNewProject($project));
+</com:TTextHighlighter>
+This means that, we expect the <tt>createNewProject</tt> method in <tt>ProjectDao</tt>
+to call the TSqlMapper method <tt>insert</tt> with parameter <tt>$project</tt> only once.
+In addition, we <b>assume</b> that the returned value from the <tt>insert</tt> method of TSqlMapper
+returns <tt>true</tt>. Finally, we test the <tt>createNewProject</tt> method with an assertion.
+</p>
+
+<p>We now run the unit tests, we see that there are some failures or errors. Since we
+have not created any code in <tt>ProjectDao</tt> that performs what we want, the tests will fail.
+So lets make these test pass, we add some code to <tt>ProjectDao</tt> class.
+
+<com:TTextHighlighter Language="php" CssClass="source">
+class ProjectDao extends BaseDao
+{
+ public function createNewProject($project)
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->insert('CreateNewProject', $project);
+ }
+
+ public function getProjectByID($projectID)
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->queryForObject('GetProjectByID', $projectID);
+ }
+}
+</com:TTextHighlighter>
+</p>
+
+<p>If we run the unit tests again, we should see a green bar indicating that the tests have passed.
+We can now proceed further and add more tests. Of course, the above test does not cover many
+conditions, such as, <i>what happens if the project already exists?</i>, the details of these
+tests is the subject of the next section. The full test suite can be found in the source.
+</p>
+</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page b/demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page new file mode 100644 index 00000000..b4da0952 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page @@ -0,0 +1,39 @@ +<com:TContent ID="body"> +<h1>Writing a Functional Web Test</h1> +<p>In addition to unit testing, we shall also do some functional +testing or web testing. Functional tests are, in this case, basically automated tests that will +interact with the overall web application, as if it was the user, while checking the output for correctness. +The functional test tool we shall use here is based on <a href="http://selenium.openqa.org/">Selenium</a> where the test cases can be written and run using PHP and SimpleTest. +</p> +<com:TTextHighlighter> +<?php +class HelloPradoTestCase extends SeleniumTestCase +{ + function test() + { + $this->open('../index.php'); + $this->assertTextPresent('Welcome to Prado!'); + } +} +?> +</com:TTextHighlighter> +<p>Save the code as <tt>HelloPradoTestCase.php</tt> in the <tt>document_root/time-tracker/tests/functional/</tt> +directory.</p> + +<p> +Functional test cases are written very similar to unit test cases. The method such as +<tt>open($url)</tt> are those found in Selenium. All the methods available in Selenium are available. +</p> + +<h2>Run your first unit test case from your browser</h2> +<p>Point your browser to your development server's unit test case runner, e.g. + <tt>http://web-server-address/time-tracker/tests/functional.php</tt>. You should see the following + <img src="<%~ functional_test1.png %>" class="figure"/> + <div class="caption"><b>Figure 4:</b> Functional test runner</div> +</p> +<p>Clicking on the <b>All</b> button, you should see + <img src="<%~ functional_test2.png %>" class="figure"/> + <div class="caption"><b>Figure 5:</b> Functional test success</div> +</p> + +</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page b/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page new file mode 100644 index 00000000..32c7bc79 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page @@ -0,0 +1,73 @@ +<com:TContent ID="body"> +<h1>Writing a Unit Test</h1> +<p>Unit testing is a useful tool when we want to start to test + our individual business logic classes. + The <tt>tests/unit</tt> directory will be used to hold the unit test cases and <tt>tests/functional</tt> directory +to hold the function test cases. +</p> + +<h2>Write a unit test case</h2> +<p>We will start be writing a very simple unit test case.</p> +<com:TTextHighlighter Language="php" CssClass="source"> +<?php +class ProjectTestCase extends UnitTestCase +{ + function testProjectClassExists() + { + $project = new Project(); + $this->pass(); + } +} +?> +</com:TTextHighlighter> +<p>Save the code as <tt>ProjectTestCase.php</tt> in the <tt>document_root/time-tracker/tests/unit/</tt> +directory.</p> + +<h2>Run your first unit test case from your browser</h2> +<p>Point your browser to your development server's unit test case runner, e.g. + <tt>http://web-server-address/time-tracker/tests/unit.php</tt>. You should see the following +<img src="<%~ unit_test1.png %>" class="figure"/> +<div class="caption"><b>Figure 1:</b> Unit test runner</div> +</p> +<p>Clicking on the <tt>ProjectTestCase.php</tt> like, you should see +<img src="<%~ unit_test2.png %>" class="figure"/> +<div class="caption"><b>Figure 2:</b> Unit test failure</div> +</p> + +<h2>Smallest step to make the test pass.</h2> + +<p>Obviously, we need create the class <tt>Project</tt>, so lets define the class.</p> +<com:TTextHighlighter Language="php" CssClass="source"> +<?php +class Project +{ +} +?> +</com:TTextHighlighter> +<p>Save the above code as <tt>time-tracker/protected/pages/APP_CODE/Project.php</tt>. + Where the <tt>APP_CODE</tt> directory will contain most of your business logic code for the Time Tracker application.</p> +<p>We also need to add the following line in our test case so as to include the <tt>Project</tt> class file when running the tests.</p> + +<p class="note"> +The statement <tt>Prado::using('Application.APP_CODE.Project')</tt> basically +loads the <tt>Project.php</tt> class file. It assumes that a class name <tt>Project</tt> has filename <tt>Project.php</tt>. +For futher details regarding <tt>Prado::using</tt> can be found in <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Fundamentals.Components#704">Prado Namespaces</a> documentation. +</p> + +<com:TTextHighlighter Language="php" CssClass="source"> +<?php +Prado::using('Application.APP_CODE.Project'); +class ProjectTestCase extends UnitTestCase +{ + ... +} +?> +</com:TTextHighlighter> +<p>Run the unit test runner again, we see that the test has passed. + <img src="<%~ unit_test3.png %>" class="figure"/> + <div class="caption"><b>Figure 3:</b> Unit test success</div> +</p> +<p> +Later on, we shall write more test cases. See the SimpleTest documentation for detailed tutorial on writing test cases.</p> + +</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/config.xml b/demos/time-tracker/protected/pages/Docs/config.xml new file mode 100644 index 00000000..e0850c2c --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/config.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?>
+
+<configuration>
+ <paths>
+ <alias id="Pages" path="." />
+ </paths>
+ <pages MasterClass="Application.controls.Layout" Theme="PradoSoft" />
+</configuration>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/db.png b/demos/time-tracker/protected/pages/Docs/db.png Binary files differnew file mode 100644 index 00000000..f2209ef4 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/db.png diff --git a/demos/time-tracker/protected/pages/Docs/functional_test1.png b/demos/time-tracker/protected/pages/Docs/functional_test1.png Binary files differnew file mode 100644 index 00000000..33908734 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/functional_test1.png diff --git a/demos/time-tracker/protected/pages/Docs/functional_test2.png b/demos/time-tracker/protected/pages/Docs/functional_test2.png Binary files differnew file mode 100644 index 00000000..fb507e72 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/functional_test2.png diff --git a/demos/time-tracker/protected/pages/Docs/preface.page b/demos/time-tracker/protected/pages/Docs/preface.page new file mode 100644 index 00000000..fa0ba5c3 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/preface.page @@ -0,0 +1,19 @@ +<com:TContent ID="body">
+<h1>Prado Time Tracker</h1>
+<p>This documentation is complete walk-through guide detailing the
+steps involved in implementating of the Prado Time Tracker web application.
+</p>
+<h2>Target Audience</h2>
+<p>The guide is intended for readers who has some experience
+using PHP version 5 and some basic concepts of Object-Oriented application
+design. The guide will assume that the reader understands the primary concepts
+of class, methods, class inheritance, and other basic features offered by PHP
+version 5 or later.</p>
+<h2>Questions and Comments</h2>
+<p>Readers are encouraged to ask questions as the forum regarding any
+particular aspect of this documentation. Comment and constructive criticisms
+are most welcome. Questions and comments may be directed at
+<a href="http://www.pradosoft.com/forum/">http://www.pradosoft.com/forum/</a>.
+</p>
+
+</com:TContent>
\ No newline at end of file diff --git a/demos/time-tracker/protected/pages/Docs/project1.png b/demos/time-tracker/protected/pages/Docs/project1.png Binary files differnew file mode 100644 index 00000000..a250a943 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/project1.png diff --git a/demos/time-tracker/protected/pages/Docs/unit_test1.png b/demos/time-tracker/protected/pages/Docs/unit_test1.png Binary files differnew file mode 100644 index 00000000..66b62e19 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/unit_test1.png diff --git a/demos/time-tracker/protected/pages/Docs/unit_test2.png b/demos/time-tracker/protected/pages/Docs/unit_test2.png Binary files differnew file mode 100644 index 00000000..e33544d2 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/unit_test2.png diff --git a/demos/time-tracker/protected/pages/Docs/unit_test3.png b/demos/time-tracker/protected/pages/Docs/unit_test3.png Binary files differnew file mode 100644 index 00000000..bbc04551 --- /dev/null +++ b/demos/time-tracker/protected/pages/Docs/unit_test3.png |