On 11/30/2013 06:26 PM, Robert Stoll wrote:
I am glad you coming back to me with this, I find it to be a very interesting 
topic

-----Original Message-----
From: talk-boun...@lists.nyphp.org [mailto:talk-boun...@lists.nyphp.org] On 
Behalf Of Gary A. Mort
Sent: Friday, November 22, 2013 8:46 PM
To: NYPHP Talk
Subject: Re: [nyphp-talk] Why do unit tests not inherit?

Thanks Robert...  I may be misunderstanding something here:

On 11/15/2013 12:57 PM, Robert Stoll wrote:
I am not sure if we talk about the same. Just to avoid misunderstands I am 
going to outline a little bit more what I
meant. I did not mean that each method of a class has to have its one test 
class. But each method of a class A
should
have an own test method in the test class T. And if the method of class A has 
branches, let's say one if-statement,
then
the ideal case would be that you create two test methods in C which covers both 
cases. Once for the case that the
if-condition evaluates to true and once to false.
For example:

class A{
      private $_isActive=false;
      function isActive(){
          return $this->_isActive;
      }
      function foo(){
          $this->_isActive=true;
      }

      function bar(){
          if($isActive){
              doesThis();
          } else{
              doesThat();
          }
      }
}

class T extends SomeTestFramework{
      public function testFoo_Standard_IsActiveSetToTrue (){
          // arrange
          // act
          // assert
      }
      public function testBar_IsActiveIsTrue_DoesThis(){}
I am assuming that there is no unit testing magic which is setting
things, so this method would actually be:

public function testBar_IsActiveIsTrue_DoesThis(){

// create an object $testObject of class A
// call $testObject->foo() to make it active
// Test that $testObject->isActive returns true
// Test that $testObject->bar executes doesThis()

}


public function testBar_IsActiveIsFalse_DoesThat(){

// create an object $testObject of class A
// Test that $testObject->isActive returns false
// Test that $testObject->bar executes doesThat()

}
[Robert Stoll]
That right, that's how my tests would look like more or less with the slight 
difference that I would not test if
isActive is true or false (I would cover it in another test case) but it's ok 
to test that as well.

It's with the above commented steps that I have an issue.  Primarily
because in practice, if someone creates:

Class APrime extend A{}

Then they also create

class TPrime extends SomeTestFramework{

      function bar(){
          if($isActive){
              doesThis();
          } else{
              doesNOTDOThat();
          }

}
[Robert Stoll]
I guess you made a mistake, the new behaviour should be in APrime (as in the 
next paragraph) and not in TPrime:
class APrime extend A{
     function bar(){
         if($isActive){
             doesThis();
         } else{
             doesNOTDOThat();
         }
}

In in TPrime will be:


public function testBar_IsActiveIsTrue_DoesThis(){

// create an object $testObject of class APrime
// call $testObject->foo() to make it active
// Test that $testObject->isActive returns true
// Test that $testObject->bar executes doesThis()

}


public function testBar_IsActiveIsFalse_DoesNOTDOThat(){

// create an object $testObject of class APrime
// Test that $testObject->isActive returns false
// Test that $testObject->bar executes doesNOTDOThat()

}

So everything has been cut and pasted from one to the other.  The only
difference is that APrime calls doesNOTDOThat instead of doesThat.
Testing items where taken from one to the other, with minor editing
changes to half of the new tests to change doesThat to doesNOTDOThat
[Robert Stoll]
I agree, but first of all the question arises, does APrime not already break 
the Liskov Substitution Principle by
invoking a different method (doesNOTDOThat instead of doesThat)?


Mostly the situation I was trying to describe but not get into nitty details are situations where you have external dependencies.

Take storing data to a cache of some sort as a really good example.

The lifecycle of a cache engine could be:
1) Abstact CacheEngine class where you define an isSupported method which will always return false since you can't store items in the abstract class. [Yes, with PHP 5.4+ this would instead be better defined as an interface, but we can't all refuse to support 5.3. :-)]

2) A child class, CacheEngineMemcache where you can run some check to see if Memcache works[on my mind mainly because I just had to create a new class for this in Joomla, CacheEngineGaeMemcache because the Joomla platform checks to see if the Memcache extension is loaded AND the Memcache class exists in it's implementation. Google happens to provide both free and paid usage of Memcache for Google App Engine - but they don't use the PHP Memcache extension, the code is included in their GAE extension to interface with their setup.]

3) A third child class, CacheEngineMemcached which since it shares 90% of the same code as Memcache, subclasses CacheEngineMemcache but modifies isSupported to check for Memcached instead.


So you have 3 classes each implementing the same method[isSupported] which will return either true or false depending on some underlying configuration. Using various PHP extensions it's possible to dynamically load/unload the extension so you can confirm your tests - as long as isSupported always returns true and false.


Things get extended, changed, modified beyond belief. Some day for some reason, someone may decide that for their engine, they may return 3 instead of TRUE for isSupported under some odd situation - maybe to indicate the version of something being supported. Due to the beautiful nature of PHP, when doing simple true/false checks 3 will show up as true, so it is a very easy way to be backwardly compatible and add some extra function.


One answer is, of course, "well, that was a bad design decision. I don't see any reason to design unit tests to prevent bad design decisions."

But to me, the entire point of unit tests IS to prevent compatibility issues, re-introduction of bugs, etc. The whole point of programming in PHP is that PHP programmers are free to code things any way we want because PHP doesn't have strict typing and all that other stuff. It's made to do fast, fun coding and serious coding - so it allows college kids to create things like Facebook as a fun, riddled with holes project, and then refine it when it becomes popular - and it allows one to establish a coding discipline inside a company to maintain that insanely popular franchise.

From what I see with unit testing in practice[open source projects], there is an assumption that the fixed coding structure is going to be followed - and so what actually occurs is that unit tests are only useful for 2-3 years, 5 years at most. At that point, there will be some new "cool" design pattern that everyone will be switching to[because it's fun...not because it is "good coding practice"] and all that "old ugly code" will get bent to fit the new practice and the unit tests don't help much to discover the problems.

Hmm, rambling out loud so I'll end it there.... it's more meta design than anything else... I think part of my issue is I have a different viewpoint then most programmers. I don't tend to come into a project and say "you need to throw out all this old code and do things the new one true way", I try to integrate things. When you integrate, you see problems where design patterns shifted and while a unit test could have detected it - they weren't written to do so.

Whereas if you can restrict a project to a common framework where everything works the same way, and the way you want - then it doesn't make sense to have tests to check to see if things are still following that pattern - everyone follows that pattern or they aren't on the team.

Both are valid approaches...the latter is a lot easier to extend and maintain - but it's a lot more expensive to implement[especially with company mergers where you have to throw away all the IT infrastructure of one company to be on a common platform].

Whereas integration can lead to a maintenance nightmare in the long run - but it keeps the budget down in the short run and avoids spending large sums of money on exploratory projects which may get cancelled.
_______________________________________________
New York PHP User Group Community Talk Mailing List
http://lists.nyphp.org/mailman/listinfo/talk

http://www.nyphp.org/show-participation

Reply via email to