Unit testing the web
In this post I will try to cover some of the pros and cons about unit testing. Show you how I do it and talk about some of the options. I have to admit, I do not spend a lot of time building unit tests, so you should not use this as a resource on how to do unit tests properly. Rather use this post to start thinking about your projects and what need they have in regards to unit testing.
Why testing
Unit tests is not a guarantee that the code delivers what the customer was asking for, it is not a guarantee that your code is free from bugs, it is a way to validate that the code you wrote today did not wreck any of the code you wrote yesterday. This means that you want to have unit tests on code that is used in a lot of situations and you want to create tests before you write the code. But also when you have to fix one of your bugs, make sure you get a test in for the cause of the error before you go and fix the code.
So you say, I'm just writing a small application. My code does not really have any dependencies on other code. Well, next week or next month or even year, you are bound to extend on the code you wrote today. Then you are going to be happy you did that test script.
Do I really need to?
It is a good thing to do, but if your application is heavy on user interfaces and there are not that many layers between the view and the data source and you are working on a web platform that can be easily modified and you have the administrative rights to correct production code on the fly. Then in many cases it is easier to just deploy and let your users test your code. And when it breaks you can just go in and fix it. This is an approach I use in many of my projects and instead of testing I have put a lot of effort into logging and error reports. So that if any error occurs on my website, I get an email describing every detail of what went wrong. But more about logging in another post. If you on the other hand work on an application that need to work and perhaps depend on code installed on different systems that can not be updated as easily, you might want to spend a little bit more time on the testing side of your application.
What options are there?
For testing the interface of a web application the two major tools are Ranorex and Webdriver. I admit I have not yet built a project where I felt that unit tests of the interface was needed. Even though there are some larger projects that I start to feel that it would have been nice to have it. But at least I have created my own php unit test code in order to test some of the functions in my main classes of the project. As you can see below, my code for running tests are simple and still gets the job done.
// classes/Unit.php
<?php
class Unit {
function test($title, $result, $validation) {
echo $title.' ';
if (String::stripUnicodeChars($result) == String::stripUnicodeChars($validation)) {
echo '<span style="color: #0f0; float: right;">OK</span>';
} else {
$validation = str_replace('<', '<', $validation);
$result = str_replace('<', '<', $result);
echo '<span style="color: #f00; float: right;">FAILED</span><br>
<pre style="font-size: 1em;"><code class="">expect: "' . $validation . '";<br>
result: "' . $result .'";</code></pre><br>';
}
echo '<br>';
}
}
It's only one function that takes a name of the test, the result of the running code and the expected result and if there are any difference then it throws an error. Here is the code I use to implement the tests, and you can see an extract of this page in the top image of this post.
// views/unit/test.php
<div>
<h1>Unit tests</h1>
<h2>Db.php</h2>
<?php
$p = new Post();
$str = $p->buildFieldString($fields=' * ');
Unit::test('Db::buildFieldString($fields=' * ')', $str, '`id`, `title`, `topic_id`, `summary`, `message`, `embed`, `user_id`, `posted`, `tags`, `publish`, `sent`');
$str = $p->buildFieldString($fields='id, name, mail');
Unit::test('Db::buildFieldString($fields='id, name, mail')', $str, '`id`, `name`, `mail`');
$str = $p->getRealName('my_column_name', '');
Unit::test('Db::getRealName('my_column_name', '')', $str, 'My column name');
$str = $p->strToCamel('my_column_name');
Unit::test('Db::strToCamel('my_column_name')', $str, 'myColumnName');
$str = $p->className('my_table');
Unit::test('Db::className('my_table')', $str, 'MyTable');
?>
<h2>String.php</h2>
<?php
$str = String::slashText('"'"', true);
Unit::test('String::slashText('"'"', true)', $str, '"'"');
$str = String::slashText('_ % ?', true);
Unit::test('String::slashText('_ % ?', true)', $str, '_ % ?');
$str = String::likeSafe('_ % ?', true);
Unit::test('String::likeSafe('_ % ?')', $str, '_ % ?');
$str = String::slashText(' ', true);
Unit::test('String::slashText(' ', true)', $str, ' ');
$str = String::slashText(' ', false);
Unit::test('String::slashText(' ', false)', $str, ' ');
$str = String::stripDangerousHtml('<script>sss</script>');
Unit::test('String::stripDangerousHtml('<script>sss</script>')', $str, '');
$str = String::stripDangerousHtml('<!-- a -->');
Unit::test('String::stripDangerousHtml('<!-- a -->')', $str, '');
$str = String::stripDangerousHtml('###a¤¤¤');
Unit::test('String::stripDangerousHtml('###a¤¤¤')', $str, '###a¤¤¤');
$str = String::stripDangerousHtml('<a href="link.com" target="_blank">Länk</a>');
Unit::test('String::stripDangerousHtml('<a href="link.com" target="_blank">Länk</a>')', $str, '<a href="link.com" target="_blank">Länk</a>');
$str = String::stripDangerousHtml('<a href="link.com">Länk</a>');
Unit::test('String::stripDangerousHtml('<a href="link.com">Länk</a>')', $str, '<a href="link.com">Länk</a>');
$str = String::stripDangerousHtml('<br>');
Unit::test('String::stripDangerousHtml('<br>')', $str, '<br>');
$str = String::stripDangerousHtml('<em script="secret code">EM</em>');
Unit::test('String::stripDangerousHtml('<em script="secret code">EM</em>')', $str, 'EM</em>');
$str = String::stripDangerousHtml('<em>EM</em>');
Unit::test('String::stripDangerousHtml('<em>EM</em>')', $str, '<em>EM</em>');
$str = String::stripDangerousHtml("'");
Unit::test('String::stripDangerousHtml("'")', $str, "'");
$str = String::fixLinks('text http://www.google.com text');
Unit::test('String::fixLinks("text http://www.google.com text")', $str, 'text <a href="http://www.google.com" target="_blank">http://www.google.com</a> text');
$str = String::fixLinks('text <a href="http://www.google.com">http://www.google.com</a> text');
Unit::test('String::fixLinks("text <a href="http://www.google.com">http://www.google.com</a> text")', $str, 'text <a href="http://www.google.com" target="_blank">http://www.google.com</a> text');
$str = String::textToHTML('text' . PHP_EOL . 'text');
Unit::test('String::textToHTML('text' . PHP_EOL . 'text')', $str, 'text<br>' . PHP_EOL . 'text');
?>
<h2>Http.php</h2>
<?php
$str = Http::filter_input(INPUT_GET, 'module');
Unit::test('Http::filter_input(INPUT_GET, 'module')', $str, 'unit');
$str = Http::currentURL();
Unit::test('Http::currentURL()', $str, 'http://' . $_SERVER["SERVER_NAME"] . '/index.php?module=unit&view=test');
$str = Http::currentURL('path');
Unit::test('Http::currentURL('path')', $str, 'http://' . $_SERVER["SERVER_NAME"] . '/');
$str = Http::currentURL('base');
Unit::test('Http::currentURL('base')', $str, 'http://' . $_SERVER["SERVER_NAME"]);
?>
<h2>File.php</h2>
<?php
$str = File::getMime('file.doc');
Unit::test('Http::getMime('file.doc')', $str, 'application/msword');
$str = File::getMime('file.1.doc');
Unit::test('Http::getMime('file.1.doc')', $str, 'application/msword');
?>
</div>
So to wrap up, unit tests are good to make sure you do not break your code, and that the code actually does what you had in mind. But there are many other kinds of tests that you also should consider that are equally valuable. Here is a list where you can read more about them.
If you want to digg deeper there is also a complete free course on testing at Guru99
- unit test, software