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
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Hey silicate, good to see you over here. I modified the code so it would support Marcus' interface. Untested and a little bit of a hack. maybe someone can improve the code -- especially the stack_track part. It should work PHP4 or PHP5, but in PHP4 you will have to call run() explicitly.

Code: Select all

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

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

        function assertFalse($result, $pos=0) {
                if ($result) {
                        $this->_trace_pos = $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 __destruct() {
                if ($this->_fails > 0) {
                        $output = '<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']}. ";
                                $output .= '<div style="' . $this->failed_test_style . '">' . $message . '</div>';
                        }
                } else {
                        $output = '<div style="' . $this->passed_heading_style . '">Passed ' .$this->_passes . ' tests.</div>';
                }
            echo $output;
        }

        function __destruct() {
            if ($this->output == '') {
                $this->run();
            }
        }
}
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

silicate wrote: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
So what would TinyMock look like?
(#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 »

So what would TinyMock look like?
I don't know how tiny it could possibly be. SimpleTest generates mock object definitions through some very interesting reflection, and PHP isn't exactly a reflective language.
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:I don't know how tiny it could possibly be. SimpleTest generates mock object definitions through some very interesting reflection, and PHP isn't exactly a reflective language.
But what if we required the programmer to give a lot of the stuff that would go into just creating a handwritten mock class itself, but it made it a little easier and more standard. That way no reflection is required.
(#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 »

A bit of repetitive code would be required then. Reflection makes things nice because all you have to do is give the constructor the name of the class and it will analyze the class and automatically wire up the functions.
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:A bit of repetitive code would be required then. Reflection makes things nice because all you have to do is give the constructor the name of the class and it will analyze the class and automatically wire up the functions.
I agree. It also depends whether it is used to mock existing classes or not yet written ones.
(#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 »

not yet written ones
But you must have the API, and if you have the API, you can...

Code: Select all

class UnwrittenClass
{
  function frob($woggle) {}
  function sendTo($woggle) {}
  // ...
}
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

You could do something brutal like:

Code: Select all

$mock = new TinyMock('UnwrittenClass') {
$mock->addProperty('foo', 0)
$mock->addMethod('frob', 'woggle')
$mock->addMethod('sendTo', 'woggle', 'froggle')
Or maybe use reflection but much more limited so tricks are not necessary.
(#10850)
User avatar
silicate
Forum Newbie
Posts: 7
Joined: Sun Apr 30, 2006 4:39 pm
Location: Toronto

Post by silicate »

I think that you are on the right track, I think that the usage of the mock really depends on your style of using mock objects. If you are using them as placeholders, then I think this would work well:

Code: Select all

$mock = new TinyMock('UnwrittenClass');
$mock->expect('method_name', array('arguments'), 'return value',[$count=1]);
The thing to remember is that if the class you are mocking is unwritten, you are using the TinyMock as merely a placeholder. You don't need to care about it's methods or properties at all, you can simple describe them using your expectations. It would me more like combining expectOnce with setReturnValue in SimpleTest speak.

The hard part is having the actual interface of the object tested as it interacts with other objects in your domain - Mock as Critic. If you don't do that, then you risk having your tests becoming out of synch with your objects. But then again, this isn't meant to be a do everything for everyone kind of test suite.

Later,

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

Post by Christopher »

You are right and I like your code a lot better than mine. I think the limitations are ok, because the orignial goal if this tiny unit tester was to be so small, obvious and easy to use that you could write code with tests in a forum and include the tester in the post for people to try out your ideas. If it has obvious limitations -- fine -- it may just encourage someone to install a real test suite after seeing how testing works.
(#10850)
User avatar
silicate
Forum Newbie
Posts: 7
Joined: Sun Apr 30, 2006 4:39 pm
Location: Toronto

Post by silicate »

Hey there,

I was playing around with this a little more and was wondering about eval()ing the parameters to assertTrue/assertFalse. I was looking at the failed test output and it seems so unhelpful... It's not even that it's required, but when you have a list of failures seeing a bunch of assertFalse items in a list makes things seem more hazy.

Would this:

1. Failed: assertFalse on line 27 of file ...

be better as:

1. Failed: 2 < 1 on line 27 of file ...

Maybe that's just overcomplicating it though.

Later,

Matthew Purdon.
lastcraft
Forum Commoner
Posts: 80
Joined: Sat Jul 12, 2003 10:31 pm
Location: London

Post by lastcraft »

Hi...
arborint wrote:But what if we required the programmer to give a lot of the stuff that would go into just creating a handwritten mock class itself, but it made it a little easier and more standard. That way no reflection is required.
The original Java mock objects library worked this way. It was a bunch of definitions for hand coding mocks. Everyone except the original poineers hated it. The same team are now the core of JMock, which is totally automatic.

Is it that much code to create a little dynamic proxy that checks it's call against the signature of the real class as it goes? It could just throw exceptions on a mismatch. Most of the SimpleTest code is PHP4 compatibility and helpful error messages. If that were degraded I reckon you could manage something in 100 lines of code. Maybe...

yours, Marcus
Post Reply