[pLog-svn] r4040 - in plog/branches/lifetype-1.1.1/class/test: helpers tests tests/ui

oscar at devel.lifetype.net oscar at devel.lifetype.net
Sun Sep 24 21:06:01 GMT 2006


Author: oscar
Date: 2006-09-24 21:06:00 +0000 (Sun, 24 Sep 2006)
New Revision: 4040

Added:
   plog/branches/lifetype-1.1.1/class/test/helpers/uiscriptrunner.class.php
   plog/branches/lifetype-1.1.1/class/test/tests/ui/
   plog/branches/lifetype-1.1.1/class/test/tests/ui/articlecategoriesui_test.class.php
   plog/branches/lifetype-1.1.1/class/test/tests/ui/login_test.class.php
Modified:
   plog/branches/lifetype-1.1.1/class/test/helpers/lifetypetestcase.class.php
Log:
added support for testing the user interface via the UIScriptRunner class. Scripts are currently written in native php code but eventually we could end up writing them in another language such as XML or YAML (so that people who are not familiar with PHP could even help us with 
UI testing stuff)
I've added two new test cases that test some UI features such as logging in and out and adding and deleting article categories, as an example of how to use this new feature.


Modified: plog/branches/lifetype-1.1.1/class/test/helpers/lifetypetestcase.class.php
===================================================================
--- plog/branches/lifetype-1.1.1/class/test/helpers/lifetypetestcase.class.php	2006-09-24 21:02:07 UTC (rev 4039)
+++ plog/branches/lifetype-1.1.1/class/test/helpers/lifetypetestcase.class.php	2006-09-24 21:06:00 UTC (rev 4040)
@@ -1,6 +1,7 @@
 <?php
 
 	include_once( PLOG_CLASS_PATH."class/test/PHPUnit/TestCase.php");
+	include_once( PLOG_CLASS_PATH."class/test/helpers/uiscriptrunner.class.php");	
 	include_once( PLOG_CLASS_PATH."class/net/http/httpclient.class.php" );
 	
 	/**
@@ -16,6 +17,11 @@
 	 */
 	class LifeTypeTestCase extends PHPUnit_TestCase
 	{		
+		/** 
+		 * An HTTP client
+		 */
+		var $c;
+		
 		/**
 		 * makes the given HTTP request and checks that the HTTP response code
 		 * matches with the expected once
@@ -91,5 +97,63 @@
 		{
 			return( str_replace( "_test", "", get_class( $this )));
 		}
+
+		/**
+		 * Finds the expected string in the content of the response after fetching the given
+		 * url via a POST request
+		 *
+		 * @param url
+		 * @param form An array that will be used as the variables of the form to be POSTed
+		 * @param expected
+		 * @param message
+		 */
+		function assertHTTPPostResponseContains( $url, $form, $expected, $message = '' )
+		{			
+			// fetch the url
+			$c = new HTTPClient();
+			$result = $c->submit( $url, $form );
+			
+			$this->assertTrue( $result, "Could not fetch $url" );
+			
+			// see if the string we're looking for is in the contents of the page
+			if( !strstr( $c->results, $expected )) {
+				$message = $message." expecting '$expected'";
+				return( $this->fail( $message ));
+			}
+		}
+
+		/**
+		 * Returns the admin url
+		 */
+		function getAdminUrl()
+		{
+			$config =& Config::getConfig();
+			return( $config->getValue( "base_url" )."/admin.php" );
+		}
+		
+		/**
+		 * Assert method that executes the given UI script and assert an error if the
+		 * script was not successfully executed
+		 *
+		 * @param script A UI script
+		 * @see UIScriptRunner
+		 */
+		function assertUIScript( $script )
+		{	
+			// keep this object as static so that its state is carried accross calls to this method
+			static $ui;
+			if(!isset($ui)) {
+				$ui = new UIScriptRunner();
+			}
+			
+			$result = $ui->run( $script );			
+						
+			if( !$result ) {
+				$message = "Error executing step ".$ui->getFailedStep().": ".$ui->getFailedStepErrorMessage();
+				return( $this->fail( $message ));
+			}
+			
+			return( true );
+		}
 	}
 ?>
\ No newline at end of file

Added: plog/branches/lifetype-1.1.1/class/test/helpers/uiscriptrunner.class.php
===================================================================
--- plog/branches/lifetype-1.1.1/class/test/helpers/uiscriptrunner.class.php	2006-09-24 21:02:07 UTC (rev 4039)
+++ plog/branches/lifetype-1.1.1/class/test/helpers/uiscriptrunner.class.php	2006-09-24 21:06:00 UTC (rev 4040)
@@ -0,0 +1,191 @@
+<?php
+
+	include_once( PLOG_CLASS_PATH."class/net/http/httpclient.class.php" );
+
+	/**
+	 * \ingroup Test
+	 *
+	 * This is a helper class that allows to execute scripts (defined as a PHP array with certain values)
+	 * to test user interfaces. Scripts are quite simple and are based on the idea of fetching a URL
+	 * (or submitting a form) and expecting to find a certain value in the resulting page.
+	 *
+	 * Scripts are fairly simple with only a small subsets of properties. The following is an
+	 * example of a script that logs in, selects a blog and logs out:
+	 *
+	 * <pre>
+	 *	Array(
+	 *		"login" => Array(
+	 *			"url" => "http://localhost/lt/admin.php",
+	 *			"type" => "post",
+	 *			"params" => Array(
+	 *				"userName" => "user",
+	 *				"userPassword" => "password",
+	 *				"op" => "Login"
+	 *			),
+	 *			"expected" => "Dashboard",
+	 *			"message" => "The dashboard did not appear when logging in"
+	 *		 ),
+	 *		"select_blog" => Array(
+	 *			"url" => "http://localhost/lt/admin.php",
+	 *			"type" => "get",
+	 *			"params" => Array(
+	 *				"op" => "blogSelect",
+	 *				"blogId" => "1"
+	 * 			),
+	 *			"expected" => "New Post",
+	 *			"message" => "The blog could not be selected after the dashboard"
+	 *		),
+	 *		"logout" => Array(
+	 *			"url" => "http://localhost/lt/admin.php",
+	 *			"type" => "get",
+	 *			"params" => Array( "op" => "Logout" ),
+	 *			"expected" => "You have been successfully logged out",
+	 *			"message" => "The logout screen was not displayed correctly"
+	 *		)
+	 *	);
+	 * </pre>
+	 *
+	 * Handling of HTTP requests is handled via the HttpClient class, and cookies are kept 
+	 * accross requests.
+	 * 
+	 * In order to incorporate these in your tests scripts, please use the method
+	 * LifeTypeTestCase::assertUIScript()
+	 */
+	class UIScriptRunner
+	{
+		var $c;
+		var $failedStep;
+		var $failedStepErrorMessage;
+		var $debug;
+		
+		function UIScriptRunner()
+		{
+			$this->c = new HttpClient();
+			
+			$this->debug = false;
+		}
+		
+		/**
+		 * Runs a UI script from an array
+		 */
+		function run( $script )
+		{
+			$result = false;
+			foreach( $script as $stepName => $step ) {
+				$result = $this->_runStep( $step );
+				if( !$result ) {
+					$this->failedStep = "$stepName";
+					$this->failedStepErrorMessage = $step["message"];
+					break;
+				}
+			}
+			
+			return( $result );
+		}
+		
+		/**
+		 * If UIStepRunner::run() returns false, this method will return the name of the
+		 * step that failed.
+		 *
+		 * @return A String
+		 */
+		function getFailedStep()
+		{
+			return( $this->failedStep );
+		}
+		
+		/**
+		 * If UIStepRunner::run() returns false, this method will return the name of the
+		 * step that failed.
+		 *
+		 * @return A String
+		 */		
+		function getFailedStepErrorMessage()
+		{
+			return( $this->failedStepErrorMessage );
+		}
+		
+		/**
+		 * @private
+		 */
+		function _runStep( $step )
+		{
+			// get the url
+			$url = $step["url"];
+			
+			// build the parameters depending on the type of request
+			$type = $step["type"];
+			if( $type == "get" ) {
+				if( isset( $step["params"] )) {
+					$params = $step["params"];
+					$query = "";
+					foreach( $params as $var => $value ) {
+						$query .= $var."=".urlencode( $value )."&";
+					}				
+					$query = $url."?".$query;
+				}
+				else {
+					$query = $url;
+				}
+				
+				// execute the query
+				$result = $this->c->fetch( $query );
+			}
+			else {
+				$result = $this->c->submit( $url, $step["params"] );
+			}
+			
+			// force the HttpClient to store its own cookies so that we can get some sort of
+			// stateful navigation
+			$this->c->setcookies();			
+						
+			// if the request was not possible, return error
+			if( !$result ) 
+				return false;
+				
+			if( $this->debug ) {
+				print($this->c->results);
+				print("<hr/>");
+			}
+				
+			// if an HTTP response code was specified, check if it matches
+			if( isset( $step["httpcode"] )) {
+				if( $this->c->response_code != $step["httpcode"] )					
+					return false;
+			}
+				
+			// and if the response did not contain the expected content, return error too
+			if( isset( $step["expected"] )) {
+				$expected = $step["expected"];
+				if( is_array( $expected )) {
+					foreach( $expected as $string ) {
+						if( !strstr( $this->c->results, $string )) 
+							return false;						
+					}
+				}
+				else {
+					if( !strstr( $this->c->results, $expected ))
+						return false;					
+				}
+			}
+
+			// additionally, check if there is something that should not be there
+			if( isset( $step["notexpected"] )) {
+				$expected = $step["notexpected"];
+				if( is_array( $expected )) {
+					foreach( $expected as $string ) {
+						if( strstr( $this->c->results, $string )) 
+							return false;						
+					}
+				}
+				else {
+					if( strstr( $this->c->results, $expected ))
+						return false;					
+				}
+			}
+			
+				
+			return true;
+		}
+	}
+?>
\ No newline at end of file

Added: plog/branches/lifetype-1.1.1/class/test/tests/ui/articlecategoriesui_test.class.php
===================================================================
--- plog/branches/lifetype-1.1.1/class/test/tests/ui/articlecategoriesui_test.class.php	2006-09-24 21:02:07 UTC (rev 4039)
+++ plog/branches/lifetype-1.1.1/class/test/tests/ui/articlecategoriesui_test.class.php	2006-09-24 21:06:00 UTC (rev 4040)
@@ -0,0 +1,94 @@
+<?php
+
+	include_once( PLOG_CLASS_PATH."class/test/helpers/lifetypetestcase.class.php" );
+	include_once( PLOG_CLASS_PATH."class/test/helpers/testtools.class.php" );	
+	
+	/**
+	 * \ingroup Test
+	 *
+	 * Tests the user interface to work with article categories
+	 */
+	class ArticleCategoriesUI_Test extends LifeTypeTestCase
+	{
+		function setUp()
+		{
+			$this->user = TestTools::createUser();
+			$this->blog = TestTools::createBlog( $this->user->getId());
+		}
+		
+		function tearDown()
+		{
+			TestTools::deleteDaoTestData( Array( $this->user, $this->blog ));
+		}
+		
+		/**
+		 * Test the whoe login and logout process
+		 */
+		function testArticleCategories()
+		{
+			$this->assertUIScript(
+				Array(
+					"login" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "post",
+						"params" => Array(
+							"userName" => $this->user->getUserName(),
+							"userPassword" => "password",
+							"op" => "Login"
+						),
+						"expected" => "Dashboard",
+						"message" => "The dashboard did not appear when logging in"
+					 ),
+					"select_blog" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"params" => Array(
+							"op" => "blogSelect",
+							"blogId" => $this->blog->getId()
+						),
+						"expected" => "New Post",
+						"message" => "The blog could not be selected after the dashboard"
+					),
+					"new_category" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"params" => Array( "op" => "newArticleCategory" ),
+						"expected" => "Name that will be used to display the category",
+						"message" => "The form to input a new category was not successfully displayed"						
+					),
+					"create_category" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "post",
+						"params" => Array( "categoryName" => "test category", "categoryDescription" => "description", "categoryInMainPage" => "1" , "op" => "addArticleCategory" ),
+						"expected" => "Category \"test category\" was successfully added to the blog",
+						"message" => "The test category was not successully added"
+					)
+				)
+			);
+			
+			// find the category in the db...
+			$cats = new ArticleCategories();
+			$cat = $cats->getCategoryByName( "test-category", $this->blog->getId());
+			
+			// ...and delete it via the UI
+			$this->assertUIScript(
+				Array(
+					"delete_category" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"params" => Array( "categoryId" => $cat->getId(), "op" => "deleteArticleCategory" ),
+						"expected" => "Category \"".$cat->getName()."\" deleted successfully",
+						"message" => "The test category was not successully deleted"
+					),
+					"delete_category_again" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"params" => Array( "categoryId" => $cat->getId(), "op" => "deleteArticleCategory" ),
+						"expected" => "There was an error deleting category with identifier \"".$cat->getId()."\"",
+						"message" => "Attempting to delete the same category twice did not generate the expected error message"
+					)					
+				)
+			);			
+		}
+	}
+?>
\ No newline at end of file

Added: plog/branches/lifetype-1.1.1/class/test/tests/ui/login_test.class.php
===================================================================
--- plog/branches/lifetype-1.1.1/class/test/tests/ui/login_test.class.php	2006-09-24 21:02:07 UTC (rev 4039)
+++ plog/branches/lifetype-1.1.1/class/test/tests/ui/login_test.class.php	2006-09-24 21:06:00 UTC (rev 4040)
@@ -0,0 +1,101 @@
+<?php
+
+	include_once( PLOG_CLASS_PATH."class/test/helpers/lifetypetestcase.class.php" );
+	include_once( PLOG_CLASS_PATH."class/test/helpers/testtools.class.php" );	
+	
+	/**
+	 * \ingroup Test
+	 */
+	class Login_Test extends LifeTypeTestCase
+	{
+		function setUp()
+		{
+			$this->user = TestTools::createUser();
+			$this->user2 = TestTools::createUser();
+			$this->blog = TestTools::createBlog( $this->user2->getId());
+		}
+		
+		function tearDown()
+		{
+			TestTools::deleteDaoTestData( Array( $this->user, $this->user2, $this->blog ));
+		}
+		
+		/**
+		 * check that the login page is working as expected
+		 */
+		function testLoginPage()
+		{
+			$this->assertUIScript(
+				Array(
+					"login_page" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"expected" => "Welcome to LifeType",
+						"message" => "The login screen did not display the right message"
+					)
+				)
+			);
+		}
+		
+		/**
+		 * Test that a user without a blog cannot login
+		 */
+		function testUserHasNoBlog()
+		{
+			$this->assertUIScript(
+				Array(
+					"login_page" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "post",
+						"params" => Array(
+							"userName" => $this->user->getUserName(),
+							"userPassword" => "password",
+							"op" => "Login"
+						),
+						"expected" => "you do not belong to any blog yet",
+						"message" => "User was not warned that he did not belong to any blog yet"
+					)
+				)
+			);			
+		}
+		
+		/**
+		 * Test the whoe login and logout process
+		 */
+		function testLoginLogout()
+		{
+			$this->assertUIScript(
+				Array(
+					"login" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "post",
+						"params" => Array(
+							"userName" => $this->user2->getUserName(),
+							"userPassword" => "password",
+							"op" => "Login"
+						),
+						"expected" => "Dashboard",
+						"message" => "The dashboard did not appear when logging in"
+					 ),
+					"select_blog" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"params" => Array(
+							"op" => "blogSelect",
+							"blogId" => $this->blog->getId()
+						),
+						"expected" => "New Post",
+						"message" => "The blog could not be selected after the dashboard"
+					),
+					"logout" => Array(
+						"url" => $this->getAdminUrl(),
+						"type" => "get",
+						"params" => Array( "op" => "Logout" ),
+						"expected" => "You have been successfully logged out",
+						"message" => "The logout screen was not displayed correctly"
+					)
+				)
+			);						
+		}
+	}
+?>
\ No newline at end of file



More information about the pLog-svn mailing list