[buddypress-trac] [BuddyPress] #4470: BuddyPress Singleton

buddypress-trac at lists.automattic.com buddypress-trac at lists.automattic.com
Tue Aug 28 07:18:21 UTC 2012


#4470: BuddyPress Singleton
------------------------------+------------------------------
 Reporter:  johnjamesjacoby   |       Owner:  johnjamesjacoby
     Type:  enhancement       |      Status:  new
 Priority:  normal            |   Milestone:  1.7
Component:  Core              |     Version:
 Severity:  normal            |  Resolution:
 Keywords:  has-patch commit  |
------------------------------+------------------------------

Comment (by foxly):

 >> Why a unit test would want to "tamper" with variables outside of the
 boundaries BuddyPress
 >> internally allows defeats the purpose of that test, since it will
 clearly always fail;
 >> rightfully so.

 The reason its imperative that unit tests be able to access and modify
 private methods and variables is so that you can get an acceptable level
 of test coverage (80%) without having to use an astronomically complicated
 test rig.

 Listed below is the class 'misery()' from one of my second year CE
 courses. It's laid out how you're proposing to structure your new BP
 classes: private methods and variables for things that other developers
 aren't supposed to 'mess with'.

 So to write a unit test for this class, all you have to do is run it
 through each possible input state, and check the output is valid. Therein
 lies the name: three of the class's methods are co-dependent (alice, bob,
 and mallory) private methods, and we can't modify the class' state
 variables ($foo, $bar, and $baz).

 http://en.wikipedia.org/wiki/Karnaugh_map

 Thus, we would need to check '''385 different states''' in the unit test,
 and '''8 of those are impossible to validate''' due to aliasing in the
 output.

 {{{
 class misery {

   private var $foo;
   private var $bar;
   private var $baz;

   public function ed($arg){

       $this->foo= $arg;
   }

   public function joe(){

       $result = '';

       if($this->foo){
          $result .= $this->foo;
       }

       if($this->bar){
          $result .= $this->$bar;
       }

       if($this->baz){
          $result .= $this->$baz;
       }

       return $result;

   }

   private function alice() {

      $this->foo++;

      if($this->bar >= 5){
         $this->bar = 0;
      }
      else {
        self::bob();
      }
   }

   private function bob(){

      $this->bar++;

      if($this->bar >= 7){
         $this->bar = 0;
      }
      else {
         self::mallory();
      }
   }

   private function mallory(){

      $this->baz++;

      if($this->baz>= 11){
         $this->baz= 0;
      }
   }

 }
 }}}

 Misery is a simple class used to illustrate a point. In the real world,
 the situation is usually *much* worse.

 None of misery's private methods accept arguments, each of which adds
 another degree of freedom to the equation. Throw in some methods that call
 assorted methods from outside classes and don't use dependency injection,
 and you can end up with a class that's literally *impossible* to test.

 In addition to high complexity and long run times, '''test results from a
 class like misery provide little insight into what's causing a problem'''.
 Imagine misery's three private methods were each 500 lines long. The
 results would show you had a problem "somewhere" in those  1,500 lines of
 code. Or something those lines of code interacted with. Good luck finding
 it.

 If I hadn't intervened in this this ticket, I'm pretty sure there would be
 all sorts of misery-like situations in BP within a few months.

 Now, if you simply didn't set those vars and methods as private, the
 entire class could be tested in just nine operations:

 1) Set $foo to 0 and check the state variables
 2) Set $foo to 4 and check the state variables
 3) Set $bar to 0 and check the state variables
 4) Set $bar to 6 and check the state variables
 5) Set $baz to 0 and check the state variables
 6) Set $baz to 10 and check the state variables
 7) Null the sate variables, run $ed, check the state variables
 8) Set all state variables to 0 and run joe()
 9) Set all the state variables to 2 and run joe()

 And these tests would show *precisely* which method isn't working
 properly.

 '''And that's why you don't set your vars and methods to private.'''

 But really, why take our word for it. Did you know WP doesn't use *any*
 private methods or vars in their classes *either*? ...seriously, just
 ctrl-shift-f 'private var' and 'private function' and be enlightened.

 {{{
 **
  * Helper class to remove the need to use eval to replace $matches[] in
 query strings.
  *
  * @since 2.9.0
  */
 class WP_MatchesMapRegex {

         /**
          * store for matches
          *
          * @access private
          * @var array
          */
         var $_matches;

         // ...

         /**
          * do the actual mapping
          *
          * @access private
          * @return string
          */
         function _map() {
                 $callback = array(&$this, 'callback');
                 return preg_replace_callback($this->_pattern, $callback,
 $this->_subject);
         }

 }
 }}}

 Instead, they mark private methods and variables with a '_' prefix
 $_private_var and _private_method(){}

 I'll give you one guess as to why they're doing that:

 http://unit-tests.trac.wordpress.org/browser/trunk/


 The reason things have to be done this way is because PHP lacks two
 important OOP paradigms: Friends,
 http://en.wikipedia.org/wiki/Friend_function and Multi-inheritance,
 http://en.wikipedia.org/wiki/Multi-inheritance.

 If PHP had friends, we could set vars and methods as protected with
 impunity, then add the unit test rig as a 'friend'. Or if PHP had multi-
 inheritance, we could partition a super-class' methods into sub-classes
 containing atomic groups of methods, unit-test the sub-classes, then
 multi-inherit them into a working super-class.

 But PHP has neither of these, so if we start limiting the visibility of
 things, the only thing we're really doing is making our own life
 difficult.

 The purpose of unit tests is to test the smallest individual units of code
 in an app *one at a time*. In all but the simplest of classes, this means
 testing the individual class methods.

 If you make it impossible to test individual methods by making a class'
 vars and methods private, you are no longer unit testing, you're black-box
 testing http://en.wikipedia.org/wiki/Black-box_testing and you do *not*
 want to be taking on that level of complexity for an open-source project.

 -F

-- 
Ticket URL: <https://buddypress.trac.wordpress.org/ticket/4470#comment:7>
BuddyPress <http://buddypress.org/>
BuddyPress


More information about the buddypress-trac mailing list