On Wednesday, 30 May 2012 15:00:08 UTC+2, Ratty wrote:
>
> It's far easier to test since all the logic is in the model and a model 
> test case can exercise all the different scenarios in one hit. 
>
> The test with exceptions would need to be something like... 
>
> function testCategoryNotEmptyDeletion { 
>      ... setup illegal data to throw the right exception but none of the 
> others ... 
>      try { 
>         $this->Category->delete(); 
>         $this->fail('Exception expected'); 
>      } 
>      catch(  CategoryNotEmptyException $cnee ) { 
>          // Pass, This one was expected 
>      } 
>      catch( Exception $e ) { 
>          $this->fail('Unexpected Exception'); 
>      } 
> } 
>
> for each exception that can be thrown making the way they are thrown and 
> the order they are thrown in by beforeDelete() very important. 
>
> say, for example the model had 
>
> CategoryModel::beforeDelete() { 
>      delete or move some related data to allow category deletion 
>      parent::beforeDelete(); 
> } 
>
> MyCategoryModel::beforeDelete() { 
>      ... detect error condition ... 
>      throw new ErrorConditionException('Error detected'); 
>      parent::beforeDelete(); 
> } 
>
> this could be coded as : 
>
> CategoryModel::beforeDelete() { 
>      delete or move some related data to allow category deletion 
>      parent::beforeDelete(); 
> } 
>
> MyCategoryModel::beforeDelete() { 
>      parent::beforeDelete(); 
>      ... detect error condition ... 
>      throw new ErrorConditionException('Error detected'); 
> } 
>
> Which would still pass the test as the exception can be thrown, but the 
> parent has already modified some data. 
>
>
> I totally agree that you could end up checking data twice, but only if 
> you do all the same checks in beforeDelete() as you do in isDeletable(). 
>
> There are some scenarios that should only be possible from the UI like 
> trying to delete a category while it still has items in it. Categories 
> deleted by code would need to remove the items from the category before 
> deleting it and should have a test case to ensure that this happens. 
> beforeDelete() would then have no need to check that condition since it 
> would have been checked by isDeletable(). 
>
>
> I have inherited some code that basically used Exceptions as a form of 
> flow control


I agree that using exceptions for flow control (as gotos) is an awful habit 
that deserves... punishment, I wouldn't say that justifies not using 
exceptions where appropriate - I don't think the examples you're giving 
are analogous.

a model test case can exercise all the different scenarios in one hit
>
>
A test method should test one thing only, writing multiple scenarios in one 
test method is in and of itself a bad habit (of which I'm guilty of doing 
in the past). Using exceptions or not does not IME make testing something 
any harder or not - you either test that an expected exception has been 
thrown or you check some return variable - the amount of work is the same 
either way. 

I get the feeling you're voicing an objection to how phpunit handles 
exceptions with annotations rather than exceptions per-se, and you could if 
you wish just wrap your tests in try/catch blocks and test the type of 
exception thrown anyway.

Here's IMO a better example of when exceptions are appropraite:

try {
    $result = $this->Foo->a();
} catch (Exception $e) {  // It's an exception. *something* is wrong, it's 
not necessarily important what - simply handle it gracefully and inform the 
user.
    $this->setFlash($e->message);
    $this->redirect($this->referer());
}

/**
 * Simulate a complex call to something. all functions in the same class 
for brevity
 */
Class Foo {
    function a() {
        throw new AException("Some A exception");
        ...
        return $this->b();
    }
    function b() {
        throw new BException("Some B exception");
        ...
        return $this->c();
    }
    function c() {
        throw new CException("Some C exception");
        ...
        return $this->d();
    }
    function d() {
        throw new DException("Some D exception");
        ...
        return $this->e();
    }
    function e() {
        throw new DException("Some D exception");
        ...
        return $result;
    }
}

Instead of:

$result = $this->Foo->a();
if (!$result) {
    $this->setFlash("Sorry. somewhere an error occurred");
    $this->redirect($this->referer());
}

/**
 * Simulate a complex call to something. all functions in the same class 
for brevity
 */
Class Foo {
    function a() {
        if ("Some A exception") {
            return false;
        }
        ...
        return $this->b();
    }
    function b() {
        if ("Some B exception") {
            return false;
        }
        ...
        return $this->c();
    }
    function c() {
        if ("Some C exception") {
            return false;
        }
        ...
        return $this->d();
    }
    function d() {
        if ("Some D exception") {
            return false;
        }
        ...
        return $this->e();
    }
    function e() {
        if ("Some E exception") {
            return false;
        }
        ...
        return $result;
    }
}

With the latter - you either need to come up with a new return variable 
format, stash an error message somewhere and retrieve it later - or simply 
accept that you are blind to know where in the nested call things stopped 
and returned false. It's also not easily possible to distinguish between a 
"valid" false return value and some unexpected case. With an exception, you 
can know as much or as little as you want about what happened by checking 
the type of exception and/or the message.

AD

-- 
Our newest site for the community: CakePHP Video Tutorials 
http://tv.cakephp.org 
Check out the new CakePHP Questions site http://ask.cakephp.org and help others 
with their CakePHP related questions.


To unsubscribe from this group, send email to
[email protected] For more options, visit this group at 
http://groups.google.com/group/cake-php

Reply via email to