Delete old content

This commit is contained in:
Oliver Davies 2020-12-23 20:32:49 +00:00
parent 80ccac2d43
commit b2dccb925e
226 changed files with 0 additions and 4770 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View file

@ -1,961 +0,0 @@
theme: Simple, 8
autoscale: true
build-lists: true
header: alignment(left), line-height(1.1), text-scale(1.3)
text: alignment(left)
text-emphasis: #53B0EB
![](images/title.png)
[.header: alignment(center)]
# **TDD: Test <br>Driven Drupal**
---
- Why write tests, and what to test
- Types of tests
- How to run tests
- Example
- Building a new module with test driven development
---
[.background-color: #FFFFFF]
[.build-lists: false]
[.header: #111111]
[.text: #111111, alignment(left)]
![right 800%](../images/me-phpnw.png)
- Full Stack Web Developer & System Administrator
- Senior Software Engineer at Inviqa
- PHP South Wales organiser
- @opdavies
- www.oliverdavies.uk
^ - Work at Inviqa.
- Organiser of the PHP South Wales user group. Formerly PHP South West, Drupal Bristol, SWDUG
- Maintain Drupal modules, PHP CLI tools and libraries
- Blog on my website
---
[.header: alignment(center)]
## Write **custom modules <br>and themes** for clients
---
[.header: alignment(center)]
## Contributor to **Drupal core**
^ - Drupal 7 and 8 core contributor
- Most recent patch submitted on a live stream
---
[.header: alignment(center)]
## Maintain and contribute <br>to **contrib projects**
^ Approaching this from a few different angles
---
[.background-color: #FFFFFF]
![inline 150%](images/timmillwood-ono.png)
---
## Override Node Options
- Become maintainer in 2012
- **#232** most used module on Drupal.org (May 2020)
- Had some existing tests
- Crucial to preventing regressions
^ Preventing regressions when adding new features or fixing bugs, but also user
submitted patches First module I ported to Drupal 8, aided by tests
---
![](images/override-node-options-2012-1.png)
---
![](images/override-node-options-2012-4.png)
---
![](images/override-node-options-2020-2.png)
^ - D5 and D6 gone down, D7 up and a few different D8 versions including D9 compatibility.
- Around 30,000 sites
---
[.header: alignment(center)]
## **Why write tests?**
---
## Why write tests?
- Catch bugs earlier
- Peace of mind
- Prevent regressions
- Write less code
- Documentation
- Drupal core requirement
- More important with regular D8/D9 releases and supporting multiple versions
^ Dave Liddament talk - better and cheaper to catch bugs earlier (e.g. whilst developing rather than after it's been released)
Refer to tests when writing implementation code ONO merge conflict
---
## Core Testing Gate
| Description | When |
| --- | --- |
| Check test coverage and ensure all tests pass | When changing/refactoring existing code |
| Add new tests | When adding new features |
| Upload a test case that fails | When fixing bugs in PHP code |
| Add JavaScript test | When making JS changes |
| Manually test in browsers and provide screenshots or screencasts | When making markup or CSS changes |
| Provide an example module | When a new feature is not implemented by Drupal core |
[.footer: https://opdavi.es/drupal-core-testing-gate]
---
## Testing in Drupal
- **Drupal 7** - SimpleTest (testing) module provided as part of core
- **Drupal 8** - PHPUnit added as a core dependency, later became the default via the PHPUnit initiative
- **Drupal 9** - SimpleTest removed, moved to contrib
---
## Writing Tests (Drupal 8)
- PHP class with **.php** extension
- **tests/src** directory within each module
- Within the **Drupal\Tests\module_name** namespace
- Class name must match the filename
- Namespace must match the directory structure
- One test class per feature
^ Different to D7
---
[.header: alignment(center)]
## **Arrange**
## **Act**
## **Assert**
---
[.header: alignment(center)]
## **Given**
## **When**
## **Then**
---
[.header: alignment(center)]
## **Given** the About page exists
## **When** I go to the page
## **Then** I should see "About Me"
---
```php, [.highlight: 1-3]
// modules/example/tests/src/Functional/ExampleTest.php
namespace Drupal\Tests\example\Functional;
use Drupal\Tests\BrowserTestBase;
class ExampleTest extends BrowserTestBase {
public function testSomething() {
// Given...
// When...
// Then...
}
}
```
---
```php, [.highlight: 5-7]
// modules/example/tests/src/Functional/ExampleTest.php
namespace Drupal\Tests\example\Functional;
use Drupal\Tests\BrowserTestBase;
class ExampleTest extends BrowserTestBase {
public function testSomething() {
// Given...
// When...
// Then...
}
}
```
---
```php, [.highlight: 9,13]
// modules/example/tests/src/Functional/ExampleTest.php
namespace Drupal\Tests\example\Functional;
use Drupal\Tests\BrowserTestBase;
class ExampleTest extends BrowserTestBase {
public function testSomething() {
// Given...
// When...
// Then...
}
}
```
---
```php, [.highlight: 10-12]
// modules/example/tests/src/Functional/ExampleTest.php
namespace Drupal\Tests\example\Functional;
use Drupal\Tests\BrowserTestBase;
class ExampleTest extends BrowserTestBase {
public function testSomething() {
// Given...
// When...
// Then...
}
}
```
---
```php
public function testSomething() {}
public function test_something() {}
/** @test */
public function it_does_something() {}
```
---
## What to test?
- Creating nodes with data from an API
- Calculating attendance figures for an event
- Determining if an event is purchasble
- Promotions and coupons for new users
- Cloning events
- Queuing private message requests
- Emails for new memberships
- Closed support tickets are re-opened when comments are added
- Custom form validation rules
^ Examples of some things that I tested on a previous project.
---
[.background-color: #FFFFFF]
![inline 125%](images/matt-stauffer-tweet.png)
---
[.header: alignment(center) ]
## **Types of Tests**
---
## Types of Tests
- **Functional** / **FunctionalJavascript** (web, browser, feature)
- **Kernel** (integration)
- **Unit**
---
## Functional Tests
- Tests end-to-end functionality
- UI testing
- Interacts with database
- Full Drupal installation
- Slower to run
- With/without JavaScript
^ testing profile Functional/FunctionalJavascript Nightwatch
---
```php
class BlockTest extends BlockTestBase {
public function testBlockVisibility() {
$block_name = 'system_powered_by_block';
$title = $this->randomMachineName(8);
$default_theme = $this->config('system.theme')->get('default');
$edit = [
'id' => strtolower($this->randomMachineName(8)),
'region' => 'sidebar_first',
'settings[label]' => $title,
'settings[label_display]' => TRUE,
];
$edit['visibility[request_path][pages]'] = '/user*';
$edit['visibility[request_path][negate]'] = TRUE;
$edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
// ...
}
```
---
```php, [.highlight: 4-18]
class BlockTest extends BlockTestBase {
public function testBlockVisibility() {
$block_name = 'system_powered_by_block';
$title = $this->randomMachineName(8);
$default_theme = $this->config('system.theme')->get('default');
$edit = [
'id' => strtolower($this->randomMachineName(8)),
'region' => 'sidebar_first',
'settings[label]' => $title,
'settings[label_display]' => TRUE,
];
$edit['visibility[request_path][pages]'] = '/user*';
$edit['visibility[request_path][negate]'] = TRUE;
$edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
// ...
}
```
^ Arrange step
---
```php
class BlockTest extends BlockTestBase {
public function testBlockVisibility() {
// ...
$this->drupalGet('admin/structure/block/add/' . $block_name . '/' . $default_theme);
$this->assertFieldChecked('edit-visibility-request-path-negate-0');
$this->drupalPostForm(NULL, $edit, t('Save block'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->clickLink('Configure');
$this->assertFieldChecked('edit-visibility-request-path-negate-1');
$this->drupalGet('');
$this->assertText($title, 'Block was displayed on the front page.');
$this->drupalGet('user');
$this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
// ...
}
```
---
```php, [.highlight: 6,9,12,15,18]
class BlockTest extends BlockTestBase {
public function testBlockVisibility() {
// ...
$this->drupalGet('admin/structure/block/add/' . $block_name . '/' . $default_theme);
$this->assertFieldChecked('edit-visibility-request-path-negate-0');
$this->drupalPostForm(NULL, $edit, t('Save block'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->clickLink('Configure');
$this->assertFieldChecked('edit-visibility-request-path-negate-1');
$this->drupalGet('');
$this->assertText($title, 'Block was displayed on the front page.');
$this->drupalGet('user');
$this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
// ...
}
```
---
```php, [.highlight: 7,10,13,16,19]
class BlockTest extends BlockTestBase {
public function testBlockVisibility() {
// ...
$this->drupalGet('admin/structure/block/add/' . $block_name . '/' . $default_theme);
$this->assertFieldChecked('edit-visibility-request-path-negate-0');
$this->drupalPostForm(NULL, $edit, t('Save block'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->clickLink('Configure');
$this->assertFieldChecked('edit-visibility-request-path-negate-1');
$this->drupalGet('');
$this->assertText($title, 'Block was displayed on the front page.');
$this->drupalGet('user');
$this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
// ...
}
```
---
## Kernel Tests
- Integration tests
- Can install modules, interact with services, container, database
- Minimal Drupal bootstrap
- Faster than functional tests
- More setup required
---
```php
class BlockRebuildTest extends KernelTestBase {
use BlockCreationTrait;
public static $modules = ['block', 'system'];
protected function setUp() {
parent::setUp();
$this->container->get('theme_installer')
->install(['stable', 'classy']);
$this->container->get('config.factory')
->getEditable('system.theme')
->set('default', 'classy')
->save();
}
// ...
}
```
---
```php, [.highlight: 5, 10-16]
class BlockRebuildTest extends KernelTestBase {
use BlockCreationTrait;
public static $modules = ['block', 'system'];
protected function setUp() {
parent::setUp();
$this->container->get('theme_installer')
->install(['stable', 'classy']);
$this->container->get('config.factory')
->getEditable('system.theme')
->set('default', 'classy')
->save();
}
// ...
}
```
^ Still have access to the service container, via $this->container
---
```php
class BlockRebuildTest extends KernelTestBase {
// ...
public function testRebuildNoBlocks() {
block_rebuild();
$messages = \Drupal::messenger()->all();
\Drupal::messenger()->deleteAll();
$this->assertEquals([], $messages);
}
}
```
^ Can still access services like \Drupal::messenger()
---
## Unit Tests
- Tests PHP logic
- No database interaction
- Fast to run
- Need to mock dependencies
- Can become tightly coupled
- Can be hard to refactor
---
```php
class BlockRepositoryTest extends UnitTestCase {
public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) {
$blocks = [];
foreach ($blocks_config as $block_id => $block_config) {
$block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->once())
->method('access')
->will($this->returnValue($block_config[0]));
$block->expects($block_config[0] ? $this->atLeastOnce() : $this->never())
->method('getRegion')
->willReturn($block_config[1]);
$block->expects($this->any())
->method('label')
->willReturn($block_id);
// ...
$blocks[$block_id] = $block;
}
// ...
}
}
```
---
```php, [.highlight: 7-20]
class BlockRepositoryTest extends UnitTestCase {
public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) {
$blocks = [];
foreach ($blocks_config as $block_id => $block_config) {
$block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->once())
->method('access')
->will($this->returnValue($block_config[0]));
$block->expects($block_config[0] ? $this->atLeastOnce() : $this->never())
->method('getRegion')
->willReturn($block_config[1]);
$block->expects($this->any())
->method('label')
->willReturn($block_id);
// ...
$blocks[$block_id] = $block;
}
// ...
}
}
```
---
```php
class BlockRepositoryTest extends UnitTestCase {
public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) {
// ..
$this->blockStorage->expects($this->once())
->method('loadByProperties')
->with(['theme' => $this->theme])
->willReturn($blocks);
$result = [];
$cacheable_metadata = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) {
$result[$region] = [];
foreach ($resulting_blocks as $plugin_id => $block) {
$result[$region][] = $plugin_id;
}
}
$this->assertEquals($expected_blocks, $result);
}
}
```
---
```php, [.highlight: 14-22]
class BlockRepositoryTest extends UnitTestCase {
public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) {
// ..
$this->blockStorage->expects($this->once())
->method('loadByProperties')
->with(['theme' => $this->theme])
->willReturn($blocks);
$result = [];
$cacheable_metadata = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) {
$result[$region] = [];
foreach ($resulting_blocks as $plugin_id => $block) {
$result[$region][] = $plugin_id;
}
}
$this->assertEquals($expected_blocks, $result);
}
}
```
---
[.header: alignment(center)]
# **Example**
---
![](images/broadbean-website.png)
---
## Specification
- Job adverts created in Broadbean UI, needs to create nodes in Drupal
- Application URL links users to separate application system
- Jobs need to be linked to offices
- Job length specified in number of days
- Path is specified as a field in the API
- Application URL constructed from domain, includes role ID as a GET parameter and optionally UTM parameters
---
[.background-color: #FFFFFF]
![inline 125%](images/broadbean-drupal-flow-2.png)
---
## Implementation
- Added route to accept data from API as XML
- Added system user with API role to authenticate
- **active_for** converted from number of days to UNIX timestamp
- **branch_name** and **locations** converted from plain text to entity reference (job node to office node)
- **url_alias** property mapped to **path**
---
```php
$data = [
'command' => 'add',
'username' => 'bobsmith',
'password' => 'p455w0rd',
'active_for' => '365',
'application_email' => 'bob.12345.123@smith.aplitrak.com',
'branch_address' => '123 Fake St, Bristol, BS1 2AB',
'branch_name' => 'Test',
'contract' => 'Temporary',
'details' => 'This is the detailed description.',
'job_id' => 'abc123_1234567',
'job_title' => 'Healthcare Assistant (HCA)',
'job_type' => 'Care at Home',
'keywords' => 'flexible, Bristol, part-time',
'locations' => 'Bath, Devizes',
'role_id' => 'A/52/86',
'salary' => '32,000.00 per annum',
'salary_prefix' => 'Basic Salary',
'status' => 'Part time',
'summary' => 'This is the short description.',
'url_alias' => 'healthcare-assistant-aldershot-june17',
];
```
---
```php, [.highlight: 5,8,15,21]
$data = [
'command' => 'add',
'username' => 'bobsmith',
'password' => 'p455w0rd',
'active_for' => '365',
'application_email' => 'bob.12345.123@smith.aplitrak.com',
'branch_address' => '123 Fake St, Bristol, BS1 2AB',
'branch_name' => 'Test',
'contract' => 'Temporary',
'details' => 'This is the detailed description.',
'job_id' => 'abc123_1234567',
'job_title' => 'Healthcare Assistant (HCA)',
'job_type' => 'Care at Home',
'keywords' => 'flexible, Bristol, part-time',
'locations' => 'Bath, Devizes',
'role_id' => 'A/52/86',
'salary' => '32,000.00 per annum',
'salary_prefix' => 'Basic Salary',
'status' => 'Part time',
'summary' => 'This is the short description.',
'url_alias' => 'healthcare-assistant-aldershot-june17',
];
```
---
## Implementation
- If no error, create the job node, return OK response to Broadbean
- If an Exception is thrown, return an error code and message
^ Required field missing Incorrect branch name
---
## Testing Goals
- Ensure job nodes are **successfully created**
- Ensure that fields are **mapped correctly**
- Ensure that **calculations are correct**
- Ensure that entity references are **linked correctly**
---
## Types of tests
- **Functional:** job nodes are created with the correct URL and the correct response code is returned
- **FunctionalJavaScript:** application URL is updated with JavaScript based on UTM parameters (hosting)
---
## Types of tests
- **Kernel:** job nodes can be added and deleted, expired job nodes are deleted, application URL is generated correctly
- **Unit:** ensure number of days are converted to timestamps correctly
---
## Results
- **0 bugs!**
- Easier to identify where issues occurred and responsibilities
- Reduced debugging time
- Added more tests for any bugs to prevent
---
[.header: alignment(center)]
## **Running Tests**
---
## Core script
```
$ php core/scripts/run-tests.sh
$ php core/scripts/run-tests.sh --module example
$ php core/scripts/run-tests.sh --class ExampleTest
```
---
## PHPUnit
```
$ vendor/bin/phpunit \
-c core \
modules/contrib/examples/phpunit_example
```
---
## Prerequisite (creating a phpunit.xml file)
- Configures PHPUnit
- Needed to run some types of tests
- Ignored by Git by default
- Copy **core/phpunit.xml.dist** to **core/phpunit.xml**
- Add and change as needed
- `SIMPLETEST_BASE_URL`, `SIMPLETEST_DB`, `BROWSERTEST_OUTPUT_DIRECTORY`
- `stopOnFailure="true"`
---
[.header: alignment(center)]
## **Test Driven<br> Development**
---
## Test Driven Development
- Write a test
- Test fails
- Write code
- Test passes
- Refactor
- Repeat
---
[.background-color: #FFFFFF]
[.footer: https://github.com/foundersandcoders/testing-tdd-intro]
[.footer-style: #2F2F2F]
![100%](images/tdd-loop.png)
---
[.header: alignment(center)]
## **Red, Green, Refactor**
---
## Porting Modules to Drupal 8
- Make a new branch
- Add/update the tests
- Write code to make the tests pass
- Refactor
- Repeat
---
## How I Write Tests - "Outside In"
- Start with functional tests
- Drop down to integration or unit tests where needed
- Programming by wishful thinking
- Write comments first, then fill in the code
- Sometimes write assertions first
---
[.header: alignment(center)]
## **Demo: Building a Blog module**
---
## Acceptance criteria
- As a site visitor
- I want to see a list of published articles at /blog
- Ordered by post date
---
## Tasks
- Ensure the blog page exists
- Ensure only published articles are shown
- Ensure the articles are shown in the correct order
---
[.background-color: #FFFFFF]
![140%](images/tawny-tweet-1.png)
---
[.background-color: #FFFFFF]
![150%](images/tawny-tweet-2.png)
---
[.header: alignment(center)]
## **TestDrivenDrupal.com**
---
![100%](../images/drupal-association-logo.png)
---
[.header: alignment(center)]
# **Questions?**
### @opdavies
### oliverdavies.uk

View file

@ -1,2 +0,0 @@
- Show composer.json setup
- Add "things you can test" and "things you shouldn't test"