| 1 |
lars |
1 |
<com:TContent ID="body">
|
|
|
2 |
<h1>Test First!</h1>
|
|
|
3 |
|
|
|
4 |
<p>Let's say that our most important client has a database and one of the tables
|
|
|
5 |
in the database is a list of people. Our client tells us:</p>
|
|
|
6 |
|
|
|
7 |
<p>"We would like to use a web application to display the people in this table
|
|
|
8 |
and to add, edit, and delete individual records."</p>
|
|
|
9 |
|
|
|
10 |
<p>Not a complicated story, but it will cover the CRUD most developers want to
|
|
|
11 |
learn first. :) Let's start with the people table that the client mentioned.
|
|
|
12 |
Since we're keeping it simple, we'll say it's a table in an Access database.
|
|
|
13 |
The table definition is shown as:</p>
|
|
|
14 |
|
|
|
15 |
<com:TTextHighlighter Language="sql" CssClass="source">
|
|
|
16 |
Name Type Size
|
|
|
17 |
PER_ID Long Integer 4
|
|
|
18 |
PER_FIRST_NAME Text 40
|
|
|
19 |
PER_LAST_NAME Text 40
|
|
|
20 |
PER_BIRTH_DATE Date/Time 8
|
|
|
21 |
PER_WEIGHT_KG Double 8
|
|
|
22 |
PER_HEIGHT_M Double 8
|
|
|
23 |
</com:TTextHighlighter>
|
|
|
24 |
|
|
|
25 |
<div class="tip"><b class="tip">Tip:</b>
|
|
|
26 |
This example is bundled with a SQLite database file "Data/test.db"
|
|
|
27 |
that contains the <tt>Person</tt> table and some data, ready to use.
|
|
|
28 |
</div>
|
|
|
29 |
|
|
|
30 |
<p>The first thing our story says is that client would like to display a list of
|
|
|
31 |
people. The following example shows our test for that.</p>
|
|
|
32 |
|
|
|
33 |
<com:TTextHighlighter Language="php" CssClass="source">
|
|
|
34 |
<?php
|
|
|
35 |
class PersonTest extends UnitTestCase
|
|
|
36 |
{
|
|
|
37 |
function testPersonList()
|
|
|
38 |
{
|
|
|
39 |
//try it
|
|
|
40 |
$people = TMapper::instance()->queryForList("SelectAll");
|
|
|
41 |
|
|
|
42 |
//test it
|
|
|
43 |
$this->assertNotNull($people, "Person list is not returned");
|
|
|
44 |
$this->assertTrue($people->getCount() > 0, "Person list is empty");
|
|
|
45 |
$person = $people[0];
|
|
|
46 |
$this->assertNotNull($person, "Person not returned");
|
|
|
47 |
}
|
|
|
48 |
}
|
|
|
49 |
?>
|
|
|
50 |
</com:TTextHighlighter>
|
|
|
51 |
|
|
|
52 |
<p>Well, the example sure looks easy enough! We ask a method to "select all", and
|
|
|
53 |
it returns a list of person objects. But, what code do we need to write to
|
|
|
54 |
pass this test?</p>
|
|
|
55 |
|
|
|
56 |
<div class="note"><b class="tip">Note:</b>
|
|
|
57 |
Save the <tt>PersonTest.php</tt> into a <tt>tests</tt> directory.
|
|
|
58 |
The unit tests are written for the <a href="http://simpletest.sf.net">SimpleTest Unit Testing framework</a>.
|
|
|
59 |
</div>
|
|
|
60 |
|
|
|
61 |
<p>Now, to setup the testing framework, suppose you have the <tt>SimpleTest</tt>
|
|
|
62 |
framework installed. Then we need to create an entry file to run the tests.
|
|
|
63 |
See the <tt>SimpleTest</tt> documentation for further details on setting up tests.</p>
|
|
|
64 |
|
|
|
65 |
<com:TTextHighlighter Language="php" CssClass="source">
|
|
|
66 |
<?php
|
|
|
67 |
require_once('../tests/simpletest/unit_tester.php');
|
|
|
68 |
require_once('../tests/simpletest/reporter.php');
|
|
|
69 |
require_once('../SQLMap/TMapper.php');
|
|
|
70 |
require_once('Models/Person.php');
|
|
|
71 |
|
|
|
72 |
//supress strict warnings from Adodb.
|
|
|
73 |
error_reporting(E_ALL);
|
|
|
74 |
|
|
|
75 |
$test = new GroupTest('All tests');
|
|
|
76 |
$test->addTestFile('Tests/PersonTest.php'); $test->run(new HtmlReporter());
|
|
|
77 |
?>
|
|
|
78 |
</com:TTextHighlighter>
|
|
|
79 |
|
|
|
80 |
<p>To run the tests, point your browser to the "<tt>run_test.php</tt>" script file
|
|
|
81 |
served from your web server.</p>
|
|
|
82 |
|
|
|
83 |
<p>Let's see. The test uses a list of person objects. We could start with a blank
|
|
|
84 |
object, just to satisfy the test, and add the display properties later. But
|
|
|
85 |
let's be naughty and skip a step. Our fully-formed person object is shown in
|
|
|
86 |
the following example</p>
|
|
|
87 |
|
|
|
88 |
<com:TTextHighlighter Language="php" CssClass="source">
|
|
|
89 |
<?php
|
|
|
90 |
class Person
|
|
|
91 |
{
|
|
|
92 |
public $ID = -1;
|
|
|
93 |
public $FirstName;
|
|
|
94 |
public $LastName;
|
|
|
95 |
public $WeightInKilograms = 0.0;
|
|
|
96 |
public $HeightInMeters = 0.0;
|
|
|
97 |
|
|
|
98 |
private $_birthDate;
|
|
|
99 |
|
|
|
100 |
//setters and getter for BirthDate
|
|
|
101 |
public function getBirthDate()
|
|
|
102 |
{
|
|
|
103 |
return $this->_birthDate;
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
public function setBirthDate($value)
|
|
|
107 |
{
|
|
|
108 |
$this->_birthDate = $value;
|
|
|
109 |
}
|
|
|
110 |
}
|
|
|
111 |
?>
|
|
|
112 |
</com:TTextHighlighter>
|
|
|
113 |
|
|
|
114 |
<p>OK, that was fun! The <tt>$this->assertXXX(...)</tt> methods are built into
|
|
|
115 |
<tt>UnitTestCase</tt> class. So to run the unit test example, we just need the
|
|
|
116 |
<tt>TMapper</tt> object and <tt>queryForList</tt> method. Wonderfully, the SQLMap
|
|
|
117 |
DataMapper framework has a <tt>TMapper</tt>class built into it that will work just
|
|
|
118 |
fine for for us to use in this tutorial, so we don't need to write that
|
|
|
119 |
either.</p>
|
|
|
120 |
|
|
|
121 |
<p>When the <tt>TMapper->instance()</tt> method is called, an instance of the SQLMap
|
|
|
122 |
<tt>TSqlMapper</tt> class is returned that has various methods available such as
|
|
|
123 |
<tt>queryForList</tt>. In this example, the SQLMap <tt>TSqlMapper->queryForList()</tt>
|
|
|
124 |
method executes our SQL statement (or stored procedure) and returns the result
|
|
|
125 |
as a list. Each row in the result becomes an entry in the list. Along with
|
|
|
126 |
<tt>queryForList()</tt>, there are also <tt>delete()</tt>, <tt>insert()</tt>,
|
|
|
127 |
<tt>queryForObject()</tt>, <tt>queryForPagedList()</tt> and a few other methods in the
|
|
|
128 |
<a href="?page=Manual.DataMapperAPI">SQLMap API</a>.
|
|
|
129 |
|
|
|
130 |
<p>Looking at unit test example, we see that the <tt>queryForList()</tt> method
|
|
|
131 |
takes the name of the statement we want to run. OK. Easy enough. But where
|
|
|
132 |
does SQLMap get the "SelectAll" statement? Some systems try to generate SQL
|
|
|
133 |
statements for you, but SQLMap specializes in data mapping, not code
|
|
|
134 |
generation. It's our job (or the job of our database administrator) to craft
|
|
|
135 |
the SQL or provide a stored procedure. We then describe the statement in an
|
|
|
136 |
XML element, like the one shown the following where
|
|
|
137 |
we use XML elements to map a database statement to an application object.
|
|
|
138 |
|
|
|
139 |
<com:TTextHighlighter Language="xml" CssClass="source">
|
|
|
140 |
<?xml version="1.0" encoding="utf-8" ?>
|
|
|
141 |
<sqlMap>
|
|
|
142 |
<select id="SelectAll" resultClass="Person">
|
|
|
143 |
SELECT
|
|
|
144 |
per_id as ID,
|
|
|
145 |
per_first_name as FirstName,
|
|
|
146 |
per_last_name as LastName,
|
|
|
147 |
per_birth_date as BirthDate,
|
|
|
148 |
per_weight_kg as WeightInKilograms,
|
|
|
149 |
per_height_m as HeightInMeters
|
|
|
150 |
FROM
|
|
|
151 |
person
|
|
|
152 |
</select>
|
|
|
153 |
</sqlMap>
|
|
|
154 |
</com:TTextHighlighter>
|
|
|
155 |
|
|
|
156 |
<p>The SQLMap mapping documents can hold several sets of related elements, like
|
|
|
157 |
those shown in the unit test case example. We can also have as many mapping
|
|
|
158 |
documents as we need to help organize our code. Additionally, having multiple
|
|
|
159 |
mapping documents is handy when several developers are working on the project
|
|
|
160 |
at once.</p>
|
|
|
161 |
|
|
|
162 |
<p>So, the framework gets the SQL code for the query from the mapping, and plugs
|
|
|
163 |
it into a prepared statement. But, how does SQLMap know where to find the
|
|
|
164 |
table's datasource?</p>
|
|
|
165 |
|
|
|
166 |
<p>Surprise! More XML! You can define a configuration file for each datasource
|
|
|
167 |
your application uses. The following code shows a configuration file named "<tt>sqlmap.config</tt>" for
|
|
|
168 |
our SQLite database.</p>
|
|
|
169 |
|
|
|
170 |
<com:TTextHighlighter Language="xml" CssClass="source">
|
|
|
171 |
<?xml version="1.0" encoding="UTF-8" ?>
|
|
|
172 |
<sqlMapConfig>
|
|
|
173 |
<provider class="TAdodbProvider">
|
|
|
174 |
<datasource driver="sqlite" host="Data/test.db" />
|
|
|
175 |
</provider>
|
|
|
176 |
<sqlMaps>
|
|
|
177 |
<sqlMap resource="Data/person.xml"/>
|
|
|
178 |
</sqlMaps>
|
|
|
179 |
</sqlMapConfig>
|
|
|
180 |
</com:TTextHighlighter>
|
|
|
181 |
|
|
|
182 |
<p>The <tt><provider></tt> specifies the database provider class, in this case
|
|
|
183 |
<tt>TAdodbProvider</tt> using the Adodb library. The <tt><datasource></tt> tag
|
|
|
184 |
specifies the database connection details. In this case, for an SQLite
|
|
|
185 |
database, we just need the driver name, and the host that points to the actual
|
|
|
186 |
SQLite database file.</p>
|
|
|
187 |
|
|
|
188 |
<p>The last part of the configuration file ("sqlMaps") is where we list our
|
|
|
189 |
mapping documents, like the one shown back in the previous code sample. We can
|
|
|
190 |
list as many documents as we need here, and they will all be read when the
|
|
|
191 |
configuration is parsed.</p>
|
|
|
192 |
|
|
|
193 |
<p>OK, so how does the configuration get parsed?</p>
|
|
|
194 |
|
|
|
195 |
<p>Look back at the unit test case example. The heart of the code is the call to the
|
|
|
196 |
"<tt>TMapper</tt>" object (under the remark "try it"). The <tt>TMapper</tt> object
|
|
|
197 |
is a singleton that handles the instantiation and configuration of an SQLMap
|
|
|
198 |
<tt>TSqlMapper</tt> object, which provides a facade to the SQLMap DataMapper
|
|
|
199 |
framework API.</p>
|
|
|
200 |
|
|
|
201 |
<p>The first time that the <tt>TMapper</tt> is called, it reads in the
|
|
|
202 |
<tt>sqlmap.config</tt> file and associated mapping documents to create an instance
|
|
|
203 |
of the <tt>TSqlMapper</tt> class. On subsequent calls, it reuses the
|
|
|
204 |
<tt>TSqlMapper</tt> object so that the configuration is not re-read.</p>
|
|
|
205 |
|
|
|
206 |
<p>The framework comes bundled with a default <tt>TMapper</tt> class for you to use
|
|
|
207 |
immediately to get access to the SQLMap client <tt>TSqlMapper</tt> object. If you want to use a
|
|
|
208 |
different name other than <tt>sqlmap.config</tt> at the default location for the
|
|
|
209 |
configuration file, or need to use more than one database and have one
|
|
|
210 |
TSqlMapper per database, you can also write your own class to mimic the role of
|
|
|
211 |
the Mapper class view by copying and modifying the standard version.</p>
|
|
|
212 |
|
|
|
213 |
<div class="tip"><b class="tip">Tip:</b>
|
|
|
214 |
You can also call <tt>TMapper::configure('/path/to/your/sqlmap.config')</tt>
|
|
|
215 |
to configure the <tt>TMapper</tt> for a specific configuration file.
|
|
|
216 |
</div>
|
|
|
217 |
|
|
|
218 |
<p>If we put this all together into a solution, we can "green bar" our test. At
|
|
|
219 |
this point you should have the following files.</p>
|
|
|
220 |
<com:TTextHighlighter Language="code" CssClass="source">
|
|
|
221 |
Data/person.xml % Mapping file.
|
|
|
222 |
Data/test.db % SQLite database file.
|
|
|
223 |
|
|
|
224 |
Models/Person.php % Person class file.
|
|
|
225 |
|
|
|
226 |
Tests/PersonTest.php % Unit test case for Person mapping.
|
|
|
227 |
|
|
|
228 |
run_tests.php % Unit test entry point.
|
|
|
229 |
sqlmap.config % SQLMap configuration file.
|
|
|
230 |
</com:TTextHighlighter>
|
|
|
231 |
|
|
|
232 |
<p>Run the tests by pointing your browser URL to the "<tt>run_tests.php</tt>" server
|
|
|
233 |
file.</p>
|
|
|
234 |
|
|
|
235 |
<img src=<%~ example1.png %> class="figure" />
|
|
|
236 |
<div class="caption"><b>Figure 2:</b> Green Bar!</div>
|
|
|
237 |
|
|
|
238 |
</com:TContent>
|