Tiny Unit Tester

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

I think the bare bones approach is fairly valid. I use unit tests, I like unit tests, but it takes a lot of willpower (or maybe a dense brain ;)) to get over the time needed to implement them when starting to learn their use. I think this is probably the main block to more widespread adoption of unit testing. There are still guys out there working on stuff from Java, Perl, and who knows where else all because they can't take the time investment unit testing takes up. When you see Perl's Test::More getting ported to PHP its a sign Unit Testing is being resisted. All the Agile Dev hype, yet still not much of an impact in PHP for regular users...

I must admit, it doesn't help when one of the most popular PHP unit testing tutorials (Simpletest Log tut) fails under the current SimpleTest releases since its out of date. No doubt Marcus is really busy (how many projects does the guy run again?) but I hear about that almost as much as the time investment issues.

I think the simple UT above by aborint is pretty good. I think any testing is good anyways, but a UT similar method at least lowers the barrier for new testers to get started and see what testing (even without the whole hog Simpletest/PHPUnit frameworks) is about and why it works.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Maugrim_The_Reaper wrote: but a UT similar method at least lowers the barrier for new testers to get started and see what testing (even without the whole hog Simpletest/PHPUnit frameworks) is about and why it works.
Hey thanks for the comments. I agree, that's why I wanted to do something really simple to give unit testing newbies a taste. I have found that once a unit test finds a bug for you, you are a little hooked. But I also agree that the pain of creating good test coverage for a real project is miserable. Unfortunately the people who create the unit testers are test junkies who seem to enjoy the pain. ;)

I took a look at my code an thought I would simplify it even more by combining the unit tester and the reporter. It would almost always be used that way anyway. The all in one design means you only need to create one object -- the tester. If you wanted to do different reporting you could just inherit the UnitTester. I renamed things a little to make it more like other unit testers, and exposed pass() and fail() methods.

Code: Select all

class UnitTester {
	var $_passes = 0;
	var $_fails = 0;
	var $_test_stack = array();
	var $_trace_pos = 0;
	
	function assertTrue($result) {
		if ($result) {
			$this->pass();
		} else {
			$this->_trace_pos = 1;
			$this->fail();
		}
	}

	function assertFalse($result) {
		if ($result) {
			$this->_trace_pos = 1;
			$this->fail();
		} else {
			$this->pass();
		}
	}

	function pass() {
		++$this->_passes;
	}

	function fail() {
		++$this->_fails;
		$test_data = debug_backtrace();
		$this->_test_stack[] = $test_data[$this->_trace_pos];
		$this->_trace_pos = 0;
	}

	var $passed_heading_style = 'color: white; background-color: green; margin-top: 2; padding: 4 4 4 4;';
	var $failed_heading_style = 'color: white; background-color: red; margin-top: 2; padding: 4 4 4 4;';
	var $failed_test_style = 'color: white; background-color: red; margin-top: 2; padding-left: 4;';

	function run() {
		if ($this->_fails > 0) {
			echo '<div style="' . $this->failed_heading_style . '">Failed ' . $this->_fails . ' of ' . ($this->_fails + $this->_passes) . ' tests. </div>';
			$n = 1;
			foreach ($this->_test_stack as $this_data) {
				$message = $n++ . ". Failed: {$this_data['function']} on line {$this_data['line']} of file {$this_data['file']}. ";
				echo '<div style="' . $this->failed_test_style . '">' . $message . '</div>';
			}
		} else {
			echo '<div style="' . $this->passed_heading_style . '">Passed ' .$this->_passes . ' tests.</div>';
		}
	}
}
And to run it:

Code: Select all

include 'TinyTest.php';

$tester = new UnitTester();
// these tests pass
$tester->assertTrue(1 == 1);
$tester->assertFalse(1 == 2);
// these tests fail (comment them to see the passed output)
$tester->assertTrue(1 == 2);
$tester->assertFalse(1 == 1);

$tester->run();
Do you think this simpler one is better? Or the first one with a separate reporter? Any comments or improvements?
Last edited by Christopher on Wed Apr 26, 2006 1:07 am, edited 1 time in total.
(#10850)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

If this is going to be a temporary solution, I think keeping it as simple as possible is a good thing. I noticed, that you omitted assertEqual(). Why?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Ambush Commander wrote:If this is going to be a temporary solution, I think keeping it as simple as possible is a good thing. I noticed, that you omitted assertEqual(). Why?
If you look at unit testers they usually only provide assertTrue() and assertFalse() at the lowest level, then build all the other asserts on top of these. I left is out because:

Code: Select all

$tester->assertEqual(1, 1);

// is the same as

$tester->assertTrue(1 == 1);
(#10850)
lastcraft
Forum Commoner
Posts: 80
Joined: Sat Jul 12, 2003 10:31 pm
Location: London

Post by lastcraft »

Hi...
Maugrim_The_Reaper wrote: I must admit, it doesn't help when one of the most popular PHP unit testing tutorials (Simpletest Log tut) fails under the current SimpleTest releases since its out of date.
Point taken! I am planning a major tutorial and doc. rewrite in the run up to the 1.0.1 release.

It's a poor excuse, but SimpleTest kind of caught me out. If I ever got 300 downloads a month I was going to be pretty happy 8O. I've been struggling to catch up ever since, mostly with a constantly changing PHP syntax and minutia of HTTP.
Maugrim_The_Reaper wrote: No doubt Marcus is really busy (how many projects does the guy run again?) but I hear about that almost as much as the time investment issues.
Nag me again in a month or two. I'm actually cutting back on projects, and forever looking for help with the ones I've got, but I'll be supporting SimpleTest as long as it has a user base. That includes the surrounding tutorials.

Regarding TinyTester, how about stripping it down some more...?

Code: Select all

<?php
ensure(strtolower('Hello') == 'hello');
?>
I am wondering if you cannot let the destruction of a global object could trigger the totals at the end, and the construction could start the page...

Code: Select all

function ensure($condition) {
    static $tester;
    if (! $tester) {
        $tester = new UnitTester();
    }
    $tester->ensure($condition);
}

class UnitTester {
    function __construct() { ... }  // Paint header
    function __destruct() { ... }   // Paint footer
    function ensure($condition) { ... } // Adapts to HTML or CLI.
}
I am nowhere near a PHP box to test this, so you might not be able to print anything as the program shuts down. If it works then group tests are just includes.

yours, Marcus
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Hi Marcus,
lastcraft wrote:I'm actually cutting back on projects, and forever looking for help with the ones I've got, but I'll be supporting SimpleTest as long as it has a user base. That includes the surrounding tutorials.
Any project that need a specific push? I'd be interested. I have thought about lamplib recently, but I am on hold on the general lib front until the Zend Framework is released.
lastcraft wrote:Regarding TinyTester, how about stripping it down some more...?

I am nowhere near a PHP box to test this, so you might not be able to print anything as the program shuts down. If it works then group tests are just includes.
I'd be interested in you input -- obviously. I really had two goals for this thing:

- First to have something that is a stepping stone to SimpleTest/PHPUnit. I think if testing newbies could see a test find a bug they would get interested in real unit testing. I tried to make the experience similar to using SimpleTest to expidite the transition.

- Second was to have something that was so minimal that you would post it with code in forums to provide a "zero install" test framework as a companion to code.
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Nagging appointment noted ;)
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

timvw wrote:
arborint wrote:
It seems like there needs to be a tool that is almost like a macro-recorder (like record mode in expect) where you go to the pages and it records what they are supposed to look like and what is supposed to be done.
Something like http://www.pushtotest.com/Downloads/features.html would be nice ;)
To me it seems that for user interaction tests Selenium with Selenium Recorder is almost ideal.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Thats what is meant by a unit test...

Ahhhhh... :)

I have never used any unit test software in PHP, but have extensive experience using debugging features in VC++...

It looks like what you are emulating is a simple MFC ASSERT macro...

But instead of inling with your production code, you use a separate system with which you can test your objects, functions, etc...?

Thus keeping your retail code clear of the convoluted test code?

I prefer this method (MFC ASSERTS that is) as you avoid having to work with a third party system, in this case a unit test system/framework, whatever...

It's like using phpDocumentor to write docs instead of firing up Word everytime you add a function, change a function, etc...

Speeds up development...IMHO :)

What else does a unit test framework do?

Cheers :)
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Don't think there is such as thing as a Unit Testing framework, although that may depend on your definition of a framework.

Testing the basically emulating possible (if not all) scenarios pertaining to specific code blocks. True testing guru's actually write the tests first, atleast that's what I've seen in my time, and write the code second. You add a couple lines of code -- re-run the test. If it passes, your golden! Not to mention if you are upgrading your codebase, you may be unaware that the line of code you just commented out was a bug fix for something else, a test would light the bug up as clear as christmas lights.

When starting a test (using TinyTester)

we would start off a method like

Code: Select all

class testingClass 
{
   function isACoolMethod() 
   {
      return true;
   }
}

$tester = new UnitTester();
$tester->assertTrue(testingClass::isACoolMethod());
$tester->run();
and we would test the code,

As we progressively are adding code to this method, we are continually runnign tests to make things are working as expected. This is also useful to make sure unexpected values are caught appropriatly. This next quick test will make sure we are handling invalid data properly.

Code: Select all

class testingClass 
{
   function isACoolMethod($number) 
   {
      return is_numeric($number);
   }
}

$tester = new UnitTester();

$tester->assertTrue(testingClass::isACoolMethod(4));
$tester->assertTrue(testingClass::isACoolMethod(5));

$tester->assertFalse(testingClass::isACoolMethod('fail'));
$tester->assertFalse(testingClass::isACoolMethod('3423fail'));

$tester->run();
It obviously gets much much much more complicated than this, although I hope that clears some things up.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Hockey wrote:What else does a unit test framework do?
Probably the most interesting thing is Mocks. That's where Test Driven Development starts to make sense when you create Mocks of classes you haven't build yet (but generally know the interface of). That allows you to start building the classes with dependencies first, rather than building the plumbing first.
(#10850)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

arborint wrote:
Hockey wrote:What else does a unit test framework do?
Probably the most interesting thing is Mocks. That's where Test Driven Development starts to make sense when you create Mocks of classes you haven't build yet (but generally know the interface of). That allows you to start building the classes with dependencies first, rather than building the plumbing first.
While I agree that establishing a solid interface first is important, determining dependancies before implementation...

I dunno...

by depedancies you mean, it's reliance on other objects, includes, etc???

If so, then dependencies are more of a side effect of your implementation...in which case I would *personally* find that redundant and a time waste...

Also, I always try to keep dependencies to a minimum...passing objects in via function arguments if I can, etc...including required headers, etc in the caller's module as opposed to hard coding a path inside my classes implementation file...

Am I missing something?

Cheers :)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Dependencies also have APIs (usually). Mocks were a Big Deal when I first came across them.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Hockey wrote:While I agree that establishing a solid interface first is important, determining dependancies before implementation...

I dunno...
Determining depencencies is one of the foundations of software design. I don't see how you could produce a design without knowing what the dependencies are.
(#10850)
User avatar
silicate
Forum Newbie
Posts: 7
Joined: Sun Apr 30, 2006 4:39 pm
Location: Toronto

Post by silicate »

Hey there,

I like the idea of the tiny tester - especially the input from Marcus about having a single method call rather than asserts. I would suggest calling it expect though since tests are meant to test your expectations. I am wondering though if this isn't approaching the functionality of assert with a callback defined. I actually use assertions in some of my class contructors to ensure that my expectations about the parameters are met.

Code: Select all

public function __construct($less, $more) {
  assert("$less < $more");
} // end constructor
This is handled by a callback that writes to a debug log and displays no output to the browser. It's kinda cool in that you can turn off the assertions and still have full error and exception support. Maybe if the assertions were enabled by the unit tests and then the callback was used as the entry point similar to the way that Marcus was using it.

Of course I think that your target audience it definitely newbie TDD coders. I personally would get frustrated without being able to Mock up objects that are already tested. For me, the beauty of mocks is that you can have an object that is incredibly complex to instantiate and not worry about it. Using Mock Objects as critics is the best as can merely set up the expected results of method calls and treat it as a little black box because you have tested it's internals in another unit test.

That being said, there are some issues with SimpleTest and type hinting that are sometimes a real pain, but then just handwriting a Mock object is good enough to get your through in most cases. The expectations are something I always have to have on hand since the parameter order seems weird

Testing is as harder than coding in my opinion, since that's where the real design part comes into play. Tests make you think about what you (and your client) want to happen, coding is just pushing data around with maybe a transformation applied on the way. The dependencies - or preferably the relationships - between objects is what gives OOP power.

later,

Matthew.

PS: sup arborint ;)
Post Reply