How to write Functional and Unit Test on NetBeans (PHPUnit and Selenium) with Yii Framework

  1. Before reading this section, i hope your computer already has what testing needed. So, please read the following link: https://ratatouille90.wordpress.com/2012/11/26/setting-up-phpunit-with-xampp/

and make some change in your NetBeans project:

  • In your project, right click and choose properties.
    project_properties
  • In Project Properties, click Sources on Categories, then set you folder that will be used for testing to “Test Folder” field if it’s not set.
    test_folder
  • After that, click PHPUnit on Categories, then click checkbox on “Use Bootstrap” and “Use XML Configuration” if it’s not checked.
  • After checked both “Use Bootstrap” and “Use XML Configuration” then browse path to your bootstrap.php and phpunit.xml file.
    phpunit
  • Click Ok.
  • When we use the yiic webapp console command to create a new Yii application, it will generate the following files and directories for us to write and perform new tests (or when you are using project template that already have yii inside):
    [project_name]/
      protected/              containing protected application files
        tests/                containing tests for the application
          config/             containing config for testing
          fixtures/           containing database fixtures
          functional/         containing functional tests
          report/             containing coverage reports
          unit/               containing unit tests
          bootstrap.php       the script executed at the very beginning
          phpunit.xml         the PHPUnit configuration file
          WebTestCase.php     the base class for Web-based functional tests
  • Bootstrap
    Let’s take a look what may be in the bootstrap.php file. This file is so special because it is like the entry script and is the starting point when we execute a set of tests.

    $yiit = dirname(__FILE__).'/../../yii/framework/yiit.php';
    $config = dirname(__FILE__).'/config/test.php';
    require_once($yiit);
    require_once(dirname(__FILE__).'/WebTestCase.php');
    Yii::createWebApplication($config);

    In the above, we first include the yiit.php file from the Yii framework, which initializes some global constants and includes necessary test base classes. We then create a Web application instance using the test.php configuration file. If we check test.php (located in tests/config), we shall find that it inherits from the main.php configuration file and adds a fixture application component whose class is CDbFixtureManager.
    *So make sure the following path are correct, because it will give you some headache if you didn’t check it properly.*
    Here is test.php:

    return CMap::mergeArray(
        require(dirname(__FILE__).'/main.php'),
        array(
            'components'=>array(
                'fixture'=>array(
                    'class'=>'system.test.CDbFixtureManager',
                ),
                /* uncomment the following to provide test database connection
                'db'=>array(
                    'connectionString'=>'DSN for test database',
                ),
                */
            ),
        )
    );

    When we run tests that involve database, we should provide a test database so that the test execution does not interfere with normal development or production activities. To do so, we just need to uncomment the db configuration in the above and fill in the connectionString property with the DSN (data source name) to the test database.
    With such a bootstrap script, when we run unit tests, we will have an application instance that is nearly the same as the one that serves for Web requests. The main difference is that it has the fixture manager and is using the test database.

  • Fixtures
    The fixture data is organized as a collection of PHP files called fixture files. Each fixture file returns an array representing the initial rows of data for a particular table. The *FILE NAME* is the same as the *TABLE NAME*. The following is an example of the fixture data for the User table stored in a file named User.php:

    return array(
        array(
            'user_id' => 'testing',
            'password' => 'testing',
            'active' => 1,
            'createTime' => new CDbExpression('SYSDATE()'),
        ),
        array(
            'user_id' => 'abcdef',
            'password' => 'password',
            'active' => 1,
            'createTime' => new CDbExpression('SYSDATE()'),
        ),
    );

    As we can see, two rows of data are returned in the above. Each row is represented as an associative array whose keys are column names and whose values are the corresponding column values.
    When CDbFixtureManager is referenced for the first time, it will go through every fixture file and use it to reset the corresponding table. It resets a table by truncating the table, resetting the sequence value for the table’s auto-incremental, and then inserting the rows of data from the fixture file into the table.
    And we can write fixture on Functional and Unit Test like this:

    class SiteTest extends WebTestCase //or CDbTestCase
    {
        public $fixtures = array(
            'user' => 'User',
            'user_email' => 'UserEmail',
        );
    }

    Where ‘user’ are fixture/table name (remember rule about fixture name) and ‘User’ are model class name.

    *Tips*:
    Having too many fixture files could increase the test time dramatically. For this reason, you should only provide fixture files for those tables whose content may change during the test. Tables that serve as look-ups do not change and thus do not need fixture files.
    Some people say that fixture file names must be in lowercase.
    If you have large tables and a database that contains some sample data, you can use phpMyAdmin to help creating the fixture arrays. It provides an option to export table data as PHP array.

  • WebTestCase
    Let’s take a look at the WebTestCase.php file generated by the yiic webapp command. This file defines WebTestCase that may serve as the base class for all functional test classes.

    define('TEST_BASE_URL','http://path/to/project-name/index-test.php');
    class WebTestCase extends CWebTestCase
    {
        protected function setUp()
        {
            parent::setUp();
            $this->setBrowserUrl(TEST_BASE_URL);
        }
    }

    We use index-test.php as the entry script instead of index.php. The only difference between index-test.php and index.php is that the former uses test.php as the application configuration file while the latter main.php. Like this (*make sure it has a correct path*):

    $yii=dirname(__FILE__).'/yii/framework/yii.php';
    $config=dirname(__FILE__).'/protected/tests/config/test.php';    run();
  • Description for Functional Testing
    A functional test is written in terms of a class XyzTest which extends from CWebTestCase, where Xyz stands for the class being tested.
    The functional test class is saved in a PHP file named as XyzTest.php. By convention, the functional test file may be stored under the directory protected/tests/functional.
    The test class mainly contains a set of test methods named as testAbc, where Abc is often the name of a feature to be tested. For example, to test the user login feature, we can have a test method named as testLogin.
    A test method usually contains a sequence of statements that would issue commands to Selenium RC to interact with the Web application being tested. It also contains assertion statements to verify that the Web application responds as expected.
  • Description for Unit Testing
    A unit test is written in terms of a class XyzTest which extends from CTestCase or CDbTestCase, where Xyz stands for the class being tested. For example, to test the Post class, we would name the corresponding unit test as PostTest by convention. The base class CTestCase is meant for generic unit tests, while CDbTestCase is suitable for testing active record model classes.
    As i’m writing this Wiki, i can’t use extends from CDbTestCase. Because when i run unit test using CDbTestCase my CLI always stop working, i don’t know why and i already try solution from googling but no one can fix it, so i use CWebTestCase as my extend on my unit test. I hope in future someone can fix this.
    The unit test class is saved in a PHP file named as XyzTest.php. By convention, the unit test file may be stored under the directory protected/tests/unit.
    The test class mainly contains a set of test methods named as testAbc, where Abc is often the name of the class method to be tested.
  • Write Functional Testing
    1.  class SiteTest extends WebTestCase
    2.  {
    3.      public $fixtures = array(
    4.          'user' => 'User',
    5.      );
    6. 
    7.      protected function logout()
    8.      {
    9.          // test logout process
    10.         $this->assertTextNotPresent('Login');
    11.         $this->clickAndWait('link=Logout');
    12.         $this->assertTextPresent('Login');
    13.     }
    14. 
    15.     protected function checkLogout()
    16.     {
    17.         $this->open('index-test.php/site/index');
    18. 
    19.         // ensure the user is logged out
    20.         if($this->isTextPresent('Logout')) {
    21.             $this->clickAndWait('link=Logout');
    22.         }
    23.     }
    24. 
    25.     public function testLoginLogout()
    26.     {
    27.         $this->checkLogout();
    28. 
    29.         // test login process, including validation
    30.         $this->clickAndWait('link=Login');
    31.         $this->assertElementPresent('name=User[user_id]');
    32. 
    33.         $this->type('name=User[user_id]','');
    34.         $this->type('name=User[password]','');
    35.         $this->click("//input[@value='Submit']");
    36.         sleep(2);
    37.         $this->assertTextPresent('Is required');
    38. 
    39.         $this->type('name=User[user_id]','testing');
    40.         $this->type('name=User[password]','testing');
    41.         $this->clickAndWait("//input[@value='Submit']");
    42. 
    43.         $this->assertTextPresent('testing');
    44.         $this->assertTextPresent('Logout');
    45. 
    46.         $this->logout();
    47.     }
    48. }

    Explanation:

    • Name your test class same with your controller.
    • This are test class that extends from WebTestCase.
    • If your test code have failed condition where it doesn’t meet your expectation for test result then test case will stop and produce fail for that test case. After that it will run on next test case (other function test) if present, otherwise testing will stop.
    • From line 3-5, it’s how to write fixtures in test class.
    • In your test class, you can make 2 type of method: Method that need to be test or Method that need to be called.
      So Method that need to be called will not run as test case, but that class will run when test case call them.
      As you can see above, function logout and checklogout are function that need to be called and function testLoginLogout are test case.
      If you want to create another test case, just add ‘test’ in front of function name. ex: public function testLogout().
    • As i already explain above that syntax command for testing use Selenium command if you have some trouble, you can find documentation for Selenium testing.
    • Line 10 have code: “$this->assertTextNotPresent(‘Login’);”.
      This code is used to check if website view didn’t have text ‘Login’.
    • Line 11 have code: “$this->clickAndWait(‘link=Logout’);”.
      This code is used to click and wait link ‘Logout’. This method can be use to click button too.
      It’s uses click and wait because website needs reload and testing must wait until website render completely, after that testing will running again.
    • Line 12 have code: “$this->assertTextPresent(‘Login’);”.
      This code is used to check if website view have text ‘Login’.
    • Line 17 have code: “$this->open(‘index-test.php/site/index’);”.
      This code is used to open link that you want to test.
      For some reason i can’t get to the index-test.php so i write it manually on “open” method,
      actually you can just write “site/index” alone.
    • Line 20 have code: “$this->isTextPresent(‘Logout’)”.
      This code is used in if-statement to check if website view have text ‘Logout’.
      If not present then statement inside if-statement will not run.
    • Line 31 have code: “$this->assertElementPresent(‘name=User[user_id]’);”.
      This code is used to check if website view have element with name “User[user_id]”.
    • Line 33 have code: “$this->type(‘name=User[user_id]’,”);”.
      This code is used to type into textfield on website view that have input with type text automatically.
      This method can be use when you need to fill some textfield to run your test code. It have 2 params:
      first param for element that you need to fill and second param for value.
    • Line 35 have code: “$this->click(“//input[@value=’Submit’]”);”.
      This code are use to click button ‘Submit’. It uses click because website didn’t need reload.
      If you see line 33-37, that’s a code to test if text “Is required” will show if textfield user_id and password empty.
      It uses JavaScript Validation so it didn’t need reloading, and you can use click.
      After that, check whether text “Is required” is present or not. Maybe you thinking why in line 36 have sleep(2) method.
      It’s because Testing are running more fast than JavaScript validation, so when line code 35 run and JavaScript make validation,
      Testing already runs line code 36 and it will produce error because testing didn’t find the text.
      That’s why must use sleep to make testing a bit slow and give a time for JavaScript to run validation and after that
      text “Is required” will show. you can change value for sleep that will be good to your testing.
    • Line 39-46 will testing user_id and password on database, so website need reload. If it’s good then it will go to the index page. And check if user_id is present on index page or not. After that make testing to logout.
  • Write Unit Testing
    1.  class UserTest extends WebTestCase
    2.  {
    3.      public $fixtures = array(
    4.          'user' => 'User',
    6.  	);
    7.  
    8.  	public function testInsertUserModel()
    9.  	{
    10. 	    $userModel = new User();
    11. 
    12. 	    $userModel->user_id = 'test';
    13. 	    $userModel->password = 'password';
    15. 	    $userModel->createTime = new CDbExpression('SYSDATE()');
    16. 	    $this->assertTrue($userModel->insertModel());
    17. 
    18. 	    $userModel = User::model()->findByPk($userModel->user_id);
    19. 	    $this->assertTrue($userModel instanceof User);
    20. 	    $this->assertEquals(0, $userModel->active);
    21. 	}
    22. 
    23. 	public function testNotExistsUserModel()
    24. 	{
    25. 	    $userModel = new User();
    26. 	    $userModel->user_id = 'test';
    27. 	    $this->assertTrue($userModel->notExistsModel());
    28. 
    29. 	    $userModel->user_id = 'hondaa';
    30. 	    $this->assertFalse($userModel->notExistsModel());
    31. 	}
    32. }

    Explanation:

    • Name your test class same with your model.
    • This are test class that extends from WebTestCase. Like i already explain above because some reason i use this extends.
      Actually you must extends from CDbTestCase.
    • If your test code have failed condition where it doesn’t meet your expectation for test result then test case will stop and produce fail for that test case. After that will run on next test case (other function test) if present, otherwise testing will stop.
    • From line 3-5, it’s how to write fixtures in test class.
    • It uses simple syntax that used by PHPUnit and the others are Yii syntax.
    • Line 8-21 are function to test insert data on database from model.
      This function make some new Model and fill attributes to save in database.
    • Line 16 have code: “$this->assertTrue($userModel->insertModel());”.
      This code check if it’s model insert data successfully (true).
    • Line 19 have code: “$this->assertTrue($userModel instanceof User);”.
      This code check if data that inserted before it’s on database or not (call from line 18 and check it on line 19).
      Because if $userModel didn’t have value from User Model then $userModel are not instance from User.
    • Line 20 have code: “$this->assertEquals(0, $userModel->active);”.
      This code check if param1 are equals with param2 or not. In this example: are 0 equals with data on active attribute
      (from insert before i didn’t include active because in database will set dafault value with 0).
    • Line 23-30 are function to test method that check if user exists or not in table user.
      Line 27 are check if user with user_id ‘testing’ are exists or not.
      Line 30 are check if user with user_id ‘hondaa’ are exists or not.
      You can see from fixtures on point 4, that have user_id ‘testing’ and didn’t have user_id ‘hondaa’.
      So the result will be true and false.
  • You can run all testing with Alt+F6 or run for one file testing with Shift+F6.
    If you run Functional Testing, you can see what your testing is doing when running.
    But if it’s Unit Testing, you can’t see what your testing doing when running.
    After that wait for result whether it’s all success or there are some error.
  • If your WebBrowser didn’t show during testing and after that your testing failed, check your phpunit.xml file. phpunit.xml is used to run Selenium Testing by calling WebBrowser and you can define your browser according to browser type that’s already define in Selenium. Like this file:
    phpunit bootstrap="bootstrap.php"
        colors="false"
        convertErrorsToExceptions="true"
        convertNoticesToExceptions="true"
        convertWarningsToExceptions="true"
        stopOnFailure="false">
    
        <selenium>
    <!--
            <browser name="Firefox" browser="*firefox" />
    -->
            <browser name="Chrome" browser="*googlechrome C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" />
    <!--
            <browser name="Internet Explorer" browser="*iexplore" />
    -->
        </selenium>
    </phpunit>
    

    You can choose use FireFox or Google Chrome or Internet Explorer. But until now i can’t use FireFox, i don’t know why. if you use Internet Explorer like above, it can run automatically. But if you want to use Google Chrome, you can’t use simple keyword like “*chrome” in browser value, but use “*googlechrome” and define Google Chrome path in your computer. That’s all, and it can work.

  • Ref:
    http://www.yiiframework.com/doc/guide/1.1/en/test.overview
    http://www.yiiframework.com/doc/guide/1.1/en/test.fixture
    http://www.yiiframework.com/doc/guide/1.1/en/test.functional
    http://www.yiiframework.com/doc/guide/1.1/en/test.unit
    http://seleniumhq.org/docs/02_selenium_ide.html
    http://seleniumhq.org/docs/02_selenium_ide.html#script-syntax

    NB:
    This “How to write Unit Test” are done with Windows 7 Prof 64-bit RAM and XAMPP v1.8.1 on “C:\”
    If this “How to write Unit Test” didn’t work, maybe you can google your problem first.

    Advertisements