DRUPAL CAMP LONDON 2018 DRUPALCAMPLONDON DRUPAL CAMP LONDON

  • Slides: 60
Download presentation
DRUPAL CAMP LONDON 2018 DRUPALCAMPLONDON

DRUPAL CAMP LONDON 2018 DRUPALCAMPLONDON

DRUPAL CAMP LONDON 2018 PHPUnit testing in Drupal 8.

DRUPAL CAMP LONDON 2018 PHPUnit testing in Drupal 8.

PHPUnit testing in Drupal 8. Sugandh Khanna Srijan, INDIA Drupal Camp London March 2018

PHPUnit testing in Drupal 8. Sugandh Khanna Srijan, INDIA Drupal Camp London March 2018

AGENDA ● ● ● ● Automated testing in D 8 - History Type of

AGENDA ● ● ● ● Automated testing in D 8 - History Type of tests in D 8 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

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 D 9) PHPUnit introduced into core This presentation focuses exclusively on PHPUnit

TYPES OF TESTS IN DRUPAL 8 Unit tests Kernel tests Functional tests Java. Script

TYPES OF TESTS IN DRUPAL 8 Unit tests Kernel tests Functional tests Java. Script functional tests

Types of testing with an oversimplified example: For a functional mobile phone, the main

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

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

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 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

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. . .

Let’s begin. . .

What is PHPUnit? - A unit testing framework written in PHP, created by Sebastian

What is PHPUnit? - A unit testing framework written in PHP, created by Sebastian Bergman - Part of the x. Unit family of testing frameworks. - While there are other testing frameworks for PHP (such as Simple. Test or Atoum) PHPUnit has become the de facto standard.

Best practice for writing PHPUnit tests

Best practice for writing PHPUnit tests

File structure and filenames ● Unit tests go in [MODULENAME]/tests/src/Unit ● Namespace is DrupalTests[MODULENAME]Unit

File structure and filenames ● Unit tests go in [MODULENAME]/tests/src/Unit ● Namespace is DrupalTests[MODULENAME]Unit

● Classnames: This should be exactly same as filenames. ● Method (test) names: Our

● 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, Unit. Test. Case or extend a class that does

Thumb rule: Before writing the first test, think about what we need to actually

Thumb rule: Before writing the first test, think about what we need to actually test from the code given.

Basic Example <? php namespace Drupalvf_hierarchyController; use DrupalCoreController. Bas e; <? php namespace DrupalTestsvf_hierarchyUnit;

Basic Example <? php namespace Drupalvf_hierarchyController; use DrupalCoreController. Bas e; <? php namespace DrupalTestsvf_hierarchyUnit; use DrupalTestsUnit. Test. Case; ? ? /** * @group vf_hierarchy */ class Hierarchy. Controller extends Controller. Base { class Hierarchy. Controller. Test extends Unit. Test. Case { public function dummy. Func() { // } public function test. Dummy. Func() { $foo = true; $this->assert. True($foo); } }

ASSERTIONS What is an assertion? Wikipedia defines an assertion as a predicate (a true–false

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. 1. Php core

In order to run this test… Either 1. Enable Testing module. 1. Php core /scripts/run-tests. sh --verbose --url localhost --color vf_hierarchy

Processing test 1 0 f 1 DrupalTestsvf_hierarchyUnitHierarchy. Controller Test

Processing test 1 0 f 1 DrupalTestsvf_hierarchyUnitHierarchy. Controller Test

What are Test Doubles?

What are Test Doubles?

Test doubles (or mock objects) allow to focus a unit test on the code

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?

When to use test double?

It is very common in our code, a function of one class is calling

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”

Types of test doubles ● Dummy objects are passed around but never actually used.

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

Setup(), Mock Objects, data Providers and Stub Methods

<? php namespace Drupalvf_device_modelsController; use DrupalCoreController. Base; use SymfonyComponentHttp. FoundationRequest; use SymfonyComponentHttp. FoundationJson. Response;

<? php namespace Drupalvf_device_modelsController; use DrupalCoreController. Base; use SymfonyComponentHttp. FoundationRequest; use SymfonyComponentHttp. FoundationJson. Response; class Device. Model. Controller extends Controller. Base { public function get. Device. Model(Request $request, $id = NULL, $name, $level) { $result = ' ‘; $result = $this->get. Device. Model. Lists($request, $id, $name, NULL, $level); $response = new Json. Response($result); If ($response) { return TRUE; } }

Before writing the first test, think about what we need to actually test from

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 DrupalTestsvf_device_modelsUnit { use Drupalvf_device_modelsControllerDevice. Model. Controller; use DrupalTestsUnit. Test. Case; /**

<? php namespace DrupalTestsvf_device_modelsUnit { use Drupalvf_device_modelsControllerDevice. Model. Controller; use DrupalTestsUnit. Test. Case; /** * @group vf_device_models */ class Device. Model. Controller. Test extends Unit. Test. Case { protected $messenger; protected $request; protected function set. Up() { parent: : set. Up(); $this->messenger = new Device. Model. Controller(); } public function testget. Device. Model() { //body of function……. . } } } ? ?

PHPUnit: set. Up protected function set. Up() { $this->stack = []; } • The

PHPUnit: set. Up protected function set. Up() { $this->stack = []; } • The set. Up method is executed for every test method in a class. • Configure fixtures or setting up known state such as database or test files. Invoked before a test method is run. • Configure test doubles or mocks, which are dependencies of a unit that you do not need to test in that test class.

protected function set. Up() { parent: : set. Up(); $this->messenger = new Device. Model.

protected function set. Up() { parent: : set. Up(); $this->messenger = new Device. Model. Controller(); } public function testget. Device. Model() { $result = $this->messenger->get. Device. Model($this>request, '1209', 'sugandh 25062', 'market', '514'); $this->assert. True($this->messenger->get. Device. Model($this>request, '1209', 'sugandh 25062', 'market', '514'), "The json object is created. "); }

MOCK Why this exception? ? ? Because it requires “Request” instance for the test

MOCK 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.

protected function set. Up() { parent: : set. Up(); $this->request = $this>get. Mock. Builder('SymfonyComponentHttp.

protected function set. Up() { parent: : set. Up(); $this->request = $this>get. Mock. Builder('SymfonyComponentHttp. Fou ndationRequest') ->disable. Original. Constructor() ->set. Methods([ ]) ->get. Mock(); $this->messenger = new Device. Model. Controller($this->request);

Happy! First test run success…. : D

Happy! First test run success…. : D

So understood Mocks? Or need 1 more example? Let’s have one more example from

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)

public function authenticate($username, $password) { $uid = FALSE; if (!empty($username) && strlen($password) > 0) { $account_search = $this->entity. Manager>get. Storage('user')->load. By. Properties(['name' => $username]); . . . } return $uid; }

/** * @data. Provider provider. Test. Authenticate. With. Missing. Credentials */ public function test.

/** * @data. Provider provider. Test. Authenticate. With. Missing. Credentials */ public function test. Authenticate. With. Missing. Credentials($username, $password) { $this->user. Storage->expects($this->never()) ->method('load. By. Properties'); $this->assert. False($this->user. Auth->authenticate($username, $password)); } /** * Data provider for test. Authenticate. With. Missing. Credentials(). */ public function provider. Test. Authenticate. With. Missing. Credentials() { return [ [NULL, NULL], Data providers. . ? ? [NULL, ''], ['', NULL], ['', ''], ]; }

PHPUnit: Data Providers • A data provider is a method that returns an array

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 set. Up() { $this->user. Storage = $this->get. Mock('DrupalCoreEntity. Storage. Interface'); $entity_manager =

protected function set. Up() { $this->user. Storage = $this->get. Mock('DrupalCoreEntity. Storage. Interface'); $entity_manager = $this->get. Mock('DrupalCoreEntity. Manager. Interface'); $entity_manager->expects($this->any()) ->method('get. Storage') ->with('user') ->will($this->return. Value($this->user. Storage)); $this->password. Service = $this->get. Mock('DrupalCorePassword. Interface'); $this->test. User = $this->get. Mock. Builder('DrupaluserEntityUser') ->disable. Original. Constructor() ->set. Methods(['id', 'set. Password', 'save', 'get. Password']) ->get. Mock(); $this->user. Auth = new User. Auth($entity_manager, $this->password. Service); } Coming up: Definition of mock.

Mock - Type of test make a replica or imitation of something. or faking

Mock - Type of test make a replica or imitation of something. or faking the behavior of object Coming up: Types of mocks

Mock Methods get. Mock() and get. Mock. Builder() Coming up: difference bw methods.

Mock Methods get. Mock() and get. Mock. Builder() Coming up: difference bw methods.

when the defaults used by the get. Mock() method to generate the test double

when the defaults used by the get. Mock() method to generate the test double do not match your needs then you can use the get. Mock. Builder($type) method to customize the test double generation using a fluent interface. Here is a list of methods provided by the Mock Builder: ● set. Methods(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 set. Methods(null), then no methods will be replaced. ● set. Constructor. Args(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). ● set. Mock. Class. Name($name) can be used to specify a class name for the generated test double class. ● disable. Original. Constructor() can be used to disable the call to the original class' constructor. ● disable. Original. Clone() can be used to disable the call to the original class' clone constructor. ● disable. Autoload() can be used to disable __autoload() during the generation of the test double class.

STUB The practice of replacing an object with a test double that (optionally) returns

STUB The practice of replacing an object with a test double that (optionally) returns configured return values is referred to as stubbing.

THE FOUR PATHWAYS OF GETMOCKBUIL DER()

THE FOUR PATHWAYS OF GETMOCKBUIL DER()

Do not call set. Methods() This is the simplest way: $this->test. User = $this>get.

Do not call set. Methods() This is the simplest way: $this->test. User = $this>get. Mock. Builder('DrupaluserEntityUser') ->get. Mock(); This produces a mock object where the methods ● Are all stubs, ● All return null by default, ● Are easily overridable

Passing an empty array You can pass an empty array to set. Methods(): $this->test.

Passing an empty array You can pass an empty array to set. Methods(): $this->test. User = $this>get. Mock. Builder('DrupaluserEntityUser') ->get. Mock(); ->set. Methods(array()) ->get. Mock(); This produces a mock object that is exactly the same as if you have not called set. Methods() at all. The methods ● Are all stubs, ● All return null by default, ● Are easily overridable

Passing null You can also pass null: $this->test. User = $this>get. Mock. Builder('DrupaluserEntityUser') ->set.

Passing null You can also pass null: $this->test. User = $this>get. Mock. Builder('DrupaluserEntityUser') ->set. Methods(null) ->get. Mock(); 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->test. User = $this>get. Mock. Builder('DrupaluserEntityUser') ->set. Methods(array(‘id',

Passing an array containing method names $this->test. User = $this>get. Mock. Builder('DrupaluserEntityUser') ->set. Methods(array(‘id', 'set. Password', 'save', 'get. Password’)) ->get. Mock(); 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

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->test. User mock object the ‘id()', 'set. Password()', 'save()'

This means that in the $this->test. User mock object the ‘id()', 'set. Password()', 'save()' and 'get. Password()’ 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

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?

Any Questions?