Ahhh

This project is not covered by Drupal’s security advisory policy.

This page is a stub.

Introduction

After faithfully using Drupal Simpletest for some time now I grew veiry of some
of its limitations and bugs - it's a great tool I just wanted something else.
Therefore I quickly made this little drupal app that integrates PHPUnit and
Selenium testcases with drupal. I'm sure it got a zillion bugs and edgecases
but it really works for us so far.

Usage

  • Copy sites/all/modules/drunit/default.settings.php to
    sites/all/modules/drunit/settings.php and change the constants to what
    you want.
  • Create a drunit folder in the root of your module folder.
  • Add a file named XXXTest.php to that folder.
  • In that file create a single class called XXXTest that extends either
    Drunit_Framework_TestCase or Drunit_Extensions_SeleniumTestCase. See
    http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html and
    http://www.phpunit.de/manual/current/en/selenium.html for documentation on
    how to write the actual tests.
  • Run the sites/all/modules/drunit/scripts/generate-testsuite.php script to
    generate the configuration file.
  • Start the selenium server if you are using functional testing.
  • Cd to the root of you drupal installation and run "phpunit --configuration
    sites/all/modules/drunit/config/config.xml".
  • Watch your first test fail and TDD away :).

Examples

These are way too long - sorry!

Example 1 - Unit test with PHPUnit

<?php

class RcdilTestStrings {
  static $_1 = '
    <p>Der kan både ses hudreaktioner og luftvejsreaktioner ved allergi, men i de fleste tilfælde er reaktionen i kroppen forskellig, og derfor  forekommer reaktionerne uafhængigt af hinanden. Det betyder, at en
  person med astma ikke har en øget risiko for at udvikle fx kontaktallergi og omvendt.</p>
{ifnode:42,45,48}<h3>Læs mere om de to mest almindelige typer af luftvejssymptomer:</h3>
<ul>
<li><a href="/node/42">Astma</a></li>
<li><a href="/node/45">Høfeber</a></li>
<li><a href="/node/48">Fødevareallergi</a></li>
</ul>{/ifnode}
<p>
  I am a sentence with a {ifnode:4545}<a href="ijeifjeijf">Link</a> inside{/ifnode}.
</p>
    ';
  static $_1yes = '
    <p>Der kan både ses hudreaktioner og luftvejsreaktioner ved allergi, men i de fleste tilfælde er reaktionen i kroppen forskellig, og derfor  forekommer reaktionerne uafhængigt af hinanden. Det betyder, at en
  person med astma ikke har en øget risiko for at udvikle fx kontaktallergi og omvendt.</p>
<h3>Læs mere om de to mest almindelige typer af luftvejssymptomer:</h3>
<ul>
<li><a href="/node/42">Astma</a></li>
<li><a href="/node/45">Høfeber</a></li>
<li><a href="/node/48">Fødevareallergi</a></li>
</ul>
<p>
  I am a sentence with a <a href="ijeifjeijf">Link</a> inside.
</p>
    ';

  static $_1no = '
    <p>Der kan både ses hudreaktioner og luftvejsreaktioner ved allergi, men i de fleste tilfælde er reaktionen i kroppen forskellig, og derfor  forekommer reaktionerne uafhængigt af hinanden. Det betyder, at en
  person med astma ikke har en øget risiko for at udvikle fx kontaktallergi og omvendt.</p>

<p>
  I am a sentence with a .
</p>
    ';

  static $_2 = '<div><p>På de følgende sider kan du læse mere om de arbejdsmiljøfaktorer, som kan have indflydelse på dit arbejde, din
arbejdsglæde og dit velvære - samt finde gode råd om fysisk aflastning
og variation i løbet af arbejdsdagen. Læs bl.a. om:</p>
<ul>
<li><a href="/node/215/">Indretning af arbejdspladsen</a></li>
<li ifnode="216"><a href="/node/216/">Indeklima</a></li>
<li ifnode="216,87454,98234"><a href="/node/216/">Indeklima</a></li>
<li><a href="/node/230/">Arbejde i trafikken</a></li>
<li><a href="/node/223/">Arbejdspladsvurdering (APV)</a></li>
<li><a href="/node/219/">Variation i arbejdsdagen</a></li>
</ul></div>';

  static $_2yes = '<div><p>På de følgende sider kan du læse mere om de arbejdsmiljøfaktorer, som kan have indflydelse på dit arbejde, din
arbejdsglæde og dit velvære - samt finde gode råd om fysisk aflastning
og variation i løbet af arbejdsdagen. Læs bl.a. om:</p>
<ul>
<li><a href="/node/215/">Indretning af arbejdspladsen</a></li>
<li><a href="/node/216/">Indeklima</a></li>
<li><a href="/node/216/">Indeklima</a></li>
<li><a href="/node/230/">Arbejde i trafikken</a></li>
<li><a href="/node/223/">Arbejdspladsvurdering (APV)</a></li>
<li><a href="/node/219/">Variation i arbejdsdagen</a></li>
</ul></div>';

  static $_2no = '<div><p>På de følgende sider kan du læse mere om de arbejdsmiljøfaktorer, som kan have indflydelse på dit arbejde, din
arbejdsglæde og dit velvære - samt finde gode råd om fysisk aflastning
og variation i løbet af arbejdsdagen. Læs bl.a. om:</p>
<ul>
<li><a href="/node/215/">Indretning af arbejdspladsen</a></li>
<li><a href="/node/230/">Arbejde i trafikken</a></li>
<li><a href="/node/223/">Arbejdspladsvurdering (APV)</a></li>
<li><a href="/node/219/">Variation i arbejdsdagen</a></li>
</ul></div>';

  static $_3 = '<div><h2>Hvorfor opstår høfeber?</h2>
<p>Man ved ikke præcis, hvorfor høfeber opstår, men arv spiller en rolle. Ligesom {ifnode_delink:42}<a href="/node/42">astma</a>{/ifnode_delink} og {ifnode_delink:440}<a href="/node/440">atopisk eksem</a>{/ifnode_delink} forekommer høfeber hyppigere i nogle familier
  end i andre, men man kan sagtens få sygdommen, uden at være arveligt disponeret.</p>
<p>Der synes muligvis at være en sammenhæng mellem spædbørns udsættelse for pelsdyr og udviklingen af høfeber. Denne sammenhæng ses dog kun hos børn, som er arveligt disponeret.</p></div>';

  static $_3yes = '<div><h2>Hvorfor opstår høfeber?</h2>
<p>Man ved ikke præcis, hvorfor høfeber opstår, men arv spiller en rolle. Ligesom <a href="/node/42">astma</a> og <a href="/node/440">atopisk eksem</a> forekommer høfeber hyppigere i nogle familier
  end i andre, men man kan sagtens få sygdommen, uden at være arveligt disponeret.</p>
<p>Der synes muligvis at være en sammenhæng mellem spædbørns udsættelse for pelsdyr og udviklingen af høfeber. Denne sammenhæng ses dog kun hos børn, som er arveligt disponeret.</p></div>';

  static $_3no = '<div><h2>Hvorfor opstår høfeber?</h2>
<p>Man ved ikke præcis, hvorfor høfeber opstår, men arv spiller en rolle. Ligesom astma og atopisk eksem forekommer høfeber hyppigere i nogle familier
  end i andre, men man kan sagtens få sygdommen, uden at være arveligt disponeret.</p>
<p>Der synes muligvis at være en sammenhæng mellem spædbørns udsættelse for pelsdyr og udviklingen af høfeber. Denne sammenhæng ses dog kun hos børn, som er arveligt disponeret.</p></div>';

  static $_4 = "<div>
    <p ifnode_delink='345'><a href='ojwoeifj'>Some test</a></p>
    <p ifnode_delink='345,454,234'><a href='ojwoeifj'>Some test</a></p>
    <p>More text</p>
    </div>";

  static $_4yes = "<div>
    <p><a href='ojwoeifj'>Some test</a></p>
    <p><a href='ojwoeifj'>Some test</a></p>
    <p>More text</p>
    </div>";

  static $_4no = "<div>
    <p>Some test</p>
    <p>Some test</p>
    <p>More text</p>
    </div>";

  static $_5 = '<div><p>Der findes fire typer næringsstoffer i vores mad og drikkevarer, der giver energi til kroppen:</p>
    <p>Spiser du sundt, varieret og følger den anbefalede energifordeling i maden, så vil dit behov for <a href="/node/79">vitaminer</a><a href="/node/78"> og </a><a href="/node/78">mineraler</a> som regel være dækket. Spiser du derimod mad med et højt indhold af sukker eller fedt siger man, at du får mange "tomme kalorier". Det er fordi, at de madvarer har et relativt lavt indhold af vitaminer og mineraler.</p>
    {ifnode_delink:83,379}<p>Man taler om <a href="/node/83">de 8 kostråd</a> og <a href="/node/379">gode spisevaner</a>, og følger du disse, så er der taget højde for den optimale energifordeling.</p>{/ifnode_delink}</div>';

  static $_5yes = '<div><p>Der findes fire typer næringsstoffer i vores mad og drikkevarer, der giver energi til kroppen:</p>
    <p>Spiser du sundt, varieret og følger den anbefalede energifordeling i maden, så vil dit behov for <a href="/node/79">vitaminer</a><a href="/node/78"> og </a><a href="/node/78">mineraler</a> som regel være dækket. Spiser du derimod mad med et højt indhold af sukker eller fedt siger man, at du får mange "tomme kalorier". Det er fordi, at de madvarer har et relativt lavt indhold af vitaminer og mineraler.</p>
    <p>Man taler om <a href="/node/83">de 8 kostråd</a> og <a href="/node/379">gode spisevaner</a>, og følger du disse, så er der taget højde for den optimale energifordeling.</p></div>';

  static $_5no = '<div><p>Der findes fire typer næringsstoffer i vores mad og drikkevarer, der giver energi til kroppen:</p>
    <p>Spiser du sundt, varieret og følger den anbefalede energifordeling i maden, så vil dit behov for <a href="/node/79">vitaminer</a><a href="/node/78"> og </a><a href="/node/78">mineraler</a> som regel være dækket. Spiser du derimod mad med et højt indhold af sukker eller fedt siger man, at du får mange "tomme kalorier". Det er fordi, at de madvarer har et relativt lavt indhold af vitaminer og mineraler.</p>
    <p>Man taler om de 8 kostråd og gode spisevaner, og følger du disse, så er der taget højde for den optimale energifordeling.</p></div>';

  static $_6before = '<div><p>{ifnode_delink:83,379}Man taler om <a href="/node/83">de 8 kostråd</a> og <a href="/node/379">gode spisevaner</a>, og følger du disse, så er der taget højde for den optimale energifordeling.{/ifnode_delink}</p></div>';

  static $_6after = '<div><p>Man taler om <a href="/node/83">de 8 kostråd</a> og gode spisevaner, og følger du disse, så er der taget højde for den optimale energifordeling.</p></div>';
}


class RcdilTest extends Drunit_Framework_TestCase {
  function redefineFunction($value) {
    $value = $value ? 'TRUE' : 'FALSE';
    runkit_function_redefine('rcdil_nodes_are_published', '', "return $value;");
  }

  function getDomNode($html) {
    $doc = DOMDocument::loadXML($html);
    return $doc->getElementsByTagName('node')->item(0);
  }

  function testEmptyReturnsEmpty() {
    $html = rcdil_process_html('');
    $this->assertTrue(empty($html));
  }

  function testIfnodeTagOnNodePublished() {
    $this->redefineFunction(TRUE);
    $this->assertEquals(
      RcdilTestStrings::$_1yes,
      rcdil_process_html(RcdilTestStrings::$_1)
    );
  }

  function testIfnodeTagOnNodeNotPublished() {
    $this->redefineFunction(FALSE);
    $this->assertEquals(
      RcdilTestStrings::$_1no,
      rcdil_process_html(RcdilTestStrings::$_1)
    );
  }

  function testIfnodeAttributeOnNodePublished() {
    $this->redefineFunction(TRUE);
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_2yes,
      rcdil_process_html(RcdilTestStrings::$_2)

    );
  }

  function testIfnodeAttributeOnNodeNotPublished() {
    $this->redefineFunction(FALSE);
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_2no,
      rcdil_process_html(RcdilTestStrings::$_2)

    );
  }

  function testDelinkTagOnNodePublished() {
    $this->redefineFunction(TRUE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 'return TRUE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_3yes,
      rcdil_process_html(RcdilTestStrings::$_3)
    );
  }

  function testDelinkTagOnNodeNotPublished() {
    $this->redefineFunction(FALSE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 'return FALSE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_3no,
      rcdil_process_html(RcdilTestStrings::$_3)
    );
  }

  function testDelinkAttributeOnNodePublished() {
    $this->redefineFunction(TRUE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 'return TRUE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_4yes,
      rcdil_process_html(RcdilTestStrings::$_4)
    );
  }

  function testDelinkAttributeOnNodeNotPublished() {
    $this->redefineFunction(FALSE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 'return FALSE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_4no,
      rcdil_process_html(RcdilTestStrings::$_4)
    );
  }

  function testDelinkTagWorksOutsidePtagOnNodesPublished() {
    $this->redefineFunction(TRUE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 'return TRUE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_5yes,
      rcdil_process_html(RcdilTestStrings::$_5)
    );
  }

  function testDelinkTagWorksOutsidePtagOnNodesNotPublished() {
    $this->redefineFunction(FALSE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 'return FALSE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_5no,
      rcdil_process_html(RcdilTestStrings::$_5)
    );
  }

  function testDelinkWorksSeperatelyOnEachATag() {
    $this->redefineFunction(FALSE);
    runkit_function_redefine('rcdil_node_is_published', '$nid', 
      'if ($nid==83) return TRUE; else return FALSE;');
    $this->assertXmlStringEqualsXmlString(
      RcdilTestStrings::$_6after,
      rcdil_process_html(RcdilTestStrings::$_6before)
    );
  }
}

Example 2 - In-browser testing with Selenium.

<?php

// Constants.
$rasl_url = DRUNIT_URL_BASE;
define('RASLTEST_URL_LOGIN', 
  "http://XXX.TLD/?id=119&return_url=$rasl_url/user/front");
define('RASL_TEST_URL_LOGOUT', 'http://XXX.TLD/?id=119');
define('RASL_TEST_URL_FRONT', "$rasl_url/user/front");
define('RASL_TEST_DUPLICATE_UID', 16);

// Testclass. 
class RaslTest extends Drunit_Extensions_SeleniumTestCase {

  private $users = array(
    array('name' => 'USERNAME1', 'pass' => 'XXXX', 
     'realname' => 'test test', 'ahash' => 'XXXX'),  
    array('name' => 'USERNAME2', 'pass' => 'XXXX', 
     'realname' => 'test test', 'ahash' => 'XXXX'),  
  );

  protected function setUp() {
    $this->setBrowserUrl(DRUNIT_URL_BASE . '/user/front');
    parent::setUp();
  }

  /**
   * Deletes all test users and returns number of deleted users.
   * 
   * @return int
   */
  private function deleteTestUsers() {
    XXXXXXXXXXX;
  }

  private function createDuplicateEmail() {
    db_query('UPDATE {users} SET mail="%s" WHERE uid=%d', 
      $this->users[0]['name'], RASL_TEST_DUPLICATE_UID);
    $this->assertEquals(db_affected_rows(), 1);
  }

  private function createDuplicateUsername() {
    db_query('UPDATE {users} SET name="%s" WHERE uid=%d', 
      $this->users[0]['name'], 
      RASL_TEST_DUPLICATE_UID);
    $this->assertEquals(db_affected_rows(), 1);
  }

  private function removeDuplicates() {
    db_query('UPDATE {users} SET name="%s" WHERE uid=%d', 
      'rune_kg',
      RASL_TEST_DUPLICATE_UID);
    db_query('UPDATE {users} SET mail="%s" WHERE uid=%d', 
      'rune@prescriba.com',
      RASL_TEST_DUPLICATE_UID);
  }

  private function loginWithUser($testuser) {
    $this->click("//a[@id='blogin']/img");
    $this->type("username", $testuser['name']);
    $this->type("password", $testuser['pass']);
    $this->click("//input[@name='sa' and @type='image' and
       @onclick='submitFrmDataLogin(); return false;']");
    $this->waitForPageToLoad("5000");
  }

  /**
   * Test helpers begin.
   */
  public function tLoginUser0() {
    $user = $this->users[0];
    $this->open(RASL_TEST_URL_FRONT);
    $this->assertEquals(RASLTEST_URL_LOGIN, $this->getLocation());
    $this->loginWithUser($user);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
    $this->click("menu-logout");
    $this->waitForPageToLoad("3000");
    $this->assertEquals(RASL_TEST_URL_LOGOUT, $this->getLocation());
  }

  public function tLoginUser1() {
    $user = $this->users[1];
    $this->open(RASL_TEST_URL_FRONT);
    $this->assertEquals(RASLTEST_URL_LOGIN, $this->getLocation());
    $this->loginWithUser($user);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
    $this->click("menu-logout");
    $this->waitForPageToLoad("3000");
    $this->assertEquals(RASL_TEST_URL_LOGOUT, $this->getLocation());
  }

  private function logUser1InWithHash() {
    $user = $this->users[0];
    $url = RASL_TEST_URL_FRONT . '?a=' . $user['ahash'];
    $this->open($url);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
    $this->click("menu-logout");
    $this->waitForPageToLoad("3000");
    $this->assertEquals(RASL_TEST_URL_LOGOUT, $this->getLocation());
  }

  private function logUser2InWithHash() {
    $user = $this->users[1];
    $url = RASL_TEST_URL_FRONT . '?a=' . $user['ahash'];
    $this->open($url);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
    $this->click("menu-logout");
    $this->waitForPageToLoad("3000");
    $this->assertEquals(RASL_TEST_URL_LOGOUT, $this->getLocation());
  }

  private function logUser1InWithHashNoLogout() {
    $user = $this->users[0];
    $url = RASL_TEST_URL_FRONT . '?a=' . $user['ahash'];
    $this->open($url);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
  }

  private function logUser2InWithHashNoLogout() {
    $user = $this->users[1];
    $url = RASL_TEST_URL_FRONT . '?a=' . $user['ahash'];
    $this->open($url);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
  }

  public function tLoginUser0NoLogut($logout = TRUE) {
    $user = $this->users[0];
    $this->open(RASLTEST_URL_LOGIN);
    if ($logout) {
      $this->click("//a[@id='blogout']/img");
      $this->waitForPageToLoad("3000");
      $this->assertEquals(RASL_APU_PAGE_FRONT, $this->getLocation());
    } else {
      $this->assertEquals(RASLTEST_URL_LOGIN, $this->getLocation());
    }
    $this->loginWithUser($user);
    $this->open(RASL_TEST_URL_FRONT);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
  }

  public function tLoginUser1NoLogut() {
    $user = $this->users[1];
    $this->open(RASLTEST_URL_LOGIN);
    $this->click("//a[@id='blogout']/img");
    $this->waitForPageToLoad("3000");
    $this->assertEquals(RASL_APU_PAGE_FRONT, $this->getLocation());
    $this->loginWithUser($user);
    $this->open(RASL_TEST_URL_FRONT);
    $this->assertTextPresent(truncate_utf8($user['name'], 18, FALSE, TRUE));
    $this->assertEquals(RASL_TEST_URL_FRONT, $this->getLocation());
  }

  /**
   * Test functions begin.
   */

  public function testLoginTwoUsers() {
    $this->removeDuplicates();
    $this->deleteTestUsers();
    $this->tLoginUser0(); // User created.
    $this->tLoginUser0(); // User updated.
    $this->tLoginUser1(); // User created.
    $this->tLoginUser1(); // User updated.
    $deleted_users = $this->deleteTestUsers();
    $this->assertEquals(2, $deleted_users);
  }

  public function testLoginUsersCanChangeWithoutLoggingOut() {
    $this->removeDuplicates();
    $this->deleteTestUsers();
    $this->tLoginUser0NoLogut(FALSE); // User created.
    $this->tLoginUser1NoLogut(); // User created.
    $this->tLoginUser0NoLogut(); // User updated.
    $this->tLoginUser1NoLogut(); // User updated.
  }

  public function testCannotCreateUserWithExistingUsername() {
    $this->removeDuplicates();
    $this->deleteTestUsers();
    $this->createDuplicateUsername();
    $user = $this->users[0];
    $this->open(RASL_TEST_URL_FRONT);
    $this->assertEquals(RASLTEST_URL_LOGIN, $this->getLocation());
    $this->loginWithUser($user);
    $e = new RaslUsernameOrEmailExists;
    $message = $e->getMessage();
    $this->assertTextPresent($message);
    $url = url('error-503', array('query' => array('message' => $message)));
    $this->assertEquals($url, $this->getLocation());
  } 

  public function testCannotCreateUserWithExistingEmail() {
    $this->removeDuplicates();
    $this->deleteTestUsers();
    $this->createDuplicateEmail();
    $user = $this->users[0];
    $this->open(RASL_TEST_URL_FRONT);
    $this->assertEquals(RASLTEST_URL_LOGIN, $this->getLocation());
    $this->loginWithUser($user);
    $e = new RaslUsernameOrEmailExists;
    $message = $e->getMessage();
    $this->assertTextPresent($message);
    $url = url('error-503', array('query' => array('message' => $message)));
    $this->assertEquals($url, $this->getLocation());
    $this->removeDuplicates();
  }

  public function testUserCanLoginWithAParameterHash() {
    $this->removeDuplicates();
    $this->deleteTestUsers();
    $this->logUser1InWithHash(); // Create.
    $this->logUser2InWithHash(); // Create.
    $this->logUser1InWithHash(); // Update.
    $this->logUser2InWithHash(); // Update.
  }

  public function testUsersCanSwitchWithATagsWithoutLoggingOut() {
    $this->removeDuplicates();
    $this->deleteTestUsers();
    $this->logUser1InWithHashNoLogout(); // Create.
    $this->logUser2InWithHashNoLogout(); // Create.
    $this->logUser1InWithHashNoLogout(); // Update.
    $this->logUser2InWithHashNoLogout(); // Update.
  }
}

Project information

  • Module categories: Developer Tools
  • Created by _rune on , updated
  • shield alertThis project is not covered by the security advisory policy.
    Use at your own risk! It may have publicly disclosed vulnerabilities.

Releases