PHPUnit ile korunan yöntemleri test etmek için en iyi uygulamalar


287

Özel yöntemi bilgilendirici test ediyor musunuz?

Bazı sınıflarda korunan yöntemlere sahip olmak istediğime karar verdim, ancak bunları test ediyorum. Bu yöntemlerin bazıları statik ve kısadır. Genel yöntemlerin çoğu bunları kullandığından, muhtemelen testleri daha sonra güvenle kaldırabileceğim. Ancak bir TDD yaklaşımı ile başlamak ve hata ayıklamaktan kaçınmak için onları gerçekten test etmek istiyorum.

Aşağıdakileri düşündüm:

  • Yöntem Bir cevapta tavsiye edilen nesne bunun için aşırıya kaçmış gibi görünüyor.
  • Herkese açık yöntemlerle başlayın ve daha yüksek düzey testlerle kod kapsamı verildiğinde, bunları korumalı hale getirin ve testleri kaldırın.
  • Korumalı yöntemleri herkese açık hale getiren test edilebilir bir arayüzle bir sınıfı devralma

Hangisi en iyi uygulama? Başka bir şey var mı?

Görünüşe göre, JUnit korumalı yöntemleri herkese açık olacak şekilde otomatik olarak değiştiriyor, ancak daha derinlemesine bir bakışım yoktu. PHP yansıma yoluyla buna izin vermez .


İki soru: 1. neden sınıfınızın açığa çıkmadığı test işlevselliğini rahatsız etmelisiniz? 2. Test etmeniz gerekiyorsa, neden özeldir?
nad2000

2
Belki de özel bir mülkün doğru bir şekilde ayarlanıp ayarlanmadığını test etmek istiyor ve sadece ayarlayıcı işlevini kullanarak test etmenin tek yolu, özel mülkü kamuya açık hale getirmek ve verileri kontrol etmektir
AntonioCS

4
Ve bu tartışma tarzı ve dolayısıyla yapıcı değil. Yine :)
mlvljr

72
Sitenin kurallarına karşı diyebilirsiniz, ama sadece "yapıcı değil" demek hakarettir.
Andy V

1
@Visser, Kendini aşağılıyor;)
Pacerier

Yanıtlar:


417

PHPUnit ile PHP5 (> = 5.3.2) kullanıyorsanız, testlerinizi çalıştırmadan önce herkese açık olacak şekilde ayarlamak için yansımayı kullanarak özel ve korumalı yöntemlerinizi test edebilirsiniz:

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(...));
  ...
}

27
Sebastians blogunun bağlantısını alıntılamak için: "Yani: Korumalı ve özel niteliklerin ve yöntemlerin test edilmesi mümkün olduğu için bunun" iyi bir şey "olduğu anlamına gelmez." - Sadece akılda tutmak için
edorian

10
Buna karşı çıkardım. Çalışmak için korumalı veya özel yöntemlerinize ihtiyacınız yoksa, bunları test etmeyin.
uckelman

10
Sadece açıklığa kavuşturmak için, bunun çalışması için PHPUnit kullanmanız gerekmez. Ayrıca SimpleTest ya da her neyse çalışır. Cevap hakkında PHPUnit'e bağlı hiçbir şey yok.
Ian Dunn

84
Korumalı / özel üyeleri doğrudan test etmemelisiniz. Sınıfın dahili uygulamasına aittirler ve testle birleştirilmemelidirler. Bu, yeniden düzenlemeyi imkansız hale getirir ve nihayetinde neyin test edilmesi gerektiğini test etmezsiniz. Herkese açık yöntemleri kullanarak dolaylı olarak test etmeniz gerekir. Bunu zor bulursanız, sınıfın kompozisyonuyla ilgili bir sorun olduğundan neredeyse emin olun ve onu daha küçük sınıflara ayırmanız gerekir. Sınıfınızın testiniz için bir kara kutu olması gerektiğini unutmayın - bir şey atıyorsunuz ve bir şey geri alıyorsunuz ve hepsi bu!
gphilip

24
@gphilip Bana göre, protectedyöntem aynı zamanda genel api'nin bir parçasıdır, çünkü herhangi bir üçüncü taraf sınıfı onu genişletebilir ve sihir olmadan kullanabilir . Bu yüzden privatedoğrudan test edilemeyen yöntemler kategorisine sadece yöntemler düştüğünü düşünüyorum . protectedve publicdoğrudan test edilmelidir.
Filip Halaxa

48

Zaten farkında gibi görünüyorsun, ama ben yine de yeniden ifade edeceğim; Korumalı yöntemleri test etmeniz gerekiyorsa, bu kötü bir işarettir. Bir birim testinin amacı, bir sınıfın arayüzünü test etmektir ve korunan yöntemler uygulama detaylarıdır. Bununla birlikte, mantıklı olduğu durumlar vardır. Kalıtım kullanıyorsanız, üst sınıfı alt sınıf için arabirim olarak görebilirsiniz. Yani burada, korunan yöntemi test etmeniz gerekir (Ama asla özel bir yöntem ). Bunun çözümü, test amacıyla bir alt sınıf oluşturmak ve bunu yöntemleri ortaya çıkarmak için kullanmaktır. Örneğin.:

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

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

Kalıtımı her zaman kompozisyonla değiştirebileceğinizi unutmayın. Kodu test ederken, genellikle bu kalıbı kullanan kodla uğraşmak çok daha kolaydır, bu nedenle bu seçeneği düşünmek isteyebilirsiniz.


2
Stuff () öğesini doğrudan genel olarak uygulayabilir ve parent :: stuff () öğesini iade edebilirsiniz. Cevabımı görün. Bugün işleri çok hızlı okuyorum.
Michael Johnson

Haklısın; Korunan bir yöntemi herkese açık bir yöntemle değiştirmek geçerlidir.
troelskn

Bu yüzden kod üçüncü seçeneğimi önerir ve "Kalıtımı her zaman kompozisyonla değiştirebileceğinizi unutmayın." ilk seçeneğimin yönünde ya da refactoring.com/catalog/replaceInheritanceWithDelegation.html
GrGr

34
Kötü bir işaret olduğunu kabul etmiyorum. TDD ve Birim Testi arasında bir fark yaratalım. Birim testi, özel yöntemler imo'yu test etmelidir, çünkü bunlar birimlerdir ve birim test genel yöntemlerinin birim testten faydalanmasıyla aynı şekilde fayda sağlayacaktır.
koen

36
Korumalı yöntemler vardır bir sınıfın arayüzü parçası, onlar sadece uygulama ayrıntıları değildir. Korunan üyelerin tüm noktası, alt sınıfların (kendi başlarına kullanıcılar) sınıf korumasındaki bu korunan yöntemleri kullanabilmeleri içindir. Bunların açıkça test edilmesi gerekiyor.
BT

40

teastburn doğru yaklaşıma sahiptir. Daha da basit olan yöntemi doğrudan aramak ve cevabı döndürmektir:

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);
    }
}

Bunu sadece testlerinizde şu şekilde arayabilirsiniz:

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

1
Bu harika bir örnek, teşekkürler. Yöntem korunmak yerine herkese açık olmalı, değil mi?
valk

İyi bir nokta. Aslında bu yöntemi test sınıflarımı genişlettiğim temel sınıfımda kullanıyorum, bu durumda bu mantıklı. Sınıfın adı burada yanlış olur.
robert.egginton

Teastburn xD
Nebulosar

23

Ben tanımlanan GetMethod hafif bir varyasyonu () önermek istiyorum uckelman cevabı .

Bu sürüm, sabit kodlanmış değerleri kaldırarak ve kullanımı biraz basitleştirerek getMethod () yöntemini değiştirir. Aşağıdaki örnekte olduğu gibi PHPUnitUtil sınıfınıza veya PHPUnit_Framework_TestCase genişletme sınıfınıza (veya genel olarak PHPUnitUtil dosyanıza) eklemenizi öneririm.

MyClass yine de başlatıldığından ve ReflectionClass bir dize veya bir nesne alabilir ...

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
}

Ayrıca beklenen ne açık olmak için getProtectedMethod () bir takma ad işlevi oluşturdum, ama bu size kalmış.

Şerefe!


Reflection Class API'sını kullanmak için +1.
Bill Ortell

10

Bence troelskn yakın. Bunun yerine bunu yapardım:

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

Sonra böyle bir şey uygulayın:

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

Daha sonra TestClassToTest karşı testlerinizi çalıştırın.

Kodu ayrıştırarak bu tür uzantı sınıflarını otomatik olarak oluşturmak mümkün olmalıdır. PHPUnit zaten böyle bir mekanizma sunuyorsa şaşırdım (gerçi kontrol etmedim).


Heh ... öyle görünüyor ki, üçüncü seçeneğinizi kullanın :)
Michael Johnson

2
Evet, bu benim üçüncü seçeneğim. PHPUnit'in böyle bir mekanizma sunmadığından eminim.
GrGr

Bu işe yaramaz, aynı ada sahip ortak bir işleve sahip korumalı bir işlevi geçersiz kılamazsınız.
Koen.

Yanılıyor olabilirim, ama bu yaklaşımın işe yarayacağını sanmıyorum. PHPUnit (şimdiye kadar kullandığım kadarıyla), test sınıfınızın gerçek test işlevselliğini sağlayan başka bir sınıfı genişletmesini gerektirir. Etrafında bir yol olmadığı sürece bu cevabın nasıl kullanılabileceğini görebileceğimden emin değilim. phpunit.de/manual/current/en/…
Cypher

1
FYI bu onl korumalı yöntemler için çalışıyor , özel olanlar için değil
Sliq

5

Şapkamı buradaki yüzüğe atacağım:

__Call kesmek için karışık derecelerde başarı elde ettim. Geldiğim alternatif Ziyaretçi desenini kullanmaktı:

1: bir stdClass veya özel sınıf oluşturun (türü zorlamak için)

2: gerekli yöntem ve argümanlarla hazırlayın

3: SUT'nuzun, ziyaret eden sınıfta belirtilen bağımsız değişkenlerle yöntemi yürütecek bir acceptVisitor yöntemine sahip olduğundan emin olun

4: test etmek istediğiniz sınıfa enjekte edin

5: SUT, operasyonun sonucunu ziyaretçiye enjekte ediyor

6: Test koşullarınızı Ziyaretçinin sonuç özelliğine uygulayın


1
İlginç bir çözüm için +1
jsh

5

Korumalı yöntemlere erişmek için __call () yöntemini genel bir şekilde kullanabilirsiniz. Bu sınıfı test edebilmek

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

ÖrnekTest.php içinde bir alt sınıf oluşturun:

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 () yönteminin sınıfa herhangi bir şekilde başvurmadığını unutmayın, böylece her sınıf için yukarıdakileri sınamak istediğiniz korumalı yöntemlerle kopyalayabilir ve yalnızca sınıf bildirimini değiştirebilirsiniz. Bu işlevi ortak bir temel sınıfa yerleştirebilirsiniz, ancak denemedim.

Şimdi, test senaryosunun kendisi sadece test edilecek nesneyi oluşturduğunuz yerde farklıdır ve Örnek için Expozion'da değiştirilmiştir.

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

PHP 5.3, yöntemlerin erişilebilirliğini doğrudan değiştirmek için yansımayı kullanmanıza izin verdiğine inanıyorum, ancak her yöntem için ayrı ayrı yapmanız gerektiğini varsayıyorum.


1
__Call () uygulaması harika çalışıyor! Oy vermeyi denedim, ancak bu yöntemi test edene kadar oyumu ayarladım ve şimdi SO'daki zaman sınırı nedeniyle oy kullanmama izin verilmiyor.
Adam Franco

Bu call_user_method_array()işlev PHP 4.1.0 ... kullanımından itibaren kullanımdan kaldırılmıştır call_user_func_array(array($this, $method), $args). PHP 5.3.2+ kullanıyorsanız , korumalı / özel yöntemlere ve özniteliklere erişim elde
unutmayın

@nuqqsa - Teşekkürler, cevabımı güncelledim. O zamandan beri Accessibletestleri özel / korumalı özellikleri ve sınıf ve nesnelerin yöntemlerine erişmek için yansıma kullanan genel bir paket yazdım .
David Harkness

Bu kod PHP 5.2.7'de benim için çalışmıyor - __call yöntemi temel sınıfın tanımladığı yöntemler için çağrılmıyor. Belgelendirilmiş bulamıyorum, ama bu davranış PHP 5.3'te değiştiğini tahmin ediyorum (nerede çalıştığını doğruladım).
Russell Davis

@Russell - __call()yalnızca çağıranın yönteme erişimi yoksa çağrılır. Sınıf ve alt sınıfları korunan yöntemlere erişebildiğinden, bunlara çağrı yapılmaz __call(). 5.2.7'de çalışmayan kodunuzu yeni bir soruya gönderebilir misiniz? Yukarıda 5.2'de kullandım ve sadece 5.3.2 ile yansımayı kullanmaya başladım.
David Harkness

2

"Henrik Paul" için geçici çözümü takip etmenizi öneririm :)

Sınıfınızın özel yöntemlerinin adlarını biliyorsunuz. Örneğin _add (), _edit (), _delete () vb.

Bu nedenle, birim testi açısından test etmek istediğinizde, bazı yaygın yöntemleri ön ekleyerek ve / veya ekleyerek özel yöntemleri çağırmanız yeterlidir. , __call () yöntemi çağrıldığında (_addPhpunit () yöntemi çağrılmadığından) sözcükleri (örneğin _addPhpunit) çağırmanız yeterlidir. owner), sınıfın ön ekli / sonlu sözcüğünü kaldırmak için __call () yöntemine gerekli kodu koydunuz ve sonra o çıkarılan özel yöntemi oradan çağırabilirsiniz. Bu, sihirli yöntemlerin bir başka iyi kullanımıdır.

Denemek.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.