Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
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
&lt;?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
?&gt;
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
&lt;?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
?&gt;
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
&lt;?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
?&gt;
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-&gt;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-&gt;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>&lt;provider&gt;</tt> specifies the database provider class, in this case
183
<tt>TAdodbProvider</tt> using the Adodb library. The <tt>&lt;datasource&gt;</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>