Data providers in PHPUnit

21 September 2012 10:11 Lennaert van der Linden Algemeen, PHP, Testen

Soms wil je met dezelfde test meerdere invoerwaarden nalopen, of wil je de invoerwaarde gescheiden houden zodat de unit test overzichtelijker is. Met behulp van data providers in PHPUnit is dit eenvoudig te realiseren. In dit artikel wordt een voorbeeld van een dergelijke test gegeven en hoe je deze compacter en beter te begrijpen kunt maken met een dataProvider.

Stel we hebben een functie die we willen testen, genaamd createProduct, die wordt aangeroepen met een data structuur, genaamd ProductDetails, waarbij een aantal velden verplicht is:

class ProductDetails {
  public $name;
  public $price;
  public $weight;
  public $description;
  public $shortDescription;
  public $category;
  public $stock;
}

We zouden dan een test methode kunnen schrijven voor elk verplicht veld:

class CreateProductTest extends PHPUnit_Framework_TestCase {
  public function testCreateProductWithoutMandatoryName() { ... }
  public function testCreateProductWithoutMandatoryPrice() { ... }
  public function testCreateProductWithoutMandatoryDescription() { ... }
  public function testCreateProductWithoutMandatoryCategory() { ... }
  public function testCreateProductWithoutMandatoryStock() { ... }
}

Maar dit maakt de test al snel onoverzichtelijk (we hebben hier alleen het testen op ontbrekende verplichte velden opgenomen), maar leidt ook tot redundantie. Want de implementatie van elke test heeft de volgende structuur:

/** 
 * @expectedException ProductServiceException 
 */
public function testCreateProductWithMissingMandatoryField() {
  $productDetails = new ProductDetails(...);
  $this->object->createProduct($productDetails);
}

Alternatief met dataProvider

Het zou dus een stuk makkelijker zijn als we de test maar één keer hoeven te schrijven en deze vervolgens laten aanroepen met steeds een andere instantie van $productDetails. Dit kan middels de dataProvider annotatie:

class CreateProductTest extends PHPUnit_Framework_TestCase {
  /** 
   * @dataProvider getProductDetailsWithMissingMandatoryFields
   * @expectedException ProductServiceException 
   */
  public function testCreateProductWithMissingMandatoryField(ProductDetails $productDetails) {
    $this->object->createProduct($productDetails);
  }

  public function getProductDetailsWithMissingMandatoryFields() {
    return array(
      array($this->createProductDetails(NULL, 50, 'Watch', 'Gadgets', 100)),
      array($this->createProductDetails('S500x', NULL, 'Watch', 'Gadgets', 100)),
      array($this->createProductDetails('S500x', 50, NULL, 'Gadgets', 100)),
      array($this->createProductDetails('S500x', 50, 'Watch', NULL, 100)),
      array($this->createProductDetails('S500x', 50, 'Watch', 'Gadgets', NULL)),
    );
  }

  private function createProductDetails($name, $price, $description, $category, $stock) { ... }
}

PHPUnit voert automatisch de test uit met elk van de waarden die door de dataProvider worden opgegeven. Elke entry is hiermee een aparte test waarvoor de setUp en tearDown functies worden aangeroepen.

De data provider is een functie die een array oplevert van arrays met aanroep argumenten. Het is ook mogelijk om meerdere argumenten mee te geven aan de testfunctie:

/**
 * @dataProvider getCalculationCases
 */
public function testCalculation($inputA, $inputB, $expectedResult) {
  $this->assertThat($this->object->calculate($inputA, $inputB), $this->equals($expectedResult));
}

public function getCalculationCases() {
  return array(
    array(10, 5, 10),
    array(10, 15, 7.5),
    array(5, 5, 0),
  );
}

De enige andere voorwaarde die aan de dataProvider functie wordt gesteld, is dat deze public moet zijn.

Conclusie

Dataproviders zijn meer dan een handig trucje: ze helpen om tests te schrijven die beter leesbaar en beter te begrijpen zijn. In plaats van verschillende methoden die elk dezelfde test uitvoeren op een ander veld, is er nu een functie die alle tests op een gelijke manier uitvoert.

En doordat de criteria in een aparte functie zijn opgenomen zijn ze ook eenvoudig te controleren op compleetheid door een domeinexpert. De tabel kan eenvoudig worden uitgebreid met extra cases (bijvoorbeeld met twee of drie missende velden), zonder dat we daarvoor nieuwe testfuncties hoeven te schrijven.

Verwijzingen

Be Sociable, Share!

Reageer


zeven × 3 =

RSS feed for comments on this post · TrackBack URI