Unit Testing your Javascript Code.
As it’s been a while since my last “useful” posting I will bite the bullet now and therefore I will try to point you to a very easily overseen part of unit testing - the Java Script part.
Unit Test and Javascript ? Oh yes - we are living in the Web 2.0 (or even 3.0?) century and most applications do have huge parts which are written in Javascript. And for sure you need to test these part’s also - especially if you are following the eXtreme way of programming.
There are some tools around doing this, most popular is probably jsunit but today I want to focus on Crosscheck - an open source, java based test framework for your javascript code. Crosscheck’s huge advantage is that you do not need any browser - it simulates them so it can be run from commandline. The upside is - it works really nice, integrates smooth with Cruisecontrol and tests on different browsers. But for sure there is a downside - and this is - it works without the browsers and simulates them.
Unfortunatly simulating a browser can never be perfect, so you will have tests which will give wrong results - either the tests claim that it should run, but in real world it won’t or vice versa. Also some popular libraries like YUI are not yet supported and throw errors - but AFAIK thy are working on it - let’s hope the best.
The best place for all infos around Crosscheck is the developer site.
Now let’s start from the scratch - point your browser to ‘thefrontside’ and download the file. Make sure that you download the compiled version and not the source (*-sc*) version as these won’t work for you.
Unzip, and copy the file ‘crosscheck.jar” where ever you want to have it.
Now test if it’s working:
crosscheck$ java -jar crosscheck.jar
must print something like
Please specify at least one test file or directory.
Usage: java -jar crosscheck.jar [options] [test-file | test-directory]*
…
If not - install the latest JRE on your machine, and make sure that JAVA_HOME is set correct and the “java” binary is in your PATH.
For easier usage on the commandline and later in cruisecontrol we should create a shell script to wrap this call - so let’s do it. It could look like this:
java -jar crosscheck/crosscheck.jar $@
Also don’t forget to chmod +x the .sh file.
Creating our first Testfile is quite easy. Create a directory “js” in your “tests” directory whereever you also have your unit tests - simply create the directory parallel to “unit” directory and there we add a file called “demo.jst” with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | crosscheck.addTest({ setup: function() { //crosscheck.load("../out/js_source/filter.js"); }, /** dummy test example */ "test if something does something else": function () { assertEquals("0,- €", "0,- €"); } }) |
Now let’s have a look if it works:
$ crosscheck.sh demo.jst
And you should get something like this:
Running tests in environment: Mozilla 1.7 (Firefox 1.0)
0 tests: 0 Ok, 0 failed, 0 errored.
Running tests in environment: Mozilla 1.8 (Firefox 1.5)
0 tests: 0 Ok, 0 failed, 0 errored.
Running tests in environment: Internet Explorer 6
0 tests: 0 Ok, 0 failed, 0 errored.
So far so good, how does the integration with cruisecontrol now work ? Actually very very simple - this is why I have chosen to use crosscheck - it can export the junit format. Running crosscheck with -xml command produces JUnit files:
crosscheck.sh -xml=report js/
So this means you have to update your build.xml and add a new section for testing jScript - it could look like this:
<target name=”testjs” description=”Run the Javascript tests”>
<exec dir=”${project.srcdir}/tests” executable=”crosscheck.sh” failonerror=”false”>
<arg line=”-xml=${project.logdir}” />
</exec>
</target>
or whatever your config.xml / build.xml philosophie is.
I hope I made mayself clear - after reading this there should be no excuse for NOT testing Javascript.
And last but not least a big THANK YOU to the guys at Frontside, Charles Lowell and Jason Wadsworth for the good work!
Artikel im PHPMagazin über die Entstehung von Swoodoo
In der aktuellen Ausgabe des PHP Magazins ist ein Artikel von mir über die Historie und Entstehung von Swoodoo inklusiver lustiger (und nicht so lustiger) technischer Problem. Wer sich für das “crawlen” bzw. “screenscraping” wie es korrekt heisst interessiert der sollte das lesen.
Von einem, der auszog, das Crawlen zu lernen.
Eine Flugsuchmaschine wie SwooDoo zu entwickeln, erfordert sehr viel Arbeit und Know-how. Wie so oft liegt die Tücke im Detail – eine „Deep-Web-Suche“ sieht auf den ersten Blick recht einfach zu realisieren aus – erst während der Entwicklung zeigen sich die Probleme und Herausforderungen. Gerade die Reise- industrie bietet etliche Fallstricke, die man einzeln angehen und lösen muss.
Debugging Unit Tests ?
It might sound trivial and the solution is more than easy - but as I am frequently asked how todo this, here is the simple answer.
Create a PHP file in the directory where you start your Unittests usually with this content:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $unitTest = '...Your Unittest name - e.g. unit_mytest.php or Alltests.php etc...'; $_SERVER['argv'] = array( '/...path to your PHPUnit.../PHPUnit/TextUI/Command.php', $unitTest ); echo "<pre>"; include( '/...path to your PHPUnit.../PHPUnit/TextUI/Command.php' ); echo "</pre>"; ... |
Point your browser to this file, et voila - Unit test in the Browser.
Ok
- this solution is very easy and silly - but it works very well. And at least I got the impression that there are enough people around who do not know how to debug unit tests…
Testing protected Methods in Unit Tests
As a followup to my talks on the IPC 07 in Frankfurt here the improved version including:
- support for parameter in constrcutors ( via reflection )
- small bug fix avoiding autoload to conflict
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /** * Create proxy of given class. Proxy allows to test of protected class methods * @param string $superClassName * @param array|null $constructorParams parameters for contructor * @return object */ function getProxy($superClassName, array $params = null) { $proxyClassName = "{$superClassName}Proxy"; if (!class_exists($proxyClassName, false)) { $class = <<<class> class $proxyClassName extends $superClassName { public function __call($function, $args) { $function = str_replace('protected_', '_', $function); return call_user_func_array(array(&$this, $function), $args); } } CLASS; eval($class); } if (!empty($params)) { // Create an instance using Reflection, because constructor has parameters $class = new ReflectionClass($proxyClassName); $instance = $class->newInstanceArgs($params); } else { $instance = new $proxyClassName(); } return $instance; } </class> |
Example:
1 2 3 | $oClass = getProxy( 'myClass', array( 'some params')); $oClass->protected_myProtectedFcuntion(); ... |
Tales from the eXtreme Side - IPC 07 is over
So finally after 5 days I survived IPC and made my way home. As always it was a real pleasure to meet “la famiglia” - I learned a lot, hopefully could teach other people something and definitly had too much beer. The real reason why the IPC is so short is probably that otherwise 90% of PHP community would have to detox from alcohol
So - the title from my last talk could have also been the title of the whole IPC.
For those of you who are interested in the slides - you can download them directly here: tales-from-the-extreme-side_ipc_07.pdf
or if you interested in the PPT you might be able to fetch them via http://phpconference.com/
Using safari on MAC OS X using selenium
Hoi,
while setting up our new testing Machine ( a very old PowerPC with 466 Mhz
) I realized that it is not that easy to make Safari work using Selenium RC. With Michele from mayflower helping me we could make it work.
Here is the solution:
1. Use this patched Version of Selenium RC Snapshot with Safari and MAC OS X support ( it is the snapshot including the patch from here ).
2. Start Selenium using sudo
3. If you face any problems like “Please add the directory containing networksetup to your path” then you should check your disc under /System/Library/CoreServices/RemoteManagement/ARDAgent.app/
Contents/Support/networksetup - this directory should exist. If not - like on my old machine - you need to create a symlink to the correct directory - e.g.
“sudo ln -s networksetup-panther networksetup”
That’s it - I hope it helps I needed quite a few hours to figure this out.
Comparing communication speed between different machines in PHP
For a project we needed some sort of inter-server communication. Due to performance critical tasks like Click Tracking we decided that we need to investigate first the most reliable and fastest way to communicate.
1. Using Sockets
I created a Test Socket Server using more or less the demo script under http://de.php.net/sockets . This Server just takes what he receives and send it back. What is not implemented is multi threading and optimization - but it should be enough to get some reasonable results.
The Test Server looks like this…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | do { if (FALSE === ($buf = socket_read ($msgsock, 2048))) { echo "socket_read() fehlgeschlagen: Grund: "; echo socket_strerror ($ret) . "n"; break 2; } if (!$buf = trim ($buf)) { continue; } if ($buf == 'quit') { break; } if ($buf == 'shutdown') { socket_close ($msgsock); break 2; } $buf .= "n"; socket_write ($msgsock, $buf, strlen ($buf)); } while (true); socket_close ($msgsock); } while (true); |
Now we need to create some client. In our case we keep the connection open so we could avoid opening and closing sockets. The client for testing looks like this:
1 2 3 4 5 | $sh = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (! socket_connect( $sh, '192.168.20.33', 10000) ) die( 'Could not bind to Socket!'.PHP_EOL); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | $dStart = microtime( true); for( $iIdx = 0; $iIdx < $iMaxCyles; $iIdx++) { $aClient[$iIdx] = $iIdx; $aData = array( 'iIdx' => $iIdx ); // call $aRes = Comm_Socket( $sh, $aData); $sResult = $aRes['iIdx']; $aServer[$iIdx] = $sResult; } $dDuration = microtime( true) - $dStart; echo( $iMaxCyles." calls total: $dDuration sec. ".PHP_EOL); echo( "Average: ".$dDuration/$iMaxCyles." sec. per call ".PHP_EOL.PHP_EOL); $aLost = array_diff( $aClient, $aServer); if( count( $aLost)) { echo( "lost call/differences:".PHP_EOL); print_r( array_diff( $aClient, $aServer)); } |
where as function Comm_Socket looks quite simple:
1 2 3 4 5 6 7 8 9 10 11 | function Comm_Socket( $Socket, $mPostData) { $sMessage = serialize( $mPostData); socket_write( $Socket, $sMessage, strlen( $sMessage)); $sRet = socket_read( $Socket,512,PHP_NORMAL_READ); return unserialize( $sRet); } |
The results look like this:
1000 calls total: 0.473767995834 sec.
Average: 0.000473767995834 sec. per call
2. Using http via file_get_contents
Now let’s modify our testscript a bit and change our server so, that he takes any POST parameter and echo’s it.
This is easy to do - and our Client Function looks now like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | function Comm_File_Get_Contents( $sUri, $mPostData) { $fTimeout = 20; $sHttpPostQuery = http_build_query ( $mPostData ); $aStreamContextOpts = array( 'http'=>array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $sHttpPostQuery ) ); $oContext = stream_context_create ( $aStreamContextOpts ); // inject a request read timeout $aAddOpts = array( 'http' => array('timeout' => (float) $fTimeout ) ); stream_context_set_option( $oContext, $aAddOpts ); $sResponse = file_get_contents ( urldecode($sUri), false, $oContext ); return $sResponse; } |
Ok the results are quite interesting:
1000 calls total: 4.02230811119 sec.
Average: 0.00402230811119 sec. per call
Nearly 10 times slower compared to our Socket communication. For sure - by using lighttpd and tweaking the system we could improve performance - but still it’s a lot slower.
Also interesting are the results when we call the server not by IP but by URI - then the results look a lot worse:
1000 calls total: 156.423214912 sec.
Average: 0.156423214912 sec. per call
This is probably because our nslookup is quite slow ( we run Microsoft Small Business Server ) but still - the differences are dramatically.
Actually I could sum up now - but for the records I will try also the cURL way.
3. Using http via cURL
For using cURL we have to modify our client function a bit - the server can be left untouched.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | function Comm_cUrl( $sUri, $mPostData) { $mOptions = array( 'host' => $sUri, 'port' => 80, 'method' => 'POST', 'timeout' => 3, 'params' => $mPostData);$ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $sUri ); curl_setopt( $ch, CURLOPT_HEADER, 0 ); curl_setopt( $ch, CURLOPT_USERAGENT, @$_SERVER['HTTP_USER_AGENT'] ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 ); if ( 'POST' == $mOptions['method'] ) { curl_setopt( $ch, CURLOPT_POST, 1 ); $sPostString = ""; foreach( $mOptions['params'] as $key => $value ) { $sPostString .= "$key=" . utf8_encode( $value ) . "&"; } $sPostString = substr( $sPostString, 0, -1 ); curl_setopt( $ch, CURLOPT_POSTFIELDS, $sPostString ); } $mData = curl_exec( $ch ); curl_close( $ch ); return $mData; } |
Using IP we get these results:
1000 calls total: 3.17922592163 sec.
Average: 0.00317922592163 sec. per call
Interesting - a bit faster then with file_get_contents.
Finally let’s also try an URI and we get:
1000 calls total: 4.69447493553 sec.
Average: 0.00469447493553 sec. per call
So - cURL obivously caches the nslookup what leads to much better results.
4. Conclusion
More or less we got what we expected. Taking into account that multi threaded socket communication is a real beast and can cause a lot of trouble my personal opinion is that http will work fine for us. There is a lot of potential of speeding things up - so the difference is tolerable for us. This might be different for you
Results: Time Avg Typeof 0.47 0.00047 Socket 4.02 0.00402 file_get_contents 156.42 0.15642 file_get_contents by uri 3.17 0.00317 curl 4.69 0.00469 curl by uri
Failed asserting that “double:0.999″ matches expected value “double:0.999″.
If you execute the following piece of code in a test using phpUnit….
$dRes = 400-399.001;
$this->assertEquals( 0.999, $dRes);
you will get…
“Failed asserting that <double:0.999> matches expected value <double:0.999>.”
That’s PHP…
The correct way would be “$this->assertEquals(0.999, round($dRes, 3));” as float handling in PHP is a bit strange. But I really love this Message - I start to think about what is reality and what not…
Ruby is…
One of my Developers today about Ruby on Rails:
Ruby is somehow crazy, well if you use it a lot you might get used to it but still …. it’s a totally crazy language
No comments…
Plat Forms done…
So, Marco, Tomas and me won the Plat_Forms contest in the PHP Area. Interesting is, that I expected to be the worst
- actually what I tried to do is focus on developing in a professional way, fully documented and tested instead of going into functionality.
The results show that our performance was more or less the same ( in terms of working and testable functionality ) then the other teams - even if some luck was involved as the ZEND team was very unlucky and created nearly at the end of the contest one single bug which made the whole SOAP interface not useable.
This again prooves that using Agile methods, including unit testing etc. does NOT decrease the teams performance.
Interesting isn’t it ?
BTW I have to state here that all PHP teams were really good. The performance was more or less the same it was pure luck that we won, that’s all.
If you are interested in the full results you can find them here.
Ah - what I want to state here very clearly is - where are the teams from .NET, ruby, phython and all the other languages ? 2007 they did not participate because there where not enough qualified teams - hey come on - at least .NET there are MANY good teams around. So next time attend - and loose ![]()