New England Drupal Camp 2017
NO MORE EXCUSES
Test your modules!
Erich Beyrent
Erich Beyrent
Senior Drupal Developer at BioRAFT
Drupal:
https://www.drupal.org/u/ebeyrent
Twitter:
https://twitter.com/ebeyrent
LinkedIn:
https://www.linkedin.com/in/erichbeyrent
Agenda
❖ Why write automated tests?
Agenda
❖ Why write automated tests?
❖ Types of automated tests in Drupal 8
Agenda
❖ Why write automated tests?
❖ Types of automated tests in Drupal 8
❖ Unit Tests
❖ Kernel Tests
❖ Functional Tests
❖ Browser Tests
❖ Javascript Tests*
❖ Behavioral Tests*
Why write automated tests?
❖ Manual testing can be generally cheaper, especially for a
short-term project (i.e. one-time testing)
❖ Manual testing is BORING AF
❖ Manual testing is time-consuming for projects that
iterate
❖ Manual testing is hard to make exactly repeatable,
requires extensive documentation
Why write automated tests?
Why write automated tests?
❖ Better to discover bugs during local development and
build processes
❖ Gives developers the confidence to make changes
❖ Some tests are self-documenting*
Unit Tests
❖ Individual methods are tested as discrete units
❖ Unit tests are solitary
❖ Uses test doubles, or mocks to control behavior of
related components
❖ Unit tests are fast, no need to bootstrap Drupal
Unit Tests
❖ Unit tests go in my_module/tests/src/Unit
❖ Unit tests require a namespace in the form of
DrupalTestsmy_moduleUnit
❖ Unit tests should include the following annotations:
❖ Class:
❖ @coversDefaultClass - Specified the name of the class being tests
❖ @group - Specifies the group of tests, usually the name of your
module
❖ Method:
❖ @covers - Specifies the class method being tested
public function __construct() {
$this->httpClient = Drupal::service('httpd_client');
$this->logger = Drupal::logger('apod');
$this->config = Drupal::config('apod.api_config');
}
public function getAstronomyPictureOfTheDay() {
$uri = $this->addQueryString(
$this->url, ['query' => [
'api_key' => $this->config->get(‘api_key')
]]);
$response = $this->httpClient->request('GET', $uri);
return $this->handleResponse($response);
}
Unit Testing depends on
Dependency Injection.
/**
* ApodClient constructor.
*
* @param GuzzleHttpClientInterface $httpClient
* Instance of the Guzzle HTTP Client.
* @param DrupalCoreLoggerLoggerChannelFactoryInterface $logger
* Instance of the Logger factory.
* @param DrupalCoreConfigConfigFactoryInterface $config_factory
* Instance of an object that implements the ConfigFactoryInterface.
*/
public function __construct(
ClientInterface $httpClient,
LoggerChannelFactoryInterface $logger,
ConfigFactoryInterface $configFactory
) {
$this->httpClient = $httpClient;
$this->logger = $logger->get(‘apod');
$this->config = $configFactory->get(‘apod.api_config’);
}
Mocks Everywhere
❖ Drupal 8 supports many different ways to create mocks
❖ PHPUnit
❖ $mock = $this->getMockBuilder(MyClass::class)
->getMock()
❖ Prophesy
❖ $mock = $this->prophesize(MyClass::class)->reveal()
❖ Mockery
❖ $mock = Mockery::mock(MyClass::class)
Running PHPUnit
$ cd web
$ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist 
--testsuite=unit --group=my_group
Demo
Kernel Tests
❖ Kernel tests are sociable
❖ Used when it’s not practical or easy to write mocks
❖ Kernel tests are executed in a minimal Drupal environment
❖ Tests have access to the database and files
❖ Tests must install dependent modules
❖ Kernel tests are slower than unit tests, because they need to
bootstrap Drupal.
Kernel Tests
❖ Kernel tests go in my_module/tests/src/Kernel
❖ Kernel tests require a namespace in the form of
DrupalTestsmy_moduleKernel
Kernel Tests
$ export SIMPLETEST_DB=‘mysql://user@localhost/testdb'
$ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist 
--testsuite=kernel --group my_module
Demo
2 Unit Tests,
0 Integration Tests
http://searchsoftwarequality.techtarget.com/feature/FAQ-Automated-software-testing-basics
Automated Testing Pyramids
https://www.tomred.net/devops/different-levels-in-automated-testing.html
Automated Testing Pyramids
Functional Tests
❖ Also known as integration tests, or UI tests
❖ Integration tests balance out the expense of speed vs
confidence
❖ Integration tests are supported in Drupal 8 as Browser
tests and Javascript tests
Browser Tests
❖ Browser tests use a fully installed Drupal environment
❖ All required modules must be declared
❖ Tests use an internal web browser behind the scene
❖ Tests tend to run much slower than unit tests
❖ Javascript is not supported
Browser Tests
❖ Browser tests go in my_module/tests/src/Functional
❖ Browser tests require a namespace in the form of
DrupalTestsmy_moduleFunctional
❖ Unit tests should include the following annotations:
❖ Class:
❖ @group - Specifies the group of tests, usually the
name of your module
Browser Tests
$ cd web
$ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist 
modules/custom/my_module/tests/src/Functional/MyTest.php
Demo
Javascript Tests
❖ Javascript tests go in my_module/tests/src/
FunctionalJavascript
❖ Browser tests require a namespace in the form of
DrupalTestsmy_moduleFunctionalJavascript
❖ Unit tests should include the following annotations:
❖ Class:
❖ @group - Specifies the group of tests, usually the
name of your module
Javascript Tests
❖ Tests require PhantomJS to run
❖ Uses a full Drupal installation
❖ Can be used to test AJAX functionality
❖ There is no UI
❖ Debugging occurs using a debugger
Javascript Tests
Install PhantomJS: http://phantomjs.org/download.html
Run PhantomJS:

$ phantomjs --ssl-protocol=any --ignore-ssl-errors=true --debug=true 
./vendor/jcalderonzumba/gastonjs/src/Client/main.js 
8510 1024 768
Run tests:

$ cd web
$ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist 
modules/custom/apod/tests/src/FunctionalJavascript/
ApodBlockHtmlTest.php
Demo
Behavioral Tests
❖ Behat FTW!
❖ Gherkin is human readable, and self-documenting
❖ Behat runs in a full local browser (IE, Firefox, Chrome)
❖ Debugging is simple and easy
Behavioral Tests
"require-dev": {
"devinci/devinci-behat-extension": "^0.1.0",
"ingenerator/behat-tableassert": "^1.1.1"
}
Running Behat Tests
❖ behat.yml
$ ./vendor/bin/behat --tags non_core_modules
Demo
Tests Help Drive Development
❖ Before starting a feature, translate requirements to
Gherkin
❖ Before starting a class, stub out methods and associated
unit tests
❖ Add functionality and tests concurrently
❖ Before marking a project as done, make sure all tests
pass
Gotchas
❖ Unit tests may need rewrites after refactoring code
❖ Modules need to be enabled for tests (other than unit tests)
to run
❖ Schema can cause problems.
❖ $this->strictConfigSchema = FALSE;
❖ Traits can cause problems.
❖ $object->setStringTranslation($this-
>getStringTranslationStub())
❖ Debugging in PhantomJS isn’t fun.
Gotchas
❖ Permissions can cause problems (run tests as Apache
user)
❖ Traits can cause problems.
// Solution

$object->setStringTranslation($this->getStringTranslationStub());
Summary
❖ Unit tests are for testing class methods.
❖ Kernel tests are for testing APIs
❖ Functional and behavioral tests are for testing web
interfaces.
❖ Behat is amazing
Resources
❖ https://www.drupal.org/docs/8/phpunit
❖ https://www.drupal.org/docs/8/phpunit/phpunit-browser-test-
tutorial
❖ https://www.drupal.org/docs/8/phpunit/phpunit-javascript-testing-
tutorial
❖ https://blog.kentcdodds.com/write-tests-not-too-many-mostly-
integration-5e8c7fff591c
❖ http://james-willett.com/2016/10/finding-the-balance-between-unit-
functional-tests/
❖ https://www.lullabot.com/articles/an-overview-of-testing-in-drupal-8
Questions?
https://www.dokeos.com/wp-content/uploads/2014/06/29-questions-test-Dokeos-EN.jpg

Test your modules

  • 1.
    New England DrupalCamp 2017 NO MORE EXCUSES Test your modules! Erich Beyrent
  • 2.
    Erich Beyrent Senior DrupalDeveloper at BioRAFT Drupal: https://www.drupal.org/u/ebeyrent Twitter: https://twitter.com/ebeyrent LinkedIn: https://www.linkedin.com/in/erichbeyrent
  • 4.
    Agenda ❖ Why writeautomated tests?
  • 5.
    Agenda ❖ Why writeautomated tests? ❖ Types of automated tests in Drupal 8
  • 6.
    Agenda ❖ Why writeautomated tests? ❖ Types of automated tests in Drupal 8 ❖ Unit Tests ❖ Kernel Tests ❖ Functional Tests ❖ Browser Tests ❖ Javascript Tests* ❖ Behavioral Tests*
  • 7.
    Why write automatedtests? ❖ Manual testing can be generally cheaper, especially for a short-term project (i.e. one-time testing) ❖ Manual testing is BORING AF ❖ Manual testing is time-consuming for projects that iterate ❖ Manual testing is hard to make exactly repeatable, requires extensive documentation
  • 8.
  • 9.
    Why write automatedtests? ❖ Better to discover bugs during local development and build processes ❖ Gives developers the confidence to make changes ❖ Some tests are self-documenting*
  • 10.
    Unit Tests ❖ Individualmethods are tested as discrete units ❖ Unit tests are solitary ❖ Uses test doubles, or mocks to control behavior of related components ❖ Unit tests are fast, no need to bootstrap Drupal
  • 11.
    Unit Tests ❖ Unittests go in my_module/tests/src/Unit ❖ Unit tests require a namespace in the form of DrupalTestsmy_moduleUnit ❖ Unit tests should include the following annotations: ❖ Class: ❖ @coversDefaultClass - Specified the name of the class being tests ❖ @group - Specifies the group of tests, usually the name of your module ❖ Method: ❖ @covers - Specifies the class method being tested
  • 12.
    public function __construct(){ $this->httpClient = Drupal::service('httpd_client'); $this->logger = Drupal::logger('apod'); $this->config = Drupal::config('apod.api_config'); } public function getAstronomyPictureOfTheDay() { $uri = $this->addQueryString( $this->url, ['query' => [ 'api_key' => $this->config->get(‘api_key') ]]); $response = $this->httpClient->request('GET', $uri); return $this->handleResponse($response); }
  • 13.
    Unit Testing dependson Dependency Injection.
  • 14.
    /** * ApodClient constructor. * *@param GuzzleHttpClientInterface $httpClient * Instance of the Guzzle HTTP Client. * @param DrupalCoreLoggerLoggerChannelFactoryInterface $logger * Instance of the Logger factory. * @param DrupalCoreConfigConfigFactoryInterface $config_factory * Instance of an object that implements the ConfigFactoryInterface. */ public function __construct( ClientInterface $httpClient, LoggerChannelFactoryInterface $logger, ConfigFactoryInterface $configFactory ) { $this->httpClient = $httpClient; $this->logger = $logger->get(‘apod'); $this->config = $configFactory->get(‘apod.api_config’); }
  • 15.
    Mocks Everywhere ❖ Drupal8 supports many different ways to create mocks ❖ PHPUnit ❖ $mock = $this->getMockBuilder(MyClass::class) ->getMock() ❖ Prophesy ❖ $mock = $this->prophesize(MyClass::class)->reveal() ❖ Mockery ❖ $mock = Mockery::mock(MyClass::class)
  • 16.
    Running PHPUnit $ cdweb $ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist --testsuite=unit --group=my_group
  • 17.
  • 18.
    Kernel Tests ❖ Kerneltests are sociable ❖ Used when it’s not practical or easy to write mocks ❖ Kernel tests are executed in a minimal Drupal environment ❖ Tests have access to the database and files ❖ Tests must install dependent modules ❖ Kernel tests are slower than unit tests, because they need to bootstrap Drupal.
  • 19.
    Kernel Tests ❖ Kerneltests go in my_module/tests/src/Kernel ❖ Kernel tests require a namespace in the form of DrupalTestsmy_moduleKernel
  • 20.
    Kernel Tests $ exportSIMPLETEST_DB=‘mysql://user@localhost/testdb' $ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist --testsuite=kernel --group my_module
  • 21.
  • 22.
    2 Unit Tests, 0Integration Tests
  • 23.
  • 24.
  • 25.
    Functional Tests ❖ Alsoknown as integration tests, or UI tests ❖ Integration tests balance out the expense of speed vs confidence ❖ Integration tests are supported in Drupal 8 as Browser tests and Javascript tests
  • 26.
    Browser Tests ❖ Browsertests use a fully installed Drupal environment ❖ All required modules must be declared ❖ Tests use an internal web browser behind the scene ❖ Tests tend to run much slower than unit tests ❖ Javascript is not supported
  • 27.
    Browser Tests ❖ Browsertests go in my_module/tests/src/Functional ❖ Browser tests require a namespace in the form of DrupalTestsmy_moduleFunctional ❖ Unit tests should include the following annotations: ❖ Class: ❖ @group - Specifies the group of tests, usually the name of your module
  • 28.
    Browser Tests $ cdweb $ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist modules/custom/my_module/tests/src/Functional/MyTest.php
  • 29.
  • 30.
    Javascript Tests ❖ Javascripttests go in my_module/tests/src/ FunctionalJavascript ❖ Browser tests require a namespace in the form of DrupalTestsmy_moduleFunctionalJavascript ❖ Unit tests should include the following annotations: ❖ Class: ❖ @group - Specifies the group of tests, usually the name of your module
  • 31.
    Javascript Tests ❖ Testsrequire PhantomJS to run ❖ Uses a full Drupal installation ❖ Can be used to test AJAX functionality ❖ There is no UI ❖ Debugging occurs using a debugger
  • 32.
    Javascript Tests Install PhantomJS:http://phantomjs.org/download.html Run PhantomJS:
 $ phantomjs --ssl-protocol=any --ignore-ssl-errors=true --debug=true ./vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 Run tests:
 $ cd web $ ../vendor/bin/phpunit -c ./core/phpunit.xml.dist modules/custom/apod/tests/src/FunctionalJavascript/ ApodBlockHtmlTest.php
  • 33.
  • 34.
    Behavioral Tests ❖ BehatFTW! ❖ Gherkin is human readable, and self-documenting ❖ Behat runs in a full local browser (IE, Firefox, Chrome) ❖ Debugging is simple and easy
  • 35.
    Behavioral Tests "require-dev": { "devinci/devinci-behat-extension":"^0.1.0", "ingenerator/behat-tableassert": "^1.1.1" }
  • 36.
    Running Behat Tests ❖behat.yml $ ./vendor/bin/behat --tags non_core_modules
  • 37.
  • 38.
    Tests Help DriveDevelopment ❖ Before starting a feature, translate requirements to Gherkin ❖ Before starting a class, stub out methods and associated unit tests ❖ Add functionality and tests concurrently ❖ Before marking a project as done, make sure all tests pass
  • 39.
    Gotchas ❖ Unit testsmay need rewrites after refactoring code ❖ Modules need to be enabled for tests (other than unit tests) to run ❖ Schema can cause problems. ❖ $this->strictConfigSchema = FALSE; ❖ Traits can cause problems. ❖ $object->setStringTranslation($this- >getStringTranslationStub()) ❖ Debugging in PhantomJS isn’t fun.
  • 40.
    Gotchas ❖ Permissions cancause problems (run tests as Apache user) ❖ Traits can cause problems. // Solution $object->setStringTranslation($this->getStringTranslationStub());
  • 41.
    Summary ❖ Unit testsare for testing class methods. ❖ Kernel tests are for testing APIs ❖ Functional and behavioral tests are for testing web interfaces. ❖ Behat is amazing
  • 42.
    Resources ❖ https://www.drupal.org/docs/8/phpunit ❖ https://www.drupal.org/docs/8/phpunit/phpunit-browser-test- tutorial ❖https://www.drupal.org/docs/8/phpunit/phpunit-javascript-testing- tutorial ❖ https://blog.kentcdodds.com/write-tests-not-too-many-mostly- integration-5e8c7fff591c ❖ http://james-willett.com/2016/10/finding-the-balance-between-unit- functional-tests/ ❖ https://www.lullabot.com/articles/an-overview-of-testing-in-drupal-8
  • 43.