So, strlen() is not co-operating.
If some code is not co-operating I do the following things:
- Isolate the problem. By getting rid of everything that is not relevant.
- Try and clarify what it should be doing by asking myself "What are my assertions and expectations?".
- Write a specific script to test the misbehaving code
- Modify the misbehaving code until it behaves as expected (making it more testable if required).
Isolate the problem. By getting rid of everything that is not relevant.
First, lets take your original example code and get rid of everything that is irrelavent to the specific question in hand. To leave the just the validation code that uses strlen. So, we can see more clearly now.
Then, try and clarify what it should be doing by asking "What are my assertions and expectations?".
I guess from your code that:
- given
- a field of information (title, name, ip)
- and its minimum length
- and its maximum length
- when a value is submitted by a user that has a length less than the minimum length or greater than its maximum length
- then, the value should be rejected
- else its ok
Write a specific script to test the misbehaving code
I'd create a html/php script purely for testing out the php that's causing grief. Call it FormFieldValidatorTest.php
for example. The test script sits with the web site project but is only meant to be run by me. So, I'd put it into a password protected directory or some other place that's inaccessible to the public.
I want a UTF-8 html page that submits some strings of known length. Say, the letter 'a' which we know is one character long and an 'empty field' which we know is zero characters long.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>My Test</title>
</head>
<body>
<form action="" method="get">
<input name="singleCharacterString" type="text" value="a" size="1" maxlength="1">
<input name="emptyString" type="text" value="" size="1" >
<input type="submit" name="Submit" value="Run Tests">
</form>
...
Then, at the top of FormFieldValidatorTest.php
I'd write some php code to:
- recieve those fixed, hard-coded, form values (fixture data).
- pass the fixture data to some tests, and then
- give me back an array of results
To keep things clean I'd put the guts of the test code into a function, call it runTestSuite()
, which will be fleshed-out below.
<?php
//FormFieldValidatorTest.php
if ( $_REQUEST['Submit'] == 'Run Tests' ) {
//perform the tests
$testResultsArray = runTestSuite();
/**
* Lets loop through the results and count the failures
*/
$failureCounter = 0;
foreach ( $testResultsArray as $result ) {
if ( $result['hasFailed'] ) {
$failureCounter++;
}
}
} else {
//no tests were run set the results to null
$testResultsArray = null;
}
?>
<html>
...
I'd add some php into the html part of FormFieldValidatorTest.php
. This should print out the results if they were run (i.e when $testResultsArray
is not identical to null
):
<?php
//FormFieldValidatorTest.php
...
?>
<html>
...
<body>
<form action="" method="get">
...
</form>
<?php if ( $testResultsArray !== null ) : ?>
<h1>Results</h1>
<pre>
<?php if ( $failureCounter > 0 ) : ?>
Failed. <?php echo $failureCounter ?> tests failed.
<?php print_r( $testResultsArray ); ?>
<?php else : ?>
Passed
<?php endif; ?>
</pre>
<?php endif; ?>
</body>
</html>
Then, I'd fill out the guts of my runTestSuite()
function. This is basically two calls to an assertTrue()
function which I flesh out later.
- First, it asserts that it is True that strlen returns
1
when given the singleCharacterString
.
- Then, it asserts that it is True that strlen returns
0
when given the emptyString
.
At the same time it passes a message that describes each test. These are stored in the results array to aid debugging if the tests fail.
/**
* @desc A suite of tests to excercise that troublesome code
* @return array of results for each test done
*/
function runTestSuite() {
/**
* Lets Run some tests
*/
/**
* Create the results array
*/
$testResultsArray = array();
/**
* Test some known data submitted via a form parameter
*/
assertTrue(
( strlen( $_REQUEST['singleCharacterString'] ) == 1 ),
'Expect it to be TRUE that singleCharacterString http parameter
has a stringlength of 1',
$testResultsArray );
/**
* @todo Add more tests here.
*/
assertTrue(
( strlen( $_REQUEST['emptyString'] ) == 0 ),
'Expect it to be TRUE that emptyString http parameter
has a stringlength of 0',
$testResultsArray );
return $testResultsArray;
}
Finally, I flesh out the assertTrue()
function. This basically tests if the first argument has Failed the assertion test. It then appends that result and the message
as a record in the $testResultsArray
.
/**
* @desc A TRUE assertion
* @param mixed - a value that we expect to be true
* @param string - a message to help understand what we are testing
* @param array - a collection of results so far passed by reference
* @return void
*/
function assertTrue( $value, $message, &$testResultsArray ) {
if ( $value ) {
//value is as asserted
$currentResult = array(
'message' => $message,
'hasFailed' => FALSE
);
} else {
//value is not as asserted
$currentResult = array(
'message' => $message,
'hasFailed' => TRUE
);
}
$testResultsArray[] = $currentResult;
}
Now the test script is complete. See it here.
If I run it and see it pass, I can be sure that strlen is co-operating. If its failing I can proceed to:
Modify the misbehaving code until it behaves as expected.
In order to do this you'd probably need to be able to split the misbehaving bit into a seperate function or class which sits in its own file. This makes it re-usable so it can be invoked from both the test code and the live production code.
What we really ought to be testing is the length rule which specifies that only strings of certain lengths are allowed.
So, lets isolate that so we can test it. Maybe a function in a rules library called isCorrectLength() in a file called ServerValidationRules.php
<?php
//ServerValidationRules.php
/**
* @desc boolean function to test string length
* @param string to test
* @param integer defining minimum length required
* @param integer defining maximum length required
* @return TRUE if its the correct length, FALSE otherwise.
*/
function isCorrectLength( $string, $minLength, $maxLength ) {
if ( strlen( $string ) < $minLength ) {
//its too short
return FALSE;
}
if ( strlen( $string ) > $maxLength ) {
//its too long
return FALSE;
}
return TRUE;
}
We can then replace the code in our test to use that instead of the native strlen()
function.
<?php
//FormFieldValidatorTest.php
require_once('ServerValidationRules.php');
...
/**
* @desc A suite of tests to excercise that troublesome code
* @return array of results for each test done
*/
function runTestSuite() {
$testResultsArray = array();
/**
* Test some known data submitted via a form parameter
*/
assertTrue(
//( strlen( $_REQUEST['singleCharacterString'] ) == 1 ),
isCorrectLength( $_REQUEST['singleCharacterString'], 0, 1 ),
'Expect it to be TRUE that singleCharacterString http parameter
has a stringlength of 1',
$testResultsArray );
/**
* @todo Add more tests here.
*/
assertTrue(
//( strlen( $_REQUEST['emptyString'] ) == 0 ),
isCorrectLength( $_REQUEST['emptyString'], 0, 0 ),
'Expect it to be TRUE that emptyString http parameter
has a stringlength of 0',
$testResultsArray );
return $testResultsArray;
}
...
So, we can now replace the code in the production script to:
<title>Terraria Server List</title>
...
<?php
...
if ( $submit ) {
if (!( isCorrectLength( $title, 0, 50 ) )) {
die("Invalid title!");
}
elseif (!( isCorrectLength($ip, 0, 50) )) {
die("Invalid IP!");
}
elseif (!( isCorrectLength( $desc, 0, 10000 ) )) {
die("Invalid description!");
}
elseif (!( isCorrectLength( $email, 0, 100 ) )) {
die("Invalid E-Mail!");
}
else {
//do the insert
}
}
...
Or, better still, move the length rules into a single function so that the logic can re-used by other code (i.e. some code in the test suite).
We could wrap all these up into a function called isServerFieldsValid
and put it into our ServerValidationRules.php
file.
<?php
//ServerValidationRules.php
...
/**
* @desc tests user-submitted fields appending feedback to an array of messages upon failure.
* @param associative array of Posted values keyed by field name
* @param array of messages passed by reference
* @return boolean True if all fields are valid. False otherwise.
*/
function isServerFieldsValid( $values, &$messages ) {
$hasFailed = FALSE;
if (!( isCorrectLength( $values['title'], 1, 50 ) )) {
$hasFailed = TRUE;
$messages[] = "Invalid title!";
}
if (!( isCorrectLength($values['ip'], 1, 50) )) {
$hasFailed = TRUE;
$messages[] = "Invalid IP!";
}
if (!( isCorrectLength( $values['desc'], 1, 1000 ) )) {
$hasFailed = TRUE;
$messages[] = "Invalid description!";
}
if (!( isCorrectLength( $values['email'], 1, 100 ) )) {
$hasFailed = TRUE;
$messages[] = "Invalid E-Mail!";
}
if ( $hasFailed ) {
return FALSE;
}
//else
return TRUE;
}
and then add some some more assertions to the test suite function in our test script.
...
/**
* @desc A suite of tests to excercise that troublesome code
* @return array of results for each test done
*/
function runTestSuite() {
/**
* Initialize the results array
*/
$testResultsArray = array();
...
/**
* Test some variants of possible user submitted data
*
* @todo We ought to invoke an assertFalse() function.
* In meantime, hack it by passing a negated value to assertTrue().
*/
/**
* When given values that are too long,
* expect a validation failure.
*/
$validationMessages = array();
$result = isServerFieldsValid(
array(
'title' => str_repeat( 'a' , 51 ),
'ip' => str_repeat( 'a' , 51 ),
'desc' => str_repeat( 'a' , 1001 ),
//?'type' => str_repeat( 'a' , 1001 ),
'email' => str_repeat( 'a' , 101 ),
),
$validationMessages
);
assertTrue(
(!( $result )),
'Expect it to be TRUE that result is False when given long values',
$testResultsArray );
assertTrue(
in_array( 'Invalid title!', $validationMessages ),
'Expect messages to say "Invalid title!"',
$testResultsArray );
assertTrue(
in_array( 'Invalid IP!', $validationMessages ),
'Expect messages to say "Invalid IP!"',
$testResultsArray );
assertTrue(
in_array( 'Invalid description!', $validationMessages ),
'Expect messages to say "Invalid description!"',
$testResultsArray );
assertTrue(
in_array( 'Invalid E-Mail!', $validationMessages ),
'Expect messages to say "Invalid E-Mail!"',
$testResultsArray );
/**
* When given values that are too short,
* expect a validation failure.
*/
$validationMessages = array();
$result = isServerFieldsValid(
array(
'title' => null,
'ip' => null,
'desc' => null,
'email' => null,
),
$validationMessages
);
assertTrue(
(!( $result )),
'Expect it to be TRUE that result is False when given short values',
$testResultsArray );
assertTrue(
in_array( 'Invalid title!', $validationMessages ),
'Expect messages to say "Invalid title!"',
$testResultsArray );
assertTrue(
in_array( 'Invalid IP!', $validationMessages ),
'Expect messages to say "Invalid IP!"',
$testResultsArray );
assertTrue(
in_array( 'Invalid description!', $validationMessages ),
'Expect messages to say "Invalid description!"',
$testResultsArray );
assertTrue(
in_array( 'Invalid E-Mail!', $validationMessages ),
'Expect messages to say "Invalid E-Mail!"',
$testResultsArray );
/**
* When given values that are the correct length,
* expect a validation success.
*/
$validationMessages = array();
$result = isServerFieldsValid(
array(
'title' => 'a title',
'ip' => 'an ip',
'desc' => 'a desc',
'email' => 'an email',
),
$validationMessages
);
assertTrue(
( $result ),
'Expect it to be TRUE that result is True when given correct values',
$testResultsArray );
assertTrue(
(!( in_array( 'Invalid title!', $validationMessages ) )),
'Expect messages NOT to say "Invalid title!"',
$testResultsArray );
assertTrue(
(!( in_array( 'Invalid IP!', $validationMessages ) )),
'Expect messages NOT to say "Invalid IP!"',
$testResultsArray );
assertTrue(
(!( in_array( 'Invalid description!', $validationMessages ) )),
'Expect messages NOT to say "Invalid description!"',
$testResultsArray );
assertTrue(
(!( in_array( 'Invalid E-Mail!', $validationMessages ) )),
'Expect messages NOT to say "Invalid E-Mail!"',
$testResultsArray );
return $testResultsArray;
}
...
So, the complete test script now looks like this.
So, if that passes, we can modify the production code by replacing all those if (strlen ) { die }
statements with a call to that new, well tested, isServerFieldsValid()
function:
<title>Terraria Server List</title>
...
if ( $submit ) {
$messages = array();
if (!( isServerFieldsValid( $_POST, $messages ) )) {
echo 'Invalid data was submitted:' . PHP_EOL;
foreach( $messages as $message ) {
echo $message . PHP_EOL;
}
exit;
} else {
//do the insert
}
...
Well, that's how I'd deal with code that is not co-operating anyway.
The code :
Note:
It took me a couple of hours to write this answer-up, but very little time to write the actual test. Once you get into the habit, it often takes just a few minutes to scribble out a test and find out why some code is not co-operating.
Tip: You don't need to write your own assertion functions.
See: http://www.simpletest.org/en/start-testing.html