أفضل الممارسات لاختبار الأساليب المحمية مع phpunit [مغلق

StackOverflow https://stackoverflow.com/questions/249664

  •  05-07-2019
  •  | 
  •  

سؤال

لقد وجدت المناقشة على هل تختبر الطريقة الخاصة غنيا بالمعلومات.

لقد قررت ، أنه في بعض الفصول الدراسية ، أريد أن يكون لدي طرق محمية ، لكن اختبارها. بعض هذه الطرق ثابتة وقصيرة. نظرًا لأن معظم الأساليب العامة تستفيد منها ، فمن المحتمل أن أتمكن من إزالة الاختبارات بأمان لاحقًا. ولكن للبدء بنهج TDD وتجنب تصحيح الأخطاء ، أريد حقًا اختبارها.

فكرت في ما يلي:

  • كائن الطريقة كما هو مشير في إجابة يبدو أن مبالغة لهذا الغرض.
  • ابدأ بالطرق العامة وعندما يتم إعطاء تغطية الكود عن طريق اختبارات المستوى الأعلى ، قم بتحويلها محميًا وإزالة الاختبارات.
  • وراثة فئة مع واجهة قابلة للاختبار تجعل الأساليب المحمية عامة

ما هي أفضل الممارسات؟ هل هناك شيء آخر؟

يبدو أن Junit يغير تلقائيًا الطرق المحمية لتكون عامة ، لكن لم يكن لدي نظرة أعمق عليها. PHP لا يسمح بذلك عبر انعكاس.

هل كانت مفيدة؟

المحلول

إذا كنت تستخدم php5 (> = 5.3.2) مع phpunit ، يمكنك اختبار أساليبك الخاصة والمحمية باستخدام الانعكاس لتعيينها للجمهور قبل إجراء اختباراتك:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}

نصائح أخرى

يبدو أنك تدرك بالفعل ، لكنني سأقوم فقط بإعادة صياغته على أي حال ؛ إنها علامة سيئة ، إذا كنت بحاجة إلى اختبار الطرق المحمية. الهدف من اختبار الوحدة ، هو اختبار واجهة الفصل ، والطرق المحمية هي تفاصيل التنفيذ. ومع ذلك ، هناك حالات يكون فيها منطقيًا. إذا كنت تستخدم الميراث ، فيمكنك رؤية الفئة الفائقة على أنها توفر واجهة للفئة الفرعية. لذلك هنا ، يجب عليك اختبار الطريقة المحمية (ولكن لم خاص واحد). الحل لهذا ، هو إنشاء فئة فرعية لغرض الاختبار ، واستخدام هذا لفضح الأساليب. على سبيل المثال:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

لاحظ أنه يمكنك دائمًا استبدال الميراث بالتكوين. عند اختبار التعليمات البرمجية ، يكون من الأسهل كثيرًا التعامل مع التعليمات البرمجية التي تستخدم هذا النمط ، لذلك قد ترغب في التفكير في هذا الخيار.

Teastburn لديه النهج الصحيح. حتى أبسط هو استدعاء الطريقة مباشرة وإعادة الإجابة:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        $method->setAccessible(true);
        return $method->invokeArgs($obj, $args);
    }
}

يمكنك استدعاء هذا ببساطة في اختباراتك بواسطة:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );

أود أن أقترح تباينًا طفيفًا لـ GetMethod () المحددة في إجابة Uckelman.

يغير هذا الإصدار getMethod () عن طريق إزالة القيم المرمزة وتبسيط الاستخدام قليلاً. أوصي بإضافته إلى فئة phpunitutil الخاصة بك كما هو الحال في المثال أدناه أو إلى فئة phpunit_framework_testcase-extending (أو ، أفترض ، على الصعيد العالمي إلى ملف phpunitutil الخاص بك).

نظرًا لأن MyClass يتم إنشاء مثيل له على أي حال ، ويمكن لـ ReflectionClass أن يأخذ سلسلة أو كائن ...

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method;
    }
    // ... some other functions
}

لقد قمت أيضًا بإنشاء وظيفة الاسم المستعار GetProtectedMethod () لتكون صريحًا ما هو متوقع ، لكن هذا الأمر متروك لك.

هتافات!

أعتقد أن Troelskn قريبة. سأفعل هذا بدلاً من ذلك:

class ClassToTest
{
   protected testThisMethod()
   {
     // Implement stuff here
   }
}

ثم ، قم بتنفيذ شيء مثل هذا:

class TestClassToTest extends ClassToTest
{
  public testThisMethod()
  {
    return parent::testThisMethod();
  }
}

يمكنك بعد ذلك تشغيل الاختبارات الخاصة بك مقابل TestClasStotest.

يجب أن يكون من الممكن إنشاء فئات التمديد هذه تلقائيًا عن طريق تحليل الرمز. لن أتفاجأ إذا كان Phpunit يقدم بالفعل مثل هذه الآلية (على الرغم من أنني لم أتحقق).

سأرمي قبعتي في الحلبة هنا:

لقد استخدمت الاختراق __call بدرجات مختلطة من النجاح. كان البديل الذي توصلت إليه هو استخدام نمط الزائر:

1: إنشاء فئة stdclass أو مخصصة (لفرض نوع)

2: برايم مع الطريقة والوسائط المطلوبة

3: تأكد

4: حقنها في الفصل الذي ترغب في اختباره

5: SUT يضخ نتيجة التشغيل في الزائر

6: تطبيق شروط الاختبار الخاصة بك على سمة نتيجة الزائر

يمكنك بالفعل استخدام __call () بطريقة عامة للوصول إلى الأساليب المحمية. لتكون قادرًا على اختبار هذه الفئة

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

يمكنك إنشاء فئة فرعية في exampletest.php:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

لاحظ أن طريقة __call () لا تشير إلى الفئة بأي شكل من الأشكال حتى تتمكن من نسخ ما ورد أعلاه لكل فئة مع طرق محمية تريد اختبارها وتغيير إعلان الفصل. قد تكون قادرًا على وضع هذه الوظيفة في فئة قاعدة مشتركة ، لكنني لم أجربها.

الآن تختلف حالة الاختبار نفسها فقط في المكان الذي تقوم فيه ببناء الكائن ليتم اختباره ، مع تبديل مثال على سبيل المثال.

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

أعتقد أن PHP 5.3 يتيح لك استخدام الانعكاس لتغيير إمكانية الوصول إلى الأساليب مباشرة ، لكنني أفترض أنه يتعين عليك القيام بذلك لكل طريقة على حدة.

أقترح اتباع الحل البديل لـ "Henrik Paul" عن الحلول/الفكرة :)

أنت تعرف أسماء الأساليب الخاصة لفصلك. على سبيل المثال ، هم مثل _add () ، _edit () ، _delete () إلخ.

وبالتالي عندما تريد اختباره من جانب اختبار الوحدة ، ما عليك سوى استدعاء الأساليب الخاصة عن طريق البادئة و/أو اللاحقة مشترك Word (على سبيل المثال _addphpunit) بحيث تسمى طريقة __call () (نظرًا لأن الطريقة _addphpunit () غير موجودة) لفئة المالك ، فأنت فقط تضع الكود اللازم في طريقة __call () لإزالة كلمة/spiled/s facexed (phpunit ) ثم لاستدعاء تلك الطريقة الخاصة الاستنتاج من هناك. هذا هو استخدام جيد آخر للطرق السحرية.

حاول.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top