- Μετατροπή του συστήματος έτσι ώστε να τρέχουν τα unit tests με την built-in βιβλιοθήκη του CodeIgniter.

- Συγγραφή κάποιων test για το Appointments Model.
This commit is contained in:
alextselegidis@gmail.com 2013-05-11 10:19:18 +00:00
parent 93289f60dd
commit aa26540fd8
12 changed files with 470 additions and 56 deletions

View File

@ -9,14 +9,6 @@
| http://codeigniter.com/user_guide/general/hooks.html
|
*/
// This hook is necessary to make the phpunit work with
// the codeigniter framework.
$hook['display_override'] = array(
'class' => 'DisplayHook',
'function' => 'captureOutput',
'filename' => 'DisplayHook.php',
'filepath' => 'hooks'
);
/* End of file hooks.php */
/* Location: ./application/config/hooks.php */

View File

@ -0,0 +1,38 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Test extends CI_Controller {
/**
* Class Constructor
*/
public function __construct() {
parent::__construct();
$this->load->driver('Unit_tests');
}
/**
* Run all available unit tests.
*/
public function index() {
$this->load->view('general/test');
$this->unit_tests->run_all_tests();
}
/**
* Test only the app models.
*/
public function models() {
$this->load->view('general/test');
$this->unit_tests->run_model_tests();
}
/**
* Test only the app libraries.
*/
public function libraries() {
$this->load->view('general/test');
$this->unit_tests->run_library_tests();
}
}
/* End of file test.php */
/* Location: ./application/controllers/test.php */

View File

@ -0,0 +1,14 @@
<?php
/**
* Database Exception Class
*/
class DatabaseException extends Exception {}
/**
* Validation Exception Class
*/
class ValidationException extends Exception {}
/* End of file exception_types_helper.php */
/* Location: ./application/helpers/exception_types_helper.php */

View File

@ -0,0 +1,18 @@
<?php
/**
* Check if a string date is valid for insertion or update
* to the database.
*
* @param string $datetime The given date.
* @return bool Returns the validation result.
*
* @link http://stackoverflow.com/a/8105844/1718162 [SOURCE]
*/
function validate_mysql_datetime($datetime) {
$dt = DateTime::createFromFormat('Y-m-d H:i:s', $datetime);
return ($dt) ? TRUE : FALSE;
}
/* End of file data_validation_helper.php */
/* Location: ./application/helpers/data_validation_helper.php */

View File

@ -25,5 +25,5 @@ function date3339($timestamp=0) {
return $date;
}
/* End of file general.php */
/* Location: ./application/helpers/general.php */
/* End of file general_helper.php */
/* Location: ./application/helpers/general_helper.php */

View File

@ -1,12 +0,0 @@
<?php
// This hook is necessary in order to run php unit tests
// against the codeigniter framework.
class DisplayHook {
public function captureOutput() {
$this->CI =& get_instance();
$output = $this->CI->output->get_output();
if (ENVIRONMENT != 'testing') {
echo $output;
}
}
}

View File

@ -0,0 +1,54 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Unit testing driver.
*
* This driver handles all the unit testing of the application.
* Custom methods (test categories) can be created in order to
* use different test groups on each testing
*/
class Unit_tests extends CI_Driver_Library {
public $CI;
public $valid_drivers;
/**
* Class Constructor
*/
public function __construct() {
$this->CI =& get_instance();
// Add more subclasses to the following array to expand
// the unit testing classes.
$this->valid_drivers = array(
'Unit_tests_appointments_model'
);
}
/**
* Run all the available tests of the system.
*
* If a new group of tests is added, it should be also added in
* this method, in order to be executed when all the tests need to
* be run.
*/
public function run_all_tests() {
$this->run_model_tests();
$this->run_library_tests();
}
///////////////////////////////////////////////////////////////////////////
// UNIT TEST GROUPS
///////////////////////////////////////////////////////////////////////////
/**
* Run all the models tests.
*/
public function run_model_tests() {
$this->appointments_model->run_all();
}
/**
* Run all the library tests.
*/
public function run_library_tests() {
}
}

View File

@ -0,0 +1,200 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Unit_tests_appointments_model extends CI_Driver {
private $CI;
private $provider_id;
private $customer_id;
private $service_id;
/**
* Class Constructor
*/
public function __construct() {
$this->CI =& get_instance();
$this->CI->load->library('Unit_test');
$this->CI->load->model('Appointments_Model');
// Get some sample data from the database (will be needed in the
// testing methods).
$this->provider_id = $this->CI->db
->select('ea_users.id')
->from('ea_users')
->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
->where('ea_roles.slug', DB_SLUG_PROVIDER)
->get()->row()->id;
$this->service_id = $this->CI->db
->select('ea_services.id')
->from('ea_services')
->join('ea_services_providers', 'ea_services_providers.id_services = ea_services.id', 'inner')
->where('ea_services_providers.id_users', $this->provider_id)
->get()->row()->id;
$this->customer_id = $this->CI->db
->select('ea_users.id')
->from('ea_users')
->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
->where('ea_roles.slug', DB_SLUG_CUSTOMER)
->get()->row()->id;
}
/**
* Run all the available tests
*/
public function run_all() {
// All the methods whose names start with "test" are going to be
// executed. If you want a method to not be executed remove the
// "test" keyword from the beginning.
$class_methods = get_class_methods('Unit_tests_appointments_model');
foreach ($class_methods as $method_name) {
if (strpos($method_name, 'test_') !== FALSE) {
call_user_func(array($this, $method_name));
}
}
// Create a report on the browser.
$this->CI->output->append_output($this->CI->unit->report());
}
/////////////////////////////////////////////////////////////////////////
// UNIT TESTS
/////////////////////////////////////////////////////////////////////////
/**
* Test the appointment add method - insert new record.
*/
private function test_add_appointment_insert() {
// Add - insert new appointment record to the database.
$appointment_data = array(
'start_datetime' => '2013-05-01 12:30:00',
'end_datetime' => '2013-05-01 13:00:00',
'notes' => 'Some notes right here...',
'id_users_provider' => $this->provider_id,
'id_users_customer' => $this->customer_id,
'id_services' => $this->service_id
);
$appointment_data['id'] = $this->CI->Appointments_Model->add($appointment_data);
$this->CI->unit->run($appointment_data['id'], 'is_int', 'Test if add() appointment (insert operation) returned the db row id.');
// Check if the record is the one that was inserted.
$db_data = $this->CI->db->get_where('ea_appointments', array('id' => $appointment_data['id']))->row_array();
$this->CI->unit->run($appointment_data, $db_data, 'Test if add() appointment (insert operation) has successfully inserted a record.');
// Delete inserted record.
$this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
}
/**
* Test the appointment add method - update existing record.
*/
private function test_add_appointment_update() {
// Insert new appointment (this row will be updated later).
$appointment_data = array(
'start_datetime' => '2013-05-01 12:30:00',
'end_datetime' => '2013-05-01 13:00:00',
'notes' => 'Some notes right here...',
'id_users_provider' => $this->provider_id,
'id_users_customer' => $this->customer_id,
'id_services' => $this->service_id
);
$this->CI->db->insert('ea_appointments', $appointment_data);
$appointment_data['id'] = $this->CI->db->insert_id();
// Perform the update operation and check if the record is update.
$changed_notes = 'Some CHANGED notes right here ...';
$appointment_data['notes'] = $changed_notes;
$update_result = $this->CI->Appointments_Model->add($appointment_data);
$this->CI->unit->run($update_result, 'is_int', 'Test if add() appointment (update operation) has returned the row id.');
$db_notes = $this->CI->db->get_where('ea_appointments', array('id' => $update_result))->row()->notes;
$this->CI->unit->run($changed_notes, $db_notes, 'Test add() appointment (update operation) has successfully updated record.');
// Delete inserted record.
$this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
}
/**
* Test appointment data insertion with wrong date
* format for the corresponding datetime db fields.
*/
private function test_add_appointment_wrong_date_format() {
// Insert new appointment with no foreign keys.
$appointment_data = array(
'start_datetime' => '2013-0WRONG5-01 12:30WRONG:00',
'end_datetime' => '2013-0WRONG5-01WRONG 13:00WRONG:00',
'notes' => 'Some notes right here...',
'id_users_provider' => $this->provider_id,
'id_users_customer' => $this->customer_id,
'id_services' => $this->service_id
);
$hasThrownException = FALSE; // This method must throw a validation exception.
try {
$this->CI->Appointments_Model->add($appointment_data);
} catch(ValidationException $valExc) {
$hasThrownException = TRUE;
}
$this->CI->unit->run($hasThrownException, TRUE, 'Test add() appointment with wrong date format.', 'A validation exception must be thrown.');
}
/**
* Test the exists() method.
*
* Insert a new appointment and test if it exists.
*/
private function test_appointment_exists() {
// Insert new appointment (this row will be checked later).
$appointment_data = array(
'start_datetime' => '2013-05-01 12:30:00',
'end_datetime' => '2013-05-01 13:00:00',
'notes' => 'Some notes right here...',
'id_users_provider' => $this->provider_id,
'id_users_customer' => $this->customer_id,
'id_services' => $this->service_id
);
$this->CI->db->insert('ea_appointments', $appointment_data);
$appointment_data['id'] = $this->CI->db->insert_id();
// Test the exists() method
$this->CI->unit->run($this->CI->Appointments_Model->exists($appointment_data), TRUE, 'Test exists() method with an inserted record.');
// Delete inserted record.
$this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
}
private function test_appointment_does_not_exist() {
// Create random appointmnet data that doesn't exist in the database.
$appointment_data = array(
'start_datetime' => '2013-05-01 08:33:45',
'end_datetime' => '2013-05-02 13:13:13',
'notes' => 'This is totally random!',
'id_users_provider' => '198678',
'id_users_customer' => '194702',
'id_services' => '8766293'
);
$this->CI->unit->run($this->CI->Appointments_Model->exists($appointment_data), FALSE, 'Test exists() method with an appointment that does not exist');
}
private function test_appointment_exists_wrong_data() {
// Create random appointmnet data that doesn't exist in the database.
$appointment_data = array(
'start_datetime' => '2WRONG013-05-01 0WRONG8:33:45',
'end_datetime' => '2013-0WRONG5-02 13:13:WRONG13',
'notes' => 'This is totally random!',
'id_users_provider' => '1986WRONG78',
'id_users_customer' => '1WRONG94702',
'id_services' => '876WRONG6293'
);
$this->CI->unit->run($this->CI->Appointments_Model->exists($appointment_data), FALSE, 'Test exists() method with wrong appointment data.');
}
// @task Test find_record_id
// @task Test delete
// @task Test get_batch
// @task Test get_row
// @task Test get_value
// @task Test validate_data
}

View File

@ -6,6 +6,7 @@ class Appointments_Model extends CI_Model {
*/
public function __construct() {
parent::__construct();
$this->load->helper('custom_exceptions');
}
/**
@ -15,26 +16,27 @@ class Appointments_Model extends CI_Model {
* appointment doesn't exists it is going to be inserted, otherwise
* the record is going to be updated.
*
* @expectedException ValidationException
* @expectedException DatabaseException
*
* @param array $appointment_data Associative array with the appointmet's
* data. Each key has the same name with the database fields.
* @return int Returns the appointments id.
*/
public function add($appointment_data) {
try {
$appointment_id = $this->exists($appointment_data);
if (!$appointment_id) {
$appointment_id = $this->insert($appointment_data);
} else {
$appointment_data['id'] = $appointment_id;
$this->update($appointment_data);
}
return $appointment_id;
} catch (Exception $exc) {
echo $exc->getMessage() . '<br/><pre>' . $exc->getTraceAsString() . '</pre>';
// Validate the appointment data before doing anything.
if (!$this->validate_data($appointment_data)) {
throw new ValidationException('Appointment data are not valid');
}
// Insert or update the appointment data.
if (!$this->exists($appointment_data)) {
$appointment_data['id'] = $this->insert($appointment_data);
} else {
$appointment_data['id'] = $this->update($appointment_data);
}
return $appointment_data['id'];
}
/**
@ -44,36 +46,35 @@ class Appointments_Model extends CI_Model {
* the database. This method does not search with the id, but with a
* combination of the appointments field values.
*
* @uses find_record_id()
*
* @param array $appointment_data Associative array with the appointment's
* data. Each key has the same name with the database fields.
* @return int|bool Returns the record id or FALSE if it doesn't exist.
* @return bool Returns wether the record exists or not.
*/
public function exists($appointment_data) {
$this->db->where(array(
'start_datetime' => $appointment_data['start_datetime'],
'end_datetime' => $appointment_data['end_datetime'],
'id_users_provider' => $appointment_data['id_users_provider'],
'id_users_customer' => $appointment_data['id_users_customer'],
'id_services' => $appointment_data['id_services']
));
$result = $this->db->get('ea_appointments');
return ($result->num_rows() > 0) ? $result->row()->id : FALSE;
public function exists($appointment_data) {
try {
$this->find_record_id($appointment_data);
return TRUE;
} catch(DatabaseException $dbExc) {
return FALSE;
}
}
/**
* Insert a new appointment record to the database.
*
* @expectedException DatabaseException
*
* @param array $appointment_data Associative array with the appointment's
* data. Each key has the same name with the database fields.
* @return int Returns the id of the new record.
*/
private function insert($appointment_data) {
if (!$this->db->insert('ea_appointments', $appointment_data)) {
throw new Exception('Could not insert new appointment record.');
throw new DatabaseException('Could not insert appointment record.');
}
return $this->db->insert_id();
return intval($this->db->insert_id());
}
/**
@ -84,12 +85,77 @@ class Appointments_Model extends CI_Model {
*
* @param array $appointment_data Associative array with the appointment's
* data. Each key has the same name with the database fields.
* @return @return int Returns the id of the updated record.
* @return int Returns the id of the updated record.
*/
private function update($appointment_data) {
if (!isset($appointment_data['id'])) {
$appointment_data['id'] = $this->find_record_id($appointment_data);
}
$this->db->where('id', $appointment_data['id']);
if (!$this->db->update('ea_appointments', $appointment_data)) {
throw new Exception('Could not update appointment record.');
throw new DatabaseException('Could not update appointment record.');
}
return $appointment_data['id'];
}
/**
* Find the database id of an appointment record.
*
* The appointment data should include the following fields in order to
* get the unique id from the database: start_datetime, end_datetime,
* id_users_provider, id_users_customer, id_services.
*
* @expectedException DatabaseException
*
* @param array $appointment_data Array with the appointment data. The
* keys of the array should have the same names as the db fields.
* @return int Returns the id.
*/
public function find_record_id($appointment_data) {
$this->db->where(array(
'start_datetime' => $appointment_data['start_datetime'],
'end_datetime' => $appointment_data['end_datetime'],
'id_users_provider' => $appointment_data['id_users_provider'],
'id_users_customer' => $appointment_data['id_users_customer'],
'id_services' => $appointment_data['id_services']
));
$result = $this->db->get('ea_appointments');
if ($result->num_rows() == 0) {
throw new DatabaseException('Could not find appointment record id.');
}
return $result->row()->id;
}
/**
* Validate appointment data before the insert or
* update operation is executed.
*
* @param array $appointment_data Contains the appointment data.
* @return boolean Returns the validation result.
*/
public function validate_data($appointment_data) {
$this->load->helper('data_validation');
try {
// Check if appointment dates are valid.
if (!validate_mysql_datetime($appointment_data['start_datetime'])) {
throw new Exception('Appointment start datetime is invalid.');
}
if (!validate_mysql_datetime($appointment_data['end_datetime'])) {
throw new Exception('Appointment end datetime is invalid.');
}
// @task Check if appointment foreign keys are valid.
return TRUE;
} catch (Exception $exc) {
return FALSE;
}
}

View File

@ -0,0 +1,45 @@
<script type="text/javascript" src="<?php echo $this->config->base_url(); ?>assets/js/libs/jquery/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var passedTestsNumber = $('span').filter(function() {
return $(this).text() === 'Passed';
}).length;
var totalTestsNumber = $('table').length;
$('#test-results').text(passedTestsNumber + ' / ' + totalTestsNumber + ' Passed');
if (passedTestsNumber == totalTestsNumber) {
$('#test-header').css('background-color', '#3DD481');
} else {
$('#test-header').css('background-color', '#D43D3D');
}
});
</script>
<style>
#test-header {
font-size: 30px;
font-family: arial, sans-serif;
height: 70px;
padding: 10px;
margin-bottom: 15px;
}
#test-heading {
margin-top: 15px;
display: inline-block;
margin-right: 21px;
color: #DBFFA6;
/* color: #C0FFD9;
text-shadow: 0px 1px 1px #0D9E41;*/
}
#test-results {
color: #FFF;
}
</style>
<div id="test-header">
<strong id="test-heading">Easy!Appointments Unit Testing</strong>
<strong id="test-results"></strong>
</div>

View File

@ -1,7 +1,7 @@
<?php
class SystemConfiguration {
// General Settings
public static $base_url = 'http://localhost/dev/external/Easy!Appointments/trunk/src/';
public static $base_url = 'http://localhost/dev/external/easy_appointments/trunk/src/';
// Database Settings
public static $db_host = 'localhost';

View File

@ -38,8 +38,7 @@ class CI_Utf8 {
{
log_message('debug', "Utf8 Class Initialized");
//global $CFG;
$CFG =& load_class('Config', 'core'); // This is changed due to php unit conflict : http://www.jamesfairhurst.co.uk/posts/view/codeigniter_phpunit_and_netbeans
global $CFG;
if (
preg_match('/./u', 'é') === 1 // PCRE must support UTF-8