Qforms unit testing framework

thread: 16 messages  |  last: about 3 years ago  |  started: wednesday, september 13, 2006, 2:11 am pdt


#1  |  Gavin Sinai (London, England) South Africa
Wednesday, September 13, 2006, 2:11 AM PDT

Hi Guys and Girls

First, I should say what a pleasure it is to have a robust and tested data access layer as soon as my datamodel is ready. No need to test that!

I've been thinking about a unit testing framework for the qforms. The testing framework would simulate the page life cycles without doing actual http requests. Suppose I had a qform with code like

<?

class MyForm{

protected function Form_Create()
{
    $this->btnTest = new QButton($this);
    $this->btnTest->AddAction(new QClickEvent(),
                    new QServerAction('btnTest_Click');    
    $this->lblTest = new QLabel($this);
    $this->txtTest = new QTextBox($this);
}

protected function btnTest_Click($formId,$controlId,$parameter)
{
    $this->lblTest->Text=$this->txtTest->Text;
}

}
?>

What I would like to do is to unit test that my btnTest_Click function is doing its job. The unit test function would have to be in a class that inherits the form, because of the convention that form objects are protected.

<?

class MyFormTester extends MyForm{

protected function Test_btnTest_Click()
{

    $this->txtTest->Text="Hello World!";
    MyFormTester::ExecuteAction($this->btnTest,Action:Click);
    assert($this->lblTest->Text=="Hello World!");

}

}
?>

The point about ExecuteAction is that it would trigger a simulation of the page life cycle, excluding serialisation and post variable parsing, but including things like validation and the triggering of all actions associated with QActionControls.

This triggering of all actions is important, because one could have added an action to the button which causes the test to fail. I can't just call btnTest_Click() in the test. Likewise,  the cause of failure could be somewhere else in the lifecycle, for example in the Form_Exit() method.

I believe this would plug into just about any unit testing framework, so you can replace the assert statement with anything relevant to your framework of choice.

#2  |  Mike Ho (San Diego, CA) United States of America Qcodo Administrator
Wednesday, September 13, 2006, 9:16 AM PDT

Gavin, you read my mind.  Many people have asked about integrated unit testing into Qcodo... it's something that I would definitely love to see but just haven't had time to implement it.

Unit Testing, when it comes to web-based application development, needs to happen in two places: at the object level and at the page level.

Object Level testing has always been more or less straightforward and easy enough to understand.  You have objects, objects have methods.  Based on data in the database and/or parameters you pass in to methods, you should reliably get back an expected return parameter and/or certain fields in the database should be modified/updated.

All the framewroks I've seen in the PHP world can more or less handle something like this... and I always thought it'd be nice if we could either implement or integrate it into Qcodo.

Now, page level testing becomes a lot more involved... but it actually becomes possible with QForms... and you've basically addressed exactly the kind of design that I've been hoping to have in Qcodo for page level testing.  This would allow your unit test code to basically simulate a web user doing stuff on the web page (type in “Hello World in the textbox, and then click on the button”).

But finally, we'd want to take this to the next level... and basically have a directory (e.g. /includes/test_scripts) where you can put all your test objects (both page level and object level tests).  And then finally, (similar to codegen), we would have a QTester object (which would be driven by a front-end run_wwwroot/_devtools/run_tests.php web page script and _devtools.cli/run_tests.cli command line script) which would go through all the objects in /includes/test_scripts and run each object's test.  For each test, there may be a startup and shutdown procedure PER OBJECT (e.g. on startup create a row in the project table with ID 5, Name “Testing Project”... on shutdown, delete the row in the project table with ID 5 and 6).

If something like this was in place, I feel it would make qcodo the first framework that supports true test-driven development for web-based applications.  Is this something that you have time to and would be willing to take on?  =)

#3  |  Gavin Sinai (London, England) South Africa
Wednesday, September 13, 2006, 10:53 AM PDT

Hi Mike

Glad we are on the same wave length.

I am pleased to report that I have a crude version of this working. I have emailed you the code.

The unit testing simulators are implemented in inside a QForm.inc.

It works with my changes to the validation architecture. As such, the attachment zip includes the changes to QFormBase and QControlBase.

It was basically a copy/paste/trim exercise. The real work is doing the stuff you have suggested, I am not sure about the schedule I would have to get back to you. But seen as though unit testing is something I really need, I will discuss this with my employers. I am sure they will be happy to oblige.


Regards

Gavin

#4  |  Mike Ho (San Diego, CA) United States of America Qcodo Administrator
Wednesday, September 13, 2006, 1:35 PM PDT

Gavin,

Thanks for the post -- and yes, I got your e-mail.  I'll try and take a look at it when i get a chance. =)

In terms of asking your employer... something to consider/note is that if you want the unit testing code to be put into qcodo core (which would be of great benefit to everyone =), your employer would obviously need to be okay with open sourcing your code, and they'd need to sign off on the Intellectual Property of it saying so.

Just a head's up.

#5  |  FallenJehova (Buenos Aires, Argentina) Argentina
Wednesday, September 13, 2006, 2:48 PM PDT

Hi,

I would like to help in this task!
I'm not a uber coder, but I have some experience with SimpleTest and would like to “test” myself.
I think that Unit testing is a must and would really enjoy to contribute to make it possible to have in Qcodo.

I hope you excuse any language mistakes I should have made.
Cheers,

Matias

#6  |  Gavin Sinai (London, England) South Africa
Thursday, September 14, 2006, 1:50 AM PDT

Its always easy to justify IP: why not give a tiny bit to an open source project that has helped us so much. In addition, any code in qcodo core is maintained by the community and not only by us, and that easy to justify in terms of cost.

Matias: I was thinking of using simple test for this myself. I can email you (or anyone who wants it) the zip file I sent Mike with the basic implementation that facilitates testing. Contact me on gsinai at gmx dot net.

I am aware that email is a terrible way to do collobaritive software development, but it will do for the moment.

And dude: Dont worry about the language.

Regards

Gavin

#7  |  Gavin Sinai (London, England) South Africa
Friday, September 15, 2006, 1:29 AM PDT

I have run unit tests with SimpleTest and its almost trivial to integrate into the framework.

Except that SimpleTest cases need to inherit a class called UnitTestCase, and in my little architecture, you need to inherit the form. Hence you need to write an interface adapter for each test, which amounts to writing two functions per test.

Alternatively you can make your included controls public, but that seems to defeat the purpose of OO to a certain extent.

Idea: Take the SimpleTest code and adapt it so that it works seamlessly.

Here is the code:

<?php
require_once "form.php";
restore_error_handler(); //Don't want to use Qcodo error handler for unit testing
require_once('simpletest/unit_tester.php');
require_once(
'simpletest/reporter.php');

class 
FormTester extends Form
{
    public function 
Test_btnTest_Click(UnitTestCase $tester)
    {
        
$this->txtTest->Text="Hello, World!";
        
Form::ExecuteAction($this->btnTest); 
        
$tester->assertEqual($this->lblTest->Text,"Hello,&nbsp;World!");
    }
    
        


class 
FormTestCase extends UnitTestCase
{
    
    protected 
$Form;
    
    function 
setUp()
    {
        
// Set up the page including "Create_Form()"
        
$this->Form Form::RunTest("FormTester");     
    }
    
    function 
test_btnTest_Click()
    {
        
$this->Form->Test_btnTest_Click($this);
    }
}

$test = &new FormTestCase();
$test->run(new HtmlReporter());

?>

The result is a very nice HTML report of the test.

Gavin.

#8  |  FallenJehova (Buenos Aires, Argentina) Argentina
Friday, September 15, 2006, 6:58 AM PDT

Gavin,

I found this way of adapting simple test easy and straightforward. It seems that with this setting you already have a hack for testing.

Now, going back to Mike words...

But finally, we'd want to take this to the next level... and basically have a directory (e.g. /includes/test_scripts) where you can put all your test objects (both page level and object level tests).  And then finally, (similar to codegen), we would have a QTester object (which would be driven by a front-end run_wwwroot/_devtools/run_tests.php web page script and _devtools.cli/run_tests.cli command line script) which would go through all the objects in /includes/test_scripts and run each object's test.  For each test, there may be a startup and shutdown procedure PER OBJECT (e.g. on startup create a row in the project table with ID 5, Name “Testing Project”... on shutdown, delete the row in the project table with ID 5 and 6).

What would be our workout here?

I have a dirty idea:

1) Have code generator create two kind of tests: Object Tests and Form Tests.
2) The Form Test Case would be generated over the custom Object or Form.
3) The Form Tester would be generated and maybe have some default test over certain code.
4) A Custom Form Tester class will be the place where coders write their tests.

We should also encapsulate Simple Test to follow Qcodo ways.

Well, that was just an impulsive idea posting of an unexperienced young programmer :P.
I believe you two must have something much better in mind.

Cheers.

Matias

#9  |  Gavin Sinai (London, England) South Africa
Friday, September 15, 2006, 8:19 AM PDT

For now, we are doing object tests so we needn't worry about page level tests. That can always come later.

Hey, not a bad idea, I would think code gen is the way to go, using the cache to hold generated code in much the same way as the QSoapService.

If you're not familiar with that, what it does is generate wrapper functions for each of the functions in a “Service” class, and caches the generated code using QCache. Therefore we already have an example in QCodo of a process of reflection and code generation. We just take that code and adapt it to do what you have said.

I am not 100% sure of the exact code generation process but I think that it would be similar to that which you have described.

#10  |  Mike Ho (San Diego, CA) United States of America Qcodo Administrator
Friday, September 15, 2006, 9:25 AM PDT

Actually, code generating tests may not necessarily be all that useful.  Because, assuming that you're generating off of already tested templates, a code generated test won't really reveal anything much.

If you're making modficiations to the templates, then yes, you would want a mechanism to run tests on those modifications.  But at that point, it is probably much more useful / valuable to write a single, more involved test that would test against the templates, or test against all your geneated objects in the temapltes, than to do metacoding/programming to write a template that would use the code generator to generate tests against the generated code.  (that make sense? =)

In code generation-based (and metaprogramming-based in general) frameworks like Qcodo, i feel it's always more valuable to have a single cohesive test object which tests the ORM, itself... and then individual testing objects to test specific business logic that are custom written in your subclasses.



Copyright © 2005 - 2012, Quasidea Development, LLC
This open-source framework for PHP is released under the terms of The MIT License.