Upload
hoanghanh
View
223
Download
0
Embed Size (px)
Citation preview
DRUPAL CON
NASHVILLE 2018
DRUPALCON NASHVILLE
DRUPAL CON
NASHVILLE 2018
Drupal 8: Let’s dive into PHPUnit testing.
Drupal 8: Let’s dive into PHPUnit testing.
Sugandh KhannaSrijan, INDIA
Drupal CON NASHVILLEMarch 2018
AGENDA
● Automated testing in D8 - History● Type of tests in D8● What is Unit Testing● Why we need unit testing ● What is PHPUnit● Php unit test -Best practices & Thumb rule● A Basic example● How to run test● Assertions & data Providers● Test doubles● Setup() Method● Mock Objects ● Stub Methods
AUTOMATED TESTING IN DRUPAL - HISTORY
Drupal 6 - Simpletest module
Drupal 7 - Simpletest in core
Drupal 8 -
Simpletest still in core but deprecated (to be removed in D9)
PHPUnit introduced into core
This presentation focuses exclusively on PHPUnit
TYPES OF TESTS IN DRUPAL 8
Unit tests
Kernel tests
Functional tests
JavaScript functional tests
Types of testing with an oversimplified example:
For a functional mobile phone, the main parts required are “battery” and “sim card”.● Unit testing– the battery is checked for its life,
capacity and other parameters. Sim card is checked for its activation.
● Kernel Testing– battery and sim card are integrated i.e. assembled in order to start the mobile phone.
● Functional Testing– the functionality of the mobile phone is checked in terms of its features and also battery usage as well as sim card facilities.
Exploring Unit testing only...
A unit test, then, is a sanity check to make sure that the chunk of
functionality does what it’s supposed to.
Few important points about unit testing and its benefits:
● Unit testing is done before Integration testing using white box testing techniques.
● Unit testing does not only check the positive behavior but also the failures that occur with invalid input.
● Finding issues/bugs at an early stage is very useful and it reduces the overall project costs.
● A unit test tests small pieces of code or individual functions so the issues/errors found in these test cases are independent and do not impact the other test cases.
● Another important advantage is that the unit test cases simplify and make testing of code easier.
● Unit test saves time and cost, and it is reusable and easy to maintain.
Coming up: why we need unit testing? Importance of unit testing.
Why we need unit testing?
Unit testing any application will not only save you a lot of headaches during development, but it can result in code that’s easier to maintain, allowing you to make more fearless changes (like major refactoring) without hesitation.
Let’s begin...
What is PHPUnit?
- A unit testing framework written in PHP, created by Sebastian Bergman
- Part of the xUnit family of testing frameworks.- While there are other testing frameworks for PHP
(such as SimpleTest or Atoum) PHPUnit has become the de facto standard.
Best practice for writing PHPUnit
tests
File structure and filenames● Unit tests go in
[MODULENAME]/tests/src/Unit● Namespace is
Drupal\Tests\[MODULENAME]\Unit
● Classnames: This should be exactly same as filenames.
● Method (test) names: Our test method names should start with test, in lower case.
● Extends PHPUnit: Classes must extend the class, UnitTestCase or extend a class that does
Thumb rule:Before writing the first test, think about what we need to actually test from the code given.
<?php
namespace Drupal\vf_hierarchy\Controller;
use Drupal\Core\Controller\ControllerBase;
class HierarchyController extends ControllerBase {
public function dummyFunc() {//}
}
<?php
namespace Drupal\Tests\vf_hierarchy\Unit;
use Drupal\Tests\UnitTestCase;
/** * @group vf_hierarchy */
class HierarchyControllerTest extends UnitTestCase {
public function testDummyFunc() { $foo = true; $this->assertTrue($foo);}
Basic Example
??
ASSERTIONS
What is an assertion?
Wikipedia defines an assertion as
a predicate (a true–false statement) placed in a program to indicate that the developer thinks that the predicate is always true at that place.
Translated, all it is saying is that an assertion verifies a statement made equals true.
In order to run this test… Either
1. Enable Testing module.
2. Php core /scripts/run-tests.sh --verbose --url localhost --color vf_hierarchy
Processing test 1 0f 1 - Drupal\Tests\vf_hierarchy\Unit\HierarchyControllerTest
What are Test Doubles?
Test doubles (or mock objects) allow to focus a unit test on the code that needs to be tested without bootstrapping dependencies.
When to use test double?
It is very common in our code, a function of one class is calling another class's function. In this case, we have a dependency in these two classes. In particular, the caller class has a dependency on calling class. But as we already know, unit test should test the smallest unit of functionality, in this case, it should test only the caller function. To solve this problem, We can use test double to replace the calling class. Since a test double can be configured to return predefined results, we can focus on testing the caller function.
Types of “Test Doubles”
Types of test doubles● Dummy objects are passed around but never actually used. Usually
they are just used to fill parameter lists.● Fake objects actually have working implementations, but usually take
some shortcut which makes them not suitable for production.● Stubs provide canned answers to calls made during the test, usually
not responding at all to anything outside what's programmed in for the test.
● Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
● Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting.
Setup(), Mock Objects, data Providers and Stub Methods
<?phpnamespace Drupal\vf_device_models\Controller;use Drupal\Core\Controller\ControllerBase;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\JsonResponse;
class DeviceModelController extends ControllerBase {
public function getDeviceModel(Request $request, $id = NULL, $name, $level) { $result = ' ‘; $result = $this->getDeviceModelLists($request, $id, $name, NULL, $level); $response = new JsonResponse($result); If ($response) { return TRUE; } }
Before writing the first test, think about what we need to actually test from the code given.
Check…..
● Weather the function is returning json object or not?
● If there’s is json object, asserts true, else test will fail.
Let’s prepare skelton:
<?php
namespace Drupal\Tests\vf_device_models\Unit {
use Drupal\vf_device_models\Controller\DeviceModelController; use Drupal\Tests\UnitTestCase;
/** * @group vf_device_models */ class DeviceModelControllerTest extends UnitTestCase { protected $messenger; protected $request;
protected function setUp() { parent::setUp(); $this->messenger = new DeviceModelController(); } public function testgetDeviceModel() { //body of function…….. } }}
??
PHPUnit: setUp
• The setUp method is executed for every test method in a class.
• Configure fixtures or setting up known state such as database or test files.
• Configure test doubles or mocks, which are dependencies of a unit that you do not need to test in that test class.
protected function setUp() { $this->stack = [];}
Invoked before a test method is run.
protected function setUp() { parent::setUp(); $this->messenger = new DeviceModelController(); }
public function testgetDeviceModel() { $result = $this->messenger->getDeviceModel($this->request,'1209','sugandh25062','market','514'); $this->assertTrue($this->messenger->getDeviceModel($this->request,'1209','sugandh25062','market','514'), "The json object is created.");}
Why this exception?????
Because it requires “Request” instance for the test function to execute. Since unit testing means test in isolation, so the need of the hour is to create a
fake object of class Request.
MOCK
protected function setUp() { parent::setUp();
$this->request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') ->disableOriginalConstructor() ->setMethods([ ]) ->getMock();
$this->messenger = new DeviceModelController($this->request);}
Happy!
First test run success…. :D
So understood Mocks?
Or need 1 more example?
Let’s have one more example from core:(Function checks whether user is authenticated or not.. If yes, returns uid.)
public function authenticate($username, $password) {
$uid = FALSE;
if (!empty($username) && strlen($password) > 0) {
$account_search = $this->entityManager->getStorage('user')->loadByProperties(['name' => $username]);
..... }
return $uid; }
/** * @dataProvider providerTestAuthenticateWithMissingCredentials */ public function testAuthenticateWithMissingCredentials($username, $password) { $this->userStorage->expects($this->never()) ->method('loadByProperties');
$this->assertFalse($this->userAuth->authenticate($username, $password)); }
/** * Data provider for testAuthenticateWithMissingCredentials(). */ public function providerTestAuthenticateWithMissingCredentials() { return [ [NULL, NULL], [NULL, ''], ['', NULL], ['', ''], ]; }
Data providers .. ??
PHPUnit: Data Providers
• A data provider is a method that returns an array of parameters to pass into a test method.
• A test method may test several inputs.
• Important to note that data provider methods are run before any setup so this cannot depend on any test doubles.
protected function setUp() { $this->userStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $entity_manager->expects($this->any()) ->method('getStorage') ->with('user') ->will($this->returnValue($this->userStorage));
$this->passwordService = $this->getMock('Drupal\Core\Password\PasswordInterface');
$this->testUser = $this->getMockBuilder('Drupal\user\Entity\User') ->disableOriginalConstructor() ->setMethods(['id', 'setPassword', 'save', 'getPassword']) ->getMock();
$this->userAuth = new UserAuth($entity_manager, $this->passwordService); }
Coming up: Definition of mock.
Mock - Type of test make a replica or imitation of something.orfaking the behavior of object
Coming up: Types of mocks
Mock Methods
getMock() and getMockBuilder()
Coming up: difference bw methods.
when the defaults used by the getMock() method to generate the test double do not match your needs then you can use the getMockBuilder($type) method to customize the test double generation using a fluent interface. Here is a list of methods provided by the Mock Builder:
● setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed. If you call setMethods(null), then no methods will be replaced.
● setConstructorArgs(array $args) can be called to provide a parameter array that is passed to the original class' constructor (which is not replaced with a dummy implementation by default).
● setMockClassName($name) can be used to specify a class name for the generated test double class.
● disableOriginalConstructor() can be used to disable the call to the original class' constructor.
● disableOriginalClone() can be used to disable the call to the original class' clone constructor.
● disableAutoload() can be used to disable __autoload() during the generation of the test double class.
STUBThe practice of replacing an object with a test double that (optionally) returns configured return values is
referred to as stubbing.
THE FOUR PATHWAYS OF GETMOCKBUILDER()
Do not call setMethods()This is the simplest way:
$this->testUser = $this->getMockBuilder('Drupal\user\Entity\User') ->getMock();This produces a mock object where the methods
● Are all stubs,● All return null by default,● Are easily overridable
Passing an empty arrayYou can pass an empty array to setMethods():
$this->testUser = $this->getMockBuilder('Drupal\user\Entity\User') ->getMock(); ->setMethods(array()) ->getMock();This produces a mock object that is exactly the same as if you have not called setMethods() at all. The methods● Are all stubs,● All return null by default,● Are easily overridable
Passing nullYou can also pass null:
$this->testUser = $this->getMockBuilder('Drupal\user\Entity\User') ->setMethods(null) ->getMock();This produces a mock object where the methods
● Are all mocks,● Run the actual code contained within the method
when called,● Do not allow you to override the return value
Passing an array containing method names$this->testUser = $this->getMockBuilder('Drupal\user\Entity\User') ->setMethods(array(‘id', 'setPassword', 'save', 'getPassword’)) ->getMock();This produces a mock object whose methods are a mix of the above three scenarios.
The methods you have identified
● Are all stubs,● All return null by default,● Are easily overridable
Methods you did not identify
● Are all mocks,● Run the actual code contained within the method
when called,● Do not allow you to override the return value
This means that in the $this->testUser mock object the ‘id()', 'setPassword()', 'save()' and 'getPassword()’ methods would return null or you can override their return values, but any method within that class other than those four will run their original code.
If you compare it to debugging:
Stub is like making sure a method returns the correct value
Mock is like actually stepping into the method and making sure everything inside is correct before returning the correct value.
Any Questions?