From 143980b6dab8ad87c44518e5b7befb614fb83b85 Mon Sep 17 00:00:00 2001
From: wei <>
Date: Fri, 14 Jul 2006 06:56:16 +0000
Subject: Add time-tracker sample and docs. (Incomplete)
---
.../protected/pages/Docs/CreateBusinessCode.page | 127 +++++++++++++
.../protected/pages/Docs/GettingStarted.page | 67 +++++++
demos/time-tracker/protected/pages/Docs/Home.page | 10 +
.../protected/pages/Docs/Introduction.page | 64 +++++++
.../protected/pages/Docs/MoreTests.page | 140 ++++++++++++++
.../pages/Docs/UserClassAndExceptions.page | 202 ++++++++++++++++++++
.../protected/pages/Docs/UsingSQLMap.page | 210 +++++++++++++++++++++
.../pages/Docs/WritingFunctionalTest.page | 39 ++++
.../protected/pages/Docs/WritingUnitTest.page | 73 +++++++
demos/time-tracker/protected/pages/Docs/config.xml | 8 +
demos/time-tracker/protected/pages/Docs/db.png | Bin 0 -> 26879 bytes
.../protected/pages/Docs/functional_test1.png | Bin 0 -> 92502 bytes
.../protected/pages/Docs/functional_test2.png | Bin 0 -> 104726 bytes
.../time-tracker/protected/pages/Docs/preface.page | 19 ++
.../time-tracker/protected/pages/Docs/project1.png | Bin 0 -> 338441 bytes
.../protected/pages/Docs/unit_test1.png | Bin 0 -> 55855 bytes
.../protected/pages/Docs/unit_test2.png | Bin 0 -> 87439 bytes
.../protected/pages/Docs/unit_test3.png | Bin 0 -> 61923 bytes
18 files changed, 959 insertions(+)
create mode 100644 demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page
create mode 100644 demos/time-tracker/protected/pages/Docs/GettingStarted.page
create mode 100644 demos/time-tracker/protected/pages/Docs/Home.page
create mode 100644 demos/time-tracker/protected/pages/Docs/Introduction.page
create mode 100644 demos/time-tracker/protected/pages/Docs/MoreTests.page
create mode 100644 demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page
create mode 100644 demos/time-tracker/protected/pages/Docs/UsingSQLMap.page
create mode 100644 demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page
create mode 100644 demos/time-tracker/protected/pages/Docs/WritingUnitTest.page
create mode 100644 demos/time-tracker/protected/pages/Docs/config.xml
create mode 100644 demos/time-tracker/protected/pages/Docs/db.png
create mode 100644 demos/time-tracker/protected/pages/Docs/functional_test1.png
create mode 100644 demos/time-tracker/protected/pages/Docs/functional_test2.png
create mode 100644 demos/time-tracker/protected/pages/Docs/preface.page
create mode 100644 demos/time-tracker/protected/pages/Docs/project1.png
create mode 100644 demos/time-tracker/protected/pages/Docs/unit_test1.png
create mode 100644 demos/time-tracker/protected/pages/Docs/unit_test2.png
create mode 100644 demos/time-tracker/protected/pages/Docs/unit_test3.png
(limited to 'demos/time-tracker/protected/pages')
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 @@
+
+Create Business Code
+We start the design with the database, the entity relationships are shown
+in the diagram below.
+
+ class="figure" />
+
+Now we can begin to create and test some business code. Let us begin
+with the Project defintions. First, we add some properties or fields.
+
+<?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 = '';
+}
+?>
+
+
+All the fields should be self explainatory. The ManagerUserName
+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 Project class inherit TComponent such
+that the getters and setters can be used like properties, such as those
+found in Prado.
+
+Business Services, test case first
+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
+Data Access Objects (DAO).
+
+
+<?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);
+ }
+}
+?>
+
+So what are we doing here? First we create a new Project named
+"Project 1". Then we create a new ProjectDao so we can insert new projects
+and retrieve it. We assert that a project will be create sucessfully using
+assertTrue($do->createNewProject(...)). We also assert that
+getProjectByID(1) will return an instance of Project class
+with same data (the reference may be different).
+
+If we run the above unit test case, nothing is going to pass since we have
+not even defined the ProjectDao class. So lets do that first and import
+the class in the tests as well.
+
+We will create a base Dao class as follows, and we save as BaseDao.php
+in our APP_CODE directory.
+
+<?php
+class BaseDao
+{
+ private $_connection;
+
+ public function setConnection($connection)
+ {
+ $this->_connection = $connection;
+ }
+
+ protected function getConnection()
+ {
+ return $this->_connection;
+ }
+}
+?>
+
+And finally our ProjectDao class.
+
+<?php
+Prado::using('Application.APP_CODE.BaseDao');
+class ProjectDao extends BaseDao
+{
+}
+?>
+
+If we run the unit test again, we get an error message something like
+"Fatal error: Call to undefined method ProjectDao::createNewProject() in ...".
+So let us, fix this.
+
+
+class ProjectDao extends BaseDao
+{
+ public function createNewProject($project)
+ {
+ }
+
+ public function getProjectByID($projectID)
+ {
+ }
+}
+
+Run the unit test again, we see a different type of error now. This time
+the unit test runner complians that the test fails.
+Write Test, Run Test, Repeat
+
+
At this point, you may notice a repetition
+
+ - write some test code,
+ - run the test, test gives an error,
+ - write some actual code to only remove the previous error,
+ - goto step 1 to repeat.
+
+
+We shall see how we can make this test pass in the next section.
+
\ 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 @@
+
+Installation
+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.
+
+Download and Install Prado
+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.
+Installation of PRADO mainly involves downloading and unpacking.
+
+ - Go to pradosoft.com to grab the latest version of PRADO.
+ - Unpack the PRADO release file to a Web-accessible directory.
+
+
+You should at least first check that the demos bundled with the Prado distribution are
+ working. See the
+ quickstart tutorial for further instructions on running the demos.
+
+
+Help! Nothing is working!
+If you encounter problems in downloading, unpacking and running the demo applications, please
+ visit the forum to seek help. Please
+ do a search on the forum first or read the PDF version 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.
+
+Create a new Prado application
+Prado is bundled with a command line tool to create the necessary directory structure to run a hello world application.
+The command tool prado-cli.php is a php script and can be found in the prado/framework/ directory.
+To create a new application, go to your document root and type the following command in console.
+
+
+php prado/framework/prado-cli.php -t -c time-tracker
+
+
+Tip:
+For linux and OS X environments, you can chmod u+x prado-cli.php
+and change the first line of prado-cli.php to the location of your
+PHP command line interpreter. Now, you may call the script from command line
+like an executable.
+
+
+A directory named time-tracker will be created containing the following.
+
+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/
+
+
The time-tracker directory is where we are going to put all our code for the Time Tracker application.
+Since -t was passed into the prado-cli.php script, unit and functional test
+ skeletons are also created.
+
+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
+
+
+
\ 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 @@
+
+Prado Time Tracker Implementation Guide
+
+
\ 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 @@
+
+Introduction
+
+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
+ ASP.NET's Time Tracker Starter Kit.
+
+
+Time Tracker Overview
+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.
+Basic Application Requirements
+The functional requirements of the Time Tracker is based on the
+ ASP.NET's Time Tracker Starter Kit
+ description.
+
+
Create projects
+
+ - Define projection information like due dates, hours to complete, project
+ resources, and more.
+ - Break down projects into tasks and track work on per-task basis.
+
+Create and track tasks
+
+ - Track time spent each day by category and project.
+
+Use reports to track progress
+
+ - Track overall progress across multiple projects, including estimated and actual work.
+ - Track total work for team resources across multiple projects.
+
+
+
+Technologies and Design Approached Demonstrated
+
+ - Prado event-driven component framework
+ - Separation of concerns: persistent storage, business logic, presentation
+ - Object-Relational mapping using SQLMap
+ - Unit testing and functional testing
+ - Promote code reuse
+
+
+Requirements
+The Time Tracker web application requires the following software and knowledge.
+Software requirements
+It is assumed that you are able to obtain and install the following pieces of software.
+
+ - PHP version 5.0.4 or greater
+ - A web server, such as Apache, able to run PHP scripts
+ - MySQL database server version 4.1 or greater (alternatively SQLite or Postgres)
+ - Prado version 3.1 or later
+
+
+In addition to software requirements, we assumed that you have some of the following knowledge.
+
+ - Access the internet to download packaged code, and unzip these code.
+ - Understand the basic concepts of Object-Oriented programming in PHP such as
+ classes, objects, methods, etc.
+ - Basic understanding of relational databases and Structured Query Language (SQL).
+ - Knows how to have fun, this is mandatory.
+
+
\ 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 @@
+
+
+Finishing up test case
+Up to this point, you should have the follow pieces of files and code.
+ class="figure" />
+
+
+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.
+
+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.
+
+
+
+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);
+}
+
+
+Our refactored test method testProjectDaoCanCreateNewProject()
+is as follows.
+
+function testProjectDaoCanCreateNewProject()
+{
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $this->setupMockConnectionFor($project);
+ $conn->expectMinimumCallCount('queryForObject', 3);
+ $conn->expectAtLeastOnce('insert');
+ }
+
+ $this->assertProjectCreated($project);
+}
+
+
+To test that the project already exists, we modify the mock
+connection and test for an exception.
+
+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();
+ }
+}
+
+
+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 ProjectDaoTestCase.php to CreateNewProjectTestCase.php.
+
+
Note:
+A heirachical exception class may be more useful in testing for specific
+exceptions. For simplicity, we decided to use only TimeTrackerException
+for this small project.
+
+
+Tip: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.
+
+
+Comment:
+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.
+
+
+
+
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 @@
+
+More complete ProjectTestCase
+
+In creating a new project, we need to check the following:
+
+ - that the project does not already exists, i.e. no duplicate project name
+ - that the project creator and project manager exists when a new project is created
+
+So to perform this task, we will modified the test code.
+
+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);
+}
+
+
+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.
+Advantages of Mock
+
+ - we don't need a real database base connection to test the code,
+ this means we can start relying on tested code ealier
+ - when a test fails we know that problem is not part of the database
+ - when a test fail, we can quickly pin point the problem
+ - the test suite gives us the confidence to refactor our code
+
+
+
+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.
+
+
+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.
+
+If we run the above test, we will be faced with numerous errors. First will be
+that the TimeTrackerUser can not be found.
+
+Creating a new User Class
+Notice that the Project class contains CreatorUserName
+and ManagerUserName properties. So at some point we
+are going to need at least one User class. We shall name the class as
+TimeTrackerUser and save it as APP_CODE/TimeTrackerUser.php
+
+<?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');
+ }
+}
+?>
+
+
+
Custom Exceptions
+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 framework/Exceptions/messages.txt. This
+file location can be changed by overriding the getErrorMessageFile()
+method of TException class. We define a custom exception class
+for all Time Tracker application exceptions as TimeTrackerException
+and save the class as APP_CODE/TimeTrackerException.php.
+
+
+<?php
+class TimeTrackerException extends TException
+{
+ /**
+ * @return string path to the error message file
+ */
+ protected function getErrorMessageFile()
+ {
+ return dirname(__FILE__).'/exceptions.txt';
+ }
+}
+?>
+
+
+We then create a exceptions.txt file in the APP_CODE
+directory with the following content.
+
+
+timetracker_user_readonly_id = Time tracker user ID is read-only.
+
+
+Additional parameters passed in the exception constructor can be
+added the message string using {0} as the first additional parameter,
+and {1} as the second additional parameter, and so on.
+For example, suppose we want to raise the follow exception.
+
+
+
+throw new TimeTrackerException('project_exists', $projectName);
+
+
+The exception error message in exceptions.txt may contain something like:
+
+project_exists = Time tracker project '{0}' already exists.
+
+
+Completing the test case
+From the unit test code, we can pretty much see what the implementation
+for createNewProject() will look like.
+
+
+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);
+ }
+}
+
+
+Tip:
+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
+TimeTrackerException exception class for most exception cases.
+
+
+
\ 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 @@
+
+Using SQLMap Data Mapper
+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 SQLMap is only available offically in Prado 3.1 or later.
+
+SQLMap is an PHP implemenation of an Object/Relational
+data mapper. SQLMap has the following basic features:
+
+ - collects all the Structured Query Language
+(SQL) statements in an external XML file
+ - Maps data return from database queries into PHP objects
+ - Takes PHP objects as parameters in SQL queries
+
+SQLMap can be seen as a generic data mapper, rather than an
+Object Relational Mapping (ORM) solution.
+
+The SQLMap API consists of the following methods. See the SQLMap manual
+for further details.
+
+
+/* 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()
+
+
+Fetch and Inserting Data Using SQLMap
+Back to our ProjectDao 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.
+
+Creating a SQLMap connection
+We can test the ProjectDao 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.
+
+Comment:
+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.
+
+
+First, let us define the database table for projects.
+
+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
+
+
+The corresponding SQLite query to create the table is given below.
+
+
+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);
+
+
+Testing with Mocked Database Connection
+At this point, we have said nothing about databases. To create some unit tests
+for the ProjectDao class, we are not going to use real database connections
+but using a Mocked TSqlMapper. We modifiy the test as follows.
+
+
+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);
+ }
+}
+
+The test code looks slight more complicated because later on we want to
+test the same assertions against some real data.
+
+In the first two lines above, we simply import the business code and the TSqlMapper class.
+We generate a MockTSqlMapper, a mock class or TSqlMapper using
+Mock::generate('TSqlMapper'). The method Mock::generate() is available from
+the SimpleTest unit testing framework. An instance of MockTSqlMapper
+will be used as our test connection for the DAO objects. This allows us to interogate the
+internal workings of our ProjectDao class.
+
+
+In the setup() (this method is called before every test method), we create an instance of
+ProjectDao and set the connection to an instance of MockTSqlMapper.
+
+
+Testing internal workings of ProjectDao
+
+So how do we test the internal workings of the ProjectDao createNewProject 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 queryForObject is called only once with
+the parameters we have assumed (e.g. $project). See the SimpleTest tutorial for further details regarding
+unit testing with Mocks.
+
+
+In our assertions and expectations, we have
+
+$conn->expectOnce('insert', array('CreateNewProject', $project));
+$conn->setReturnValue('insert', true);
+
+$this->assertTrue($this->dao->createNewProject($project));
+
+This means that, we expect the createNewProject method in ProjectDao
+to call the TSqlMapper method insert with parameter $project only once.
+In addition, we assume that the returned value from the insert method of TSqlMapper
+returns true. Finally, we test the createNewProject method with an assertion.
+
+
+We now run the unit tests, we see that there are some failures or errors. Since we
+have not created any code in ProjectDao that performs what we want, the tests will fail.
+So lets make these test pass, we add some code to ProjectDao class.
+
+
+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);
+ }
+}
+
+
+
+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, what happens if the project already exists?, the details of these
+tests is the subject of the next section. The full test suite can be found in the source.
+
+
\ 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 @@
+
+Writing a Functional Web Test
+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 Selenium where the test cases can be written and run using PHP and SimpleTest.
+
+
+<?php
+class HelloPradoTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open('../index.php');
+ $this->assertTextPresent('Welcome to Prado!');
+ }
+}
+?>
+
+Save the code as HelloPradoTestCase.php in the document_root/time-tracker/tests/functional/
+directory.
+
+
+Functional test cases are written very similar to unit test cases. The method such as
+open($url) are those found in Selenium. All the methods available in Selenium are available.
+
+
+Run your first unit test case from your browser
+Point your browser to your development server's unit test case runner, e.g.
+ http://web-server-address/time-tracker/tests/functional.php. You should see the following
+
+
Figure 4: Functional test runner
+
+Clicking on the All button, you should see
+
+
Figure 5: Functional test success
+
+
+
\ 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 @@
+
+Writing a Unit Test
+Unit testing is a useful tool when we want to start to test
+ our individual business logic classes.
+ The tests/unit directory will be used to hold the unit test cases and tests/functional directory
+to hold the function test cases.
+
+
+Write a unit test case
+We will start be writing a very simple unit test case.
+
+<?php
+class ProjectTestCase extends UnitTestCase
+{
+ function testProjectClassExists()
+ {
+ $project = new Project();
+ $this->pass();
+ }
+}
+?>
+
+Save the code as ProjectTestCase.php in the document_root/time-tracker/tests/unit/
+directory.
+
+Run your first unit test case from your browser
+Point your browser to your development server's unit test case runner, e.g.
+ http://web-server-address/time-tracker/tests/unit.php. You should see the following
+
+
Figure 1: Unit test runner
+
+Clicking on the ProjectTestCase.php like, you should see
+
+
Figure 2: Unit test failure
+
+
+Smallest step to make the test pass.
+
+Obviously, we need create the class Project, so lets define the class.
+
+<?php
+class Project
+{
+}
+?>
+
+Save the above code as time-tracker/protected/pages/APP_CODE/Project.php.
+ Where the APP_CODE directory will contain most of your business logic code for the Time Tracker application.
+We also need to add the following line in our test case so as to include the Project class file when running the tests.
+
+
+The statement Prado::using('Application.APP_CODE.Project') basically
+loads the Project.php class file. It assumes that a class name Project has filename Project.php.
+For futher details regarding Prado::using can be found in Prado Namespaces documentation.
+
+
+
+<?php
+Prado::using('Application.APP_CODE.Project');
+class ProjectTestCase extends UnitTestCase
+{
+ ...
+}
+?>
+
+Run the unit test runner again, we see that the test has passed.
+
+
Figure 3: Unit test success
+
+
+Later on, we shall write more test cases. See the SimpleTest documentation for detailed tutorial on writing test cases.
+
+
\ 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 @@
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 00000000..f2209ef4
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/db.png differ
diff --git a/demos/time-tracker/protected/pages/Docs/functional_test1.png b/demos/time-tracker/protected/pages/Docs/functional_test1.png
new file mode 100644
index 00000000..33908734
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/functional_test1.png differ
diff --git a/demos/time-tracker/protected/pages/Docs/functional_test2.png b/demos/time-tracker/protected/pages/Docs/functional_test2.png
new file mode 100644
index 00000000..fb507e72
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/functional_test2.png differ
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 @@
+
+Prado Time Tracker
+This documentation is complete walk-through guide detailing the
+steps involved in implementating of the Prado Time Tracker web application.
+
+Target Audience
+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.
+Questions and Comments
+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
+http://www.pradosoft.com/forum/.
+
+
+
\ 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
new file mode 100644
index 00000000..a250a943
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/project1.png differ
diff --git a/demos/time-tracker/protected/pages/Docs/unit_test1.png b/demos/time-tracker/protected/pages/Docs/unit_test1.png
new file mode 100644
index 00000000..66b62e19
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/unit_test1.png differ
diff --git a/demos/time-tracker/protected/pages/Docs/unit_test2.png b/demos/time-tracker/protected/pages/Docs/unit_test2.png
new file mode 100644
index 00000000..e33544d2
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/unit_test2.png differ
diff --git a/demos/time-tracker/protected/pages/Docs/unit_test3.png b/demos/time-tracker/protected/pages/Docs/unit_test3.png
new file mode 100644
index 00000000..bbc04551
Binary files /dev/null and b/demos/time-tracker/protected/pages/Docs/unit_test3.png differ
--
cgit v1.2.3