| 1 |
<html> |
|---|
| 2 |
<head> |
|---|
| 3 |
<title>SlideShow</title> |
|---|
| 4 |
<link type="text/css" rel="stylesheet" href="slideshow/slideshow.css" /> |
|---|
| 5 |
<script type="text/javascript" src="slideshow/jsbase/misclib.js"></script> |
|---|
| 6 |
<script type="text/javascript" src="slideshow/jsbase/testing.js"></script> |
|---|
| 7 |
<script type="text/javascript" src="slideshow/slideshow.js"></script> |
|---|
| 8 |
</head> |
|---|
| 9 |
<body onload="slideshow.INCLUDE_FRONTPAGE_AS_SLIDE = true; |
|---|
| 10 |
slideshow.initialize(document, 'slide', 'slides', 'slidenum', |
|---|
| 11 |
'numslides')"> |
|---|
| 12 |
<div id="slide" title="hypertest"> |
|---|
| 13 |
<h3>HyperTest</h3> |
|---|
| 14 |
<div class="textbody"> |
|---|
| 15 |
<div>Guido Wesdorp</div> |
|---|
| 16 |
<div>EuroPython 2006</div> |
|---|
| 17 |
<div>guido@merlinux.de</div> |
|---|
| 18 |
</div> |
|---|
| 19 |
</div> |
|---|
| 20 |
<div id="slides"> |
|---|
| 21 |
<div title="introduction"> |
|---|
| 22 |
<h3>Introduction</h3> |
|---|
| 23 |
<div class="body"> |
|---|
| 24 |
<ul> |
|---|
| 25 |
<li>More JavaScript! :|</li> |
|---|
| 26 |
<li>EcmaUnit, HTTPUnit, AppTest</li> |
|---|
| 27 |
<li>Python command-line test runner</li> |
|---|
| 28 |
<li>Open source (BSD-style license)</li> |
|---|
| 29 |
</ul> |
|---|
| 30 |
</div> |
|---|
| 31 |
</div> |
|---|
| 32 |
<div title="existing solutions"> |
|---|
| 33 |
<h3>Existing solutions</h3> |
|---|
| 34 |
<div class="body"> |
|---|
| 35 |
<ul> |
|---|
| 36 |
<li>Unit test suites</li> |
|---|
| 37 |
<li>JSUnit - not OO, messy, lot of globals</li> |
|---|
| 38 |
<li>Functional testing</li> |
|---|
| 39 |
<li>Selenium - strange (tables), restrictive</li> |
|---|
| 40 |
<li>Haven't seen everything yet...</li> |
|---|
| 41 |
</ul> |
|---|
| 42 |
</div> |
|---|
| 43 |
</div> |
|---|
| 44 |
<div title="ecmaunit"> |
|---|
| 45 |
<h3>EcmaUnit - unit tests</h3> |
|---|
| 46 |
<div class="body"> |
|---|
| 47 |
<ul> |
|---|
| 48 |
<li>Written for Kupu</li> |
|---|
| 49 |
<li>Traditional unit test API, like Python's 'unittest'</li> |
|---|
| 50 |
<li>May be changed later, made simpler</li> |
|---|
| 51 |
<li>Also works in SpiderMonkey</li> |
|---|
| 52 |
</ul> |
|---|
| 53 |
</div> |
|---|
| 54 |
</div> |
|---|
| 55 |
<div title="ecmaunit example"> |
|---|
| 56 |
<h3>EcmaUnit example</h3> |
|---|
| 57 |
<!-- Trac blob ahead! --> |
|---|
| 58 |
|
|---|
| 59 |
<div class="code"><pre> |
|---|
| 60 |
<B><span class="code-lang">function</span></B> <B><span class="code-func">ExampleTestCase</span></B>() { |
|---|
| 61 |
<I><span class="code-comment">/* an example unit test */</span></I> |
|---|
| 62 |
<B><span class="code-lang">this</span></B>.name = <B><span class="code-string">'ExampleTestCase'</span></B>; |
|---|
| 63 |
|
|---|
| 64 |
<I><span class="code-comment">// this method will be executed before *every* test |
|---|
| 65 |
</span></I> <I><span class="code-comment">// method, there's also a tearDown counterpart that |
|---|
| 66 |
</span></I> <I><span class="code-comment">// is not used in this example |
|---|
| 67 |
</span></I> <B><span class="code-lang">this</span></B>.setUp = <B><span class="code-lang">function</span></B>() { |
|---|
| 68 |
<B><span class="code-lang">function</span></B> <B><span class="code-func">Foo</span></B>() { |
|---|
| 69 |
<B><span class="code-lang">this</span></B>.returnfoo = <B><span class="code-lang">function</span></B>() { |
|---|
| 70 |
<B><span class="code-lang">return</span></B> <B><span class="code-string">'foo'</span></B>; |
|---|
| 71 |
}; |
|---|
| 72 |
<B><span class="code-lang">this</span></B>.throwfoo = <B><span class="code-lang">function</span></B>() { |
|---|
| 73 |
<B><span class="code-lang">throw</span></B>(<B><span class="code-string">'foo'</span></B>); |
|---|
| 74 |
}; |
|---|
| 75 |
}; |
|---|
| 76 |
<B><span class="code-lang">this</span></B>.foo = <B><span class="code-lang">new</span></B> Foo(); |
|---|
| 77 |
}; |
|---|
| 78 |
|
|---|
| 79 |
<I><span class="code-comment">// an example of a successful assertEquals call |
|---|
| 80 |
</span></I> <B><span class="code-lang">this</span></B>.testAssertEquals = <B><span class="code-lang">function</span></B>() { |
|---|
| 81 |
<B><span class="code-lang">this</span></B>.assertEquals(<B><span class="code-lang">this</span></B>.foo.returnfoo(), <B><span class="code-string">'foo'</span></B>); |
|---|
| 82 |
}; |
|---|
| 83 |
|
|---|
| 84 |
<I><span class="code-comment">// an example of a successful assertThrows call (to |
|---|
| 85 |
</span></I> <I><span class="code-comment">// test whether some call results in an exception) |
|---|
| 86 |
</span></I> <B><span class="code-lang">this</span></B>.testAssertThrows = <B><span class="code-lang">function</span></B>() { |
|---|
| 87 |
<B><span class="code-lang">this</span></B>.assertThrows(<B><span class="code-lang">this</span></B>.foo.throwfoo, <B><span class="code-string">'foo'</span></B>); |
|---|
| 88 |
}; |
|---|
| 89 |
}; |
|---|
| 90 |
|
|---|
| 91 |
<I><span class="code-comment">// each test case has to subclass from TestCase, which provides |
|---|
| 92 |
</span></I><I><span class="code-comment">// the .assert* methods and some stubs |
|---|
| 93 |
</span></I>ExampleTestCase.prototype = <B><span class="code-lang">new</span></B> TestCase; |
|---|
| 94 |
</pre></div> |
|---|
| 95 |
|
|---|
| 96 |
<!-- end Trac blob 1 --> |
|---|
| 97 |
|
|---|
| 98 |
</div> |
|---|
| 99 |
<div title="ecmaunit running example"> |
|---|
| 100 |
<h3>EcmaUnit running</h3> |
|---|
| 101 |
<iframe class="bodyframe" loadsrc="http://johnnydebris.net:8080/projects/hypertest/js/ecmaunit/testecmaunit.html"></iframe> |
|---|
| 102 |
</div> |
|---|
| 103 |
<div title="httpunit"> |
|---|
| 104 |
<h3>HTTPUnit - testing HTTP APIs</h3> |
|---|
| 105 |
<div class="body"> |
|---|
| 106 |
<ul> |
|---|
| 107 |
<li>Uses XMLHttpRequest</li> |
|---|
| 108 |
<li>Examine returned headers and body</li> |
|---|
| 109 |
<li>Test with real clients</li> |
|---|
| 110 |
<li>Can mostly be replaced with server-side tests,</li> |
|---|
| 111 |
<li>but can be useful in some cases</li> |
|---|
| 112 |
</ul> |
|---|
| 113 |
</div> |
|---|
| 114 |
</div> |
|---|
| 115 |
<div title="httpunit example"> |
|---|
| 116 |
<h3>HTTPUnit example</h3> |
|---|
| 117 |
<!-- more Trac blob --> |
|---|
| 118 |
|
|---|
| 119 |
<div class="code"><pre><I><span class="code-comment">// a simple test function, assumes there's a cgi script called 'square.cgi' in |
|---|
| 120 |
</span></I><I><span class="code-comment">// the root of the proxy which returns 4 if a POST body 'int=16' is inputted, |
|---|
| 121 |
</span></I><I><span class="code-comment">// if this fails, an error is reported and the next test is called |
|---|
| 122 |
</span></I><B><span class="code-lang">function</span></B> <B><span class="code-func">test_square_root</span></B>(testcase, status, headers, body) { |
|---|
| 123 |
testcase.assertEquals(status, <B><span class="code-string">'200'</span></B>); |
|---|
| 124 |
testcase.assertEquals(headers[<B><span class="code-string">'content-type'</span></B>].substr(0, 10), |
|---|
| 125 |
<B><span class="code-string">'text/plain'</span></B>); |
|---|
| 126 |
<I><span class="code-comment">// note that string.strip() is a function of the 'jsbase' library |
|---|
| 127 |
</span></I> testcase.assertEquals(string.strip(body), <B><span class="code-string">'4'</span></B>); |
|---|
| 128 |
}; |
|---|
| 129 |
|
|---|
| 130 |
<I><span class="code-comment">// register the function so it can be ran later |
|---|
| 131 |
</span></I>window.httpunit_test_registry.push([ |
|---|
| 132 |
test_square_root, <I><span class="code-comment">// test function |
|---|
| 133 |
</span></I> <B><span class="code-string">'/square_root.cgi'</span></B>, <I><span class="code-comment">// url to test |
|---|
| 134 |
</span></I> <B><span class="code-string">'POST'</span></B>, <I><span class="code-comment">// method |
|---|
| 135 |
</span></I> {<B><span class="code-string">'Content-Type'</span></B>: <B><span class="code-string">'application/x-www-form-urlencoded'</span></B>}, <I><span class="code-comment">// headers |
|---|
| 136 |
</span></I> <B><span class="code-string">'int=16'</span></B> <I><span class="code-comment">// body |
|---|
| 137 |
</span></I>]); |
|---|
| 138 |
</pre></div> |
|---|
| 139 |
|
|---|
| 140 |
<!-- end Trac blob --> |
|---|
| 141 |
</div> |
|---|
| 142 |
<div title="httpunit running example"> |
|---|
| 143 |
<h3>HTTPUnit running</h3> |
|---|
| 144 |
<iframe class="bodyframe" loadsrc="http://johnnydebris.net:8080/projects/hypertest/js/httpunit.js/run_tests.html"></iframe> |
|---|
| 145 |
</div> |
|---|
| 146 |
<div title="apptest"> |
|---|
| 147 |
<h3>AppTest - functional tests</h3> |
|---|
| 148 |
<div class="body"> |
|---|
| 149 |
<ul> |
|---|
| 150 |
<li>Sequence of user actions</li> |
|---|
| 151 |
<li>'Lower level' functional testing framework</li> |
|---|
| 152 |
<li>Simple framework with some helper functions</li> |
|---|
| 153 |
<li>More (higher level) helper stuff coming up</li> |
|---|
| 154 |
</ul> |
|---|
| 155 |
</div> |
|---|
| 156 |
</div> |
|---|
| 157 |
<div title="apptest example"> |
|---|
| 158 |
<h3>AppTest example</h3> |
|---|
| 159 |
|
|---|
| 160 |
<!-- and another one... --> |
|---|
| 161 |
|
|---|
| 162 |
<div class="code"><pre><B><span class="code-lang">function</span></B> <B><span class="code-func">test_sync</span></B>(at, browser) { |
|---|
| 163 |
<I><span class="code-comment">/* simple test of a plain HTML document */</span></I> |
|---|
| 164 |
<I><span class="code-comment">// see whether the title is what we expect; if this fails (or |
|---|
| 165 |
</span></I> <I><span class="code-comment">// any other at.assert* call), execution of the test chain |
|---|
| 166 |
</span></I> <I><span class="code-comment">// stops entirely |
|---|
| 167 |
</span></I> at.assertEquals(browser.title, |
|---|
| 168 |
<B><span class="code-string">'Test document 1'</span></B>); |
|---|
| 169 |
|
|---|
| 170 |
<I><span class="code-comment">// also test the content of the 'body' element |
|---|
| 171 |
</span></I> at.assertEquals( |
|---|
| 172 |
string.strip( |
|---|
| 173 |
browser.document.getElementsByTagName( |
|---|
| 174 |
<B><span class="code-string">'body'</span></B> |
|---|
| 175 |
)[0].childNodes[0].nodeValue |
|---|
| 176 |
), |
|---|
| 177 |
<B><span class="code-string">'Test document content'</span></B> |
|---|
| 178 |
); |
|---|
| 179 |
|
|---|
| 180 |
<I><span class="code-comment">// in the end we have to make sure we send the browser to the |
|---|
| 181 |
</span></I> <I><span class="code-comment">// next location, either by clicking a button or by setting a new |
|---|
| 182 |
</span></I> <I><span class="code-comment">// location (unless this is the last test, in that case nothing |
|---|
| 183 |
</span></I> <I><span class="code-comment">// special needs to be done) |
|---|
| 184 |
</span></I> browser.navigateTo(<B><span class="code-string">'apptestdata/test_async.html'</span></B>); |
|---|
| 185 |
}; |
|---|
| 186 |
|
|---|
| 187 |
window.app_test_registry.push([<B><span class="code-string">'apptestdata/test_sync.html'</span></B>, test_sync]); |
|---|
| 188 |
</pre></div> |
|---|
| 189 |
|
|---|
| 190 |
<!-- end --> |
|---|
| 191 |
|
|---|
| 192 |
</div> |
|---|
| 193 |
<div title="ajax testing"> |
|---|
| 194 |
<h3>AJAX - hard to test</h3> |
|---|
| 195 |
<div class="body"> |
|---|
| 196 |
<ul> |
|---|
| 197 |
<li>Outside of main loops or function bodies</li> |
|---|
| 198 |
<li>So no error catching</li> |
|---|
| 199 |
<li>No sleep() or tight loops, block the interpreter</li> |
|---|
| 200 |
<li>Polling or callbacks</li> |
|---|
| 201 |
</ul> |
|---|
| 202 |
</div> |
|---|
| 203 |
</div> |
|---|
| 204 |
<div title="async apptest"> |
|---|
| 205 |
<h3>Asynchronous AppTest</h3> |
|---|
| 206 |
<div class="body"> |
|---|
| 207 |
<ul> |
|---|
| 208 |
<li>Return special token, continue outside of test function body</li> |
|---|
| 209 |
<li>Navigate away *and* call callback when done</li> |
|---|
| 210 |
<li>Users have to do their own error handling</li> |
|---|
| 211 |
<li>No way around those issues (afaik?), but at least it works!</li> |
|---|
| 212 |
</ul> |
|---|
| 213 |
</div> |
|---|
| 214 |
</div> |
|---|
| 215 |
<div title="async apptest example"> |
|---|
| 216 |
<h3>Async AppTest example</h3> |
|---|
| 217 |
<!-- ... --> |
|---|
| 218 |
|
|---|
| 219 |
<div class="code"><pre> |
|---|
| 220 |
<I><span class="code-comment">// simple polling loop that continues testing |
|---|
| 221 |
</span></I><B><span class="code-lang">var</span></B> poll_and_continue = <B><span class="code-lang">function</span></B>(at, browser, callback, orgtext) { |
|---|
| 222 |
<B><span class="code-lang">var</span></B> newtext = browser.getText( |
|---|
| 223 |
browser.document.getElementById(<B><span class="code-string">'container'</span></B>) |
|---|
| 224 |
); |
|---|
| 225 |
<I><span class="code-comment">// check if the text has been updated yet |
|---|
| 226 |
</span></I> <B><span class="code-lang">if</span></B> (newtext == orgtext) { |
|---|
| 227 |
<I><span class="code-comment">// not yet updated, poll |
|---|
| 228 |
</span></I> misclib.schedule(<B><span class="code-lang">this</span></B>, poll_and_continue, 100, at, browser, callback, orgtext); |
|---|
| 229 |
<B><span class="code-lang">return</span></B>; |
|---|
| 230 |
}; |
|---|
| 231 |
at.assertEquals(string.strip(newtext), <B><span class="code-string">'Text loaded'</span></B>); |
|---|
| 232 |
|
|---|
| 233 |
callback(); |
|---|
| 234 |
}; |
|---|
| 235 |
|
|---|
| 236 |
<B><span class="code-lang">function</span></B> <B><span class="code-func">test_async</span></B>(at, browser, callback) { |
|---|
| 237 |
<I><span class="code-comment">/* we have a document with a div that is filled with data, using |
|---|
| 238 |
XMLHttpRequest, after pressing some button... |
|---|
| 239 |
*/</span></I> |
|---|
| 240 |
|
|---|
| 241 |
<I><span class="code-comment">// first we store the original text |
|---|
| 242 |
</span></I> <B><span class="code-lang">var</span></B> orgtext = browser.getText( |
|---|
| 243 |
browser.document.getElementById(<B><span class="code-string">'container'</span></B>) |
|---|
| 244 |
); |
|---|
| 245 |
<I><span class="code-comment">// see whether it's the text we expect |
|---|
| 246 |
</span></I> at.assertEquals( |
|---|
| 247 |
string.strip(orgtext), |
|---|
| 248 |
<B><span class="code-string">'Nothing to see here, please move along.'</span></B> |
|---|
| 249 |
); |
|---|
| 250 |
|
|---|
| 251 |
<I><span class="code-comment">// click the button that starts the XHR text loading |
|---|
| 252 |
</span></I> browser.getElementsByText(<B><span class="code-string">'load'</span></B>)[0].click(); |
|---|
| 253 |
|
|---|
| 254 |
<I><span class="code-comment">// start the polling and continue testing there |
|---|
| 255 |
</span></I> poll_and_continue(at, browser, callback, orgtext); |
|---|
| 256 |
|
|---|
| 257 |
<I><span class="code-comment">// tell the framework to wait until the callback is called |
|---|
| 258 |
</span></I> <B><span class="code-lang">return</span></B> at.CONTINUE_ASYNC; |
|---|
| 259 |
}; |
|---|
| 260 |
|
|---|
| 261 |
window.app_test_registry.push([<B><span class="code-string">'apptestdata/test_async.html'</span></B>, test_async]); |
|---|
| 262 |
</pre></div> |
|---|
| 263 |
|
|---|
| 264 |
<!-- i think this is it for now... --> |
|---|
| 265 |
</div> |
|---|
| 266 |
<div title="apptest running example"> |
|---|
| 267 |
<h3>AppTest running</h3> |
|---|
| 268 |
<iframe class="bodyframe" loadsrc="http://johnnydebris.net:8080/projects/hypertest/js/apptest/testtest.html"></iframe> |
|---|
| 269 |
</div> |
|---|
| 270 |
<div title="hypertest combines"> |
|---|
| 271 |
<h3>HyperTest combines</h3> |
|---|
| 272 |
<div class="body"> |
|---|
| 273 |
<ul> |
|---|
| 274 |
<li>Run all the tests from one page</li> |
|---|
| 275 |
<li>One reporter</li> |
|---|
| 276 |
<li>Similar registration for all tests</li> |
|---|
| 277 |
<li>(Somewhat) consistent API</li> |
|---|
| 278 |
</ul> |
|---|
| 279 |
</div> |
|---|
| 280 |
</div> |
|---|
| 281 |
<div title="hypertest running example"> |
|---|
| 282 |
<h3>HyperTest running</h3> |
|---|
| 283 |
<iframe class="bodyframe" loadsrc="http://johnnydebris.net:8080/projects/hypertest/htdocs/run_tests.html"></iframe> |
|---|
| 284 |
</div> |
|---|
| 285 |
<div title="browsertest"> |
|---|
| 286 |
<h3>BrowserTest test runner</h3> |
|---|
| 287 |
<div class="body"> |
|---|
| 288 |
<ul> |
|---|
| 289 |
<li>Python script</li> |
|---|
| 290 |
<li>Boots (built-in) web server and runs browsers</li> |
|---|
| 291 |
<li>Browser receives tests from server and POSTs results</li> |
|---|
| 292 |
<li>Web server receives and processes results</li> |
|---|
| 293 |
<li>Integration into Python test libs (e.g. py.test)</li> |
|---|
| 294 |
</ul> |
|---|
| 295 |
</div> |
|---|
| 296 |
</div> |
|---|
| 297 |
<div title="browsertest example run"> |
|---|
| 298 |
<h3>BrowserTest example output</h3> |
|---|
| 299 |
<pre class="cli"> |
|---|
| 300 |
johnny@medusa:~/hypertest$ ./run_browsertest |
|---|
| 301 |
Going to test browser: epiphany |
|---|
| 302 |
TestEcmaUnitTestCase (testecmaunit.js, js test) |
|---|
| 303 |
TestTestCase - testAssert: OK |
|---|
| 304 |
TestTestCase - testAssertEquals: OK |
|---|
| 305 |
TestTestCase - testAssertNotEquals: OK |
|---|
| 306 |
TestTestCase - testAssertTrue: OK |
|---|
| 307 |
TestTestCase - testAssertFalse: OK |
|---|
| 308 |
TestTestCase - testAssertThrows: OK |
|---|
| 309 |
TestTestCase2 - testAssert: OK |
|---|
| 310 |
TestTestCase2 - testAssertEquals: OK |
|---|
| 311 |
TestTestCase2 - testAssertNotEquals: OK |
|---|
| 312 |
TestTestCase2 - testAssertTrue: OK |
|---|
| 313 |
TestTestCase2 - testAssertFalse: OK |
|---|
| 314 |
TestTestCase2 - testAssertThrows: OK |
|---|
| 315 |
time spent: 8 msecs |
|---|
| 316 |
|
|---|
| 317 |
TestHTTPUnitTestCase (testhttpunittests.js, http test) |
|---|
| 318 |
TestHTTPUnitTestCase - test_square_root: OK |
|---|
| 319 |
time spent: 75 msecs |
|---|
| 320 |
|
|---|
| 321 |
TestAppTestTestCase (testapptests.js, app test) |
|---|
| 322 |
TestAppTestTestCase - apptestdata/test_sync.html: OK |
|---|
| 323 |
TestAppTestTestCase - apptestdata/test_async.html: OK |
|---|
| 324 |
time spent: 439 msecs |
|---|
| 325 |
|
|---|
| 326 |
johnny@medusa:~/hypertest$ |
|---|
| 327 |
</pre> |
|---|
| 328 |
</div> |
|---|
| 329 |
<div title="py.test"> |
|---|
| 330 |
<h3>py.test integration</h3> |
|---|
| 331 |
<div class="body"> |
|---|
| 332 |
<ul> |
|---|
| 333 |
<li>Original goal, but not done yet</li> |
|---|
| 334 |
<li>Working on it, though...</li> |
|---|
| 335 |
<li>Unit test integration using SpiderMonkey</li> |
|---|
| 336 |
<li>Full integration with browsertest</li> |
|---|
| 337 |
</ul> |
|---|
| 338 |
</div> |
|---|
| 339 |
</div> |
|---|
| 340 |
<div title="references"> |
|---|
| 341 |
<h3>References</h3> |
|---|
| 342 |
<div class="body"> |
|---|
| 343 |
<div>http://hypertest.johnnydebris.net</div> |
|---|
| 344 |
<div>http://kupu.oscom.org - EcmaUnit</div> |
|---|
| 345 |
<div>http://codespeak.net/py - py.test</div> |
|---|
| 346 |
<div>guido@merlinux.de</div> |
|---|
| 347 |
</div> |
|---|
| 348 |
</div> |
|---|
| 349 |
</div> |
|---|
| 350 |
<div class="footer"> |
|---|
| 351 |
<span id="title">HyperTest - Guido Wesdorp - EuroPython 2006</span> |
|---|
| 352 |
- slide <span id="slidenum">0</span> of <span id="numslides">0</span> |
|---|
| 353 |
</div> |
|---|
| 354 |
</body> |
|---|
| 355 |
</html> |
|---|