Selenium are not generate Code coverage report
Set up the Selenium (with PHPUnit) to generate the code coverage is not hard. You just following the information in the PHPUnit website, that enough. BUT!, If you write your own web application that never ending your script (such as you use exit() function), this thing will make you in the trouble.
The PHP script that not running to the end will not execute the code to end the code coverage session, so, you have to modify Selenium Extensions/SeleniumTestcase/prepend.php to include the function in append.php. Use the register_shutdown_function php function to make your function in prepend.php called even the script terminate by the exit() operation.
Note: During now, you cannot use WebDriver in Selenium2Testcase to generate the code coverage yet. You have to use SeleniumTestcase until the version that they will supported on it.
Apache cannot make AJAX request
In my previous week, I just found that my machine have a problem when try to make the second request during the first request still loading. I think something must be wrong in the communication process between Windows - Apache - PHP / MySQL.
I see that during the problem occur, my Windows have many TIME_WAIT TCP/IP connection. So, after search about the apache problem, I try to change Registry of Windows to increase the number of User-port and reduce TIME_WAIT time before close the connection.
The problem was not solved. I try another way by change the Apache MPM parameter, I reduce number of concurrent connection, disable KeepAlive and Disable Win32AcceptEx. Problem seem like disappear for just only a moment and come back.
I found that my Apache (mod_status) is working properly during the Apache not respond. HTML files can be load. So, it should not be about the connection problem, it should be something wrong in our PHP Framework OR Apache <-> PHP.
Last, I try the Apache Benchmark to shooting my Apache server by making 10 concurrent to get more information. I found that all PHP-related files cannot make the concurrent connections at all. (even it just A simple PHP_Info file)
Hmmm, this should be about PHP extension, I think. I begin to disable one-by-one. and....
Yea!.... Finally I found that it is because Alternative PHP Cache (APC -- PHP extension) that make the error occurs. This problem cannot be found in PHP Error log, Apache Error log, MySQL Error Log or whatever. Finally I found from the APC website that my version have the Win7 file locking problem. They fixed in the new released. So, that is the finally time I can run concurrent HTTP Request without problem.
I am in a trap for really long time because it hard to investigate the component problem when there are no any information in the error log. I also trap by the session_write_close() php function that it is about file locking problem.
So, In case that you may have the same problem with me, if you have APC loaded in your PHP configuration, try to disable it and let check, if it help.
Install PHP 5.2/5.3 in the same machine
This is solution for DirectAdmin machine owner who would like to use both version of PHP5 in their system. First, you may see reference topic here and here. To install both PHP versions with custombuild, you are require to trick the DirectAdmin with PHP6 configuration because DirectAdmin itself doesn't support both PHP5 working at the same time.
One solution to choice
In the solution I provided, I make PHP5.3 as default compiler running as CLI. and PHP 5.2 can be optional used with .htaccess level configuration. (running by suPHP)
- Change the custombuild build script to version 1.2
cd /usr/local/directadmin/custombuild/
./build set custombuild 1.2 - Change automatic download new versions.txt to be No.
Later, explained../build set autover no - Build update and copy PHP5 configuration to PHP6 custombuild path
./build update
mkdir -p custom/suphp
cp -pf configure/suphp/configure.php5 custom/suphp/configure.php6 - For me, I implement PHP5.3 running as CLI and PHP5.2 as CGI by suPHP. This is difference from referenced topic
perl -pi -e 's/php5\:/phprep\:/' versions.txt
perl -pi -e 's/php6/php5/' versions.txt
perl -pi -e 's/phprep/php6/' versions.txt - Set build options
./build set php5_ver 5.3
./build set php6_cgi yes
./build set php6_cli no
./build set php5_cgi no
./build set php5_cli yes
Note that php6 is a configuration for PHP 5.2 and PHP5 is a configuration for PHP 5.3 and running as default engine. - Build both PHP versions.
./build php n - Change path of the PHP 5.2 package
Now, below is a configuration for PHP 5.2 based website. you have to put these code in .htaccess to root of public_html directory
AddHandler x-httpd-php6 .php
</FilesMatch>
if you don't like to use x-httpd-php6, you may change to x-httpd-php52 or somewhat you want. but you have to config below files
perl -pi -e 's/x-httpd-php6/x-httpd-php52/' /usr/local/suphp/etc/suphp.conf
perl -pi -e 's/x-httpd-php6/x-httpd-php52/' /etc/httpd/conf/extra/httpd-suphp.conf
Now, you can use website with PHP 5.2 and PHP 5.3 upon .htaccess configuration.
Netbean : Selected PHPUnit (version ?.?.?) is too old
If you experience with error message from Netbean "Selected PHPUnit (version ?.?.?) is too old, upgrade it if possible". you may set your PHP configuration file incorrected.
Try add Windows Environment variables
- PHPRC - Specific location of your PHP configuration file.
- PATH - add Path of your PHP execution file.
Try command line at your PHP binary directory
> phpunit
Verify that you MUST see manual page of phpunit command, otherwise, I may experience with your PEAR download problem. Try re-install them.
PHPUnit + Selenium = Code Coverage Report
โดยปกติ PHPUnit ซึ่งใช้ทำ Unit test นั้น จะไม่สามารถทำการทดสอบผ่านเว็บบราวเซอร์ตรงๆได้ ดังนั้น ถ้าหากใช้รูปแบบการพัฒนาโปรแกรมแบบ MVC อาจจะทำการทดสอบได้เฉพาะ Model เท่านั้น หากจะทดสอบ View / Control ต้องใช้เครื่องมือคือ Selenium ในการทำการทดสอบผ่านหน้าเว็บบราวเซอร์
ดังนั้น ถ้าเราต้องการสร้าง Code Coverage Report ขึ้นมา จะต้องทำผ่าน Selenium Interface ด้วย. ผมได้เขียน Guideline ในการสร้าง Code Coverage Report กับ Yii Framework ไว้ที่ Yii Framework Forum ครับ รายละเอียดสามารถตามอ่านได้ตามลิงค์
เครื่องมือที่ผมใช้พัฒนา PHP ประกอบด้วย
- Apache 2
- Windows 7
- Netbeans 6.9.1 พร้อมกับ Selenium Plugin
- PHP 5.3 กับ XDebug module
- PEAR/PHPUnit
จริงๆ แล้วใน Netbeans website มี Tutorial อยู่เช่นกัน แต่ว่า ในส่วนนี้ ผมจะแนะนำสำหรับคนที่ทำเวอร์ชวลโฮส ที่มีเว็บไซต์หลายๆเว็บไซต์ภายในเครื่อง. แนะนำให้ทำเฉพาะเครื่องสำหรับ Development เท่านั้น สำหรับ Production server จะทำให้ช้าลงมาก ไม่แนะนำ
ก่อนอื่น ทำการแก้ไขไฟล์ ดังรายละเอียดด้านล่างนี้
File: %PEAR_Directory%/PHPUnit/Extensions/SeleniumTestCase/prepend.php
// $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = FALSE; $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = $_SERVER["DOCUMENT_ROOT"] ;
File: %PEAR_Directory%/PHPUnit/Extensions/SeleniumTestCase/phpunit_coverage.php
//$GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = getcwd(); $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = $_SERVER["DOCUMENT_ROOT"] ;
File: php.ini (ไฟล์ Configuration ของ PHP)
; Automatically add files before PHP document. auto_prepend_file = "D:\Service\var\scripts\PHPUnit\Extensions\SeleniumTestCase\prepend.php" ; Automatically add files after PHP document. auto_append_file = "D:\Service\var\scripts\PHPUnit\Extensions\SeleniumTestCase\append.php"
File: httpd.conf (ไฟล์ Configuration ของ Apache2)
<Directory "D:/Service/var/scripts"> Options FollowSymLinks Indexes Allow from all Order allow,deny AllowOverride All </Directory>
ขั้นตอนต่อไป เป็นขั้นตอนสำหรับทำให้ไดเรกทอรี่ดังกล่าว สามารถใช้ได้ในเว็บไซต์ใดๆ ที่อยู่ในเครื่อง
File: httpd.conf (ไฟล์ Configuration ของ Apache2)
# LoadModule alias_module modules/mod_alias.so LoadModule alias_module modules/mod_alias.so<IfModule alias_module> # ScriptAlias /cgi-bin/ "E:/Service/Process/Apache2.2/cgi-bin/" Alias /selenium-phpunit/ "D:/Service/var/scripts/PHPUnit/Extensions/SeleniumTestCase/" </IfModule>
ต่อมาขั้นตอนสำคัญ อย่าลืม Restart Apache 2 service
ต่อจากนี้เป็นขั้นตอนเพิ่มเติมสำหรับ Yii Framework
File: %YiiFramework%/protected/test/WebTest.php
// Add this to class protected $coverageScriptUrl = 'http://your.localhost./selenium-phpunit/phpunit_coverage.php';
จากนั้นอย่าลืมตรวจสอบไฟล์ phpunit.xml ให้ทำการสร้าง Code Coverage report เมื่อมีการทำการทดสอบ
File: %YiiFramework%/protected/test/phpunit.xml
<logging> <log type="coverage-html" target="%YiiFramework%\protected\tests\coverage" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70" /> </logging>
เสร็จครับ หมดแล้ว หลังจากนั้น เริ่มใช้งานได้เลย
การเขียน Pre-step เงื่อนไขก่อนทำเทสเคส
การใช้ PHPUnit ร่วมกับ Selenium ปกติแล้ว เราจะมี 2 Methods ที่ช่วยในการทำงานก่อนและหลังทำเทสเคส นั่นคือ setUp(); และ tearDown();
ฟังก์ชั่น setUp(); จะทำงานทุกครั้งก่อนที่จะทำเทสเคสใดๆ และเมื่อทำเทสเคสเสร็จเรียบร้อยแล้วจะเรียก tearDown(); ฟังก์ชั่นมาปิดท้ายทุกครั้ง ดังนั้น จึงเป็นประโยชน์กรณีที่เราต้องเตรียมตัวอย่างสำหรับทำการทดสอบ หรือการทำลายตัวแปร หรือข้อมูล ภายหลังจากการทำการทดสอบเสร็จสิ้น
ในการใช้ PHPUnit ร่วมกับ Selenium เพื่อทดสอบเว็บแอพลิเคชั่นนั้น เวลาจะสั่งให้เว็บบราวเซอร์ทำการทดสอบใดๆ ตัว Selenium จะต้องทำการ Initialize สร้าง SessionID ขึ้นมาก่อน ดังนั้น ถ้าหากเราเขียนเทสเคสดังข้างล่าง จะไม่เกิดปัญหา เพราะหลังมันเรียกฟังก์ชั่น setUp(); แล้ว ภายในเฟรมเวิร์คจะมีไปเรียกฟังก์ชั่นอื่นๆ อีกนิดหน่อย เพื่อทำการ Initialize ค่าของ Browser ก่อนที่จะเข้าไปทำในแต่ละเทสเคส
protected function setUp()
{
parent::setUp();
}
protected function tearDown() {}
public function testActionIndex() {
// Login
$this->open("/");
$this->click("link=Login");
$this->waitForPageToLoad("30000");
$this->type("LoginForm_username", "username");
$this->type("LoginForm_password", "password");
$this->click("yt0");
$this->waitForPageToLoad("30000");
// Test Case
$this->click("link=Send SMS");
$this->waitForPageToLoad("30000");
$this->type("SendSMSForm_to", "0123456789");
$this->type("SendSMSForm_message", "Message Test");
$this->click("yt0");
$this->waitForPageToLoad("30000");
try {
$this->assertTrue($this->isTextPresent("ส่งเรียบร้อย"));
} catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
// Logout
$this->click("link=Logout (demo)");
$this->waitForPageToLoad("30000");
}
}
ปัญหามันเกิดขึ้น ตรงที่ว่า ตอน Initialize browser มันจะทำตอนจบ setUp(); method ไปแล้ว และทำก่อนเริ่มเทสเคส ดังนั้น ถ้าเรามี Pre-step อย่างจากตัวอย่าง Source code ด้านบน ถ้าเราใส่ Login ซึ่งเป็น Pre-step ไปในส่วน setUp(); จะเกิดปัญหา เพราะว่า Browser ยังไม่ถูก Initialize และจะแจ้งเป็น Error Message ดังข้างล่างนี้
PHPUnit_Framework_Exception: Response from Selenium RC server for click(link=Login).ERROR Server Exception: sessionId should not be null; has this session been started yet?.
ปัญหานี้ จะเกิดขึ้นเมื่อเราทำการ Login ไปโดยไม่ได้ Initialize Browser ขึ้นมาก่อน ดังข้างล่าง
parent::setUp();
$this->setBrowserUrl(TEST_BASE_URL);
$this->click("link=Login");
$this->waitForPageToLoad("30000");
$this->type("LoginForm_username", "username");
$this->type("LoginForm_password", "password");
$this->click("yt0");
$this->waitForPageToLoad("30000");
$this->click("link=Send SMS");
}
วิธีการคือ เราต้องสั่งให้ Selenium ทำงานก่อน โดยใช้ start(); method และตั้งค่าให้ไม่ต้องหยุดการทำงานอัตโนมัติเมื่อทำเทสเคสเสร็จ ผ่านฟังก์ชั่น setAutoStop(false);
โดยเราจะทำการหยุดการทำงานเอง ผ่าน tearDown(); ท้ายที่สุดแล้ว จึงได้ ซอสโค้ดประมาณนี้
- class AppControllerTest extends WebTestCase {
- protected function setUp() {
- parent::setUp();
- $this->setBrowser("*googlechrome");
- $this->setBrowserUrl(TEST_BASE_URL);
- $this->setAutoStop(false);
- $this->start();
- $this->open("/index-test.php");
- $this->click("link=Login");
- $this->waitForPageToLoad("30000");
- $this->type("LoginForm_username", "username");
- $this->type("LoginForm_password", "password");
- $this->click("yt0");
- $this->waitForPageToLoad("30000");
- $this->click("link=Send SMS");
- }
- protected function tearDown() {
- $this->stop();
- }
- public function testActionIndex() {
- $this->waitForPageToLoad("30000");
- $this->type("SendSMSForm_to", "0123456789");
- $this->type("SendSMSForm_message", "Message Test");
- $this->click("yt0");
- $this->waitForPageToLoad("30000");
- try {
- $this->assertTrue($this->isTextPresent("ส่งเรียบร้อย"));
- } catch (PHPUnit_Framework_AssertionFailedError $e) {
- }
- $this->click("link=Logout (demo)");
- $this->waitForPageToLoad("30000");
- }
- }
บรรทัดที่ 11, 13 เป็นการสั่งให้ Initialize Browser ก่อนที่เราจะใส่ Pre-step ในบรรทัดต่อๆ มาครับ.



