talks/tdd-test-driven-drupal/slides.md

2333 lines
49 KiB
Markdown

theme: poster, 8
autoscale: true
build-lists: true
header-emphasis: #53B0EB
header: alignment(left)
text: alignment(left)
text-emphasis: #53B0EB
code: Monaco, #6699FF, #999999, #6666FF, #66FF66, #66FF66, line-height(1.3)
[.header: alignment(center)]
![](images/title.png)
# _TDD:_ Test <br>Driven Drupal
---
[.header: alignment(center)]
## [fit] opdavi.es/_tdd-test-driven-drupal_
^ View on the website or click through to Speakerdeck
---
[.background-color: #FFFFFF]
[.header: #111111, alignment(left)]
## Diamond sponsor
![inline 100%](images/ddd-2.jpeg)
---
[.background-color: #FFFFFF]
[.header: #111111, alignment(left)]
## Platinum sponsors
![inline 100%](images/ddd-3.jpeg)
---
[.background-color: #FFFFFF]
[.header: #111111, alignment(left)]
## Gold sponsors
![inline 100%](images/ddd-4.jpeg)
---
![fit](images/ddd-5.jpg)
---
[.build-lists: false]
- Module and theme developers
- Want to know more about automated testing
- Looking to start writing your first tests
- Drupal 8
- PHPUnit
---
- Why write tests, and what to test
- Types of tests
- How to run tests
- Real life example
- Building a new module with TDD
---
[.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 Developer at Microserve
- Part-time freelancer
- Acquia certified Drupal 8 Grand Master
- Drupal 7 & 8 core contributor
- Symfony, Laravel, ~~Silex,~~ Sculpin
- @opdavies
- www.oliverdavies.uk
^ Work at Microserve.
Maintain Drupal modules, PHP CLI tools and libraries
Blog on my website
---
[.build-lists: false]
- oliverdavies.uk/_talks_
- oliverdavies.uk/_twitter_
- oliverdavies.uk/_drupal_
- oliverdavies.uk/_github_
^ Example code on GitHub
---
[.background-color: #FFFFFF]
[.text: #111111, alignment(left)]
![right 100%](../images/microserve-light.png)
- https://microserve.io
- https://www.drupal.org/microserve
- https://github.com/microserve-io
- https://twitter.com/microserveltd
- https://www.linkedin.com/company/microserve-ltd
---
[.header: alignment(center)]
## test_driven_drupal_.com_
---
[.header: alignment(center)]
## Write custom modules and themes _for clients_
---
[.header: alignment(center)]
## Occassionally <br>contribute _to core_
---
[.header: alignment(center)]
## Maintain and contribute to _contrib projects_
---
[.background-color: #FFFFFF]
![inline 150%](images/timmillwood-ono.png)
---
## _Override Node Options_
- Become maintainer in 2012
- Had some existing tests
- Used on _11,046 sites_ in October 2012 (_84_ D5, _7,094_ D6, _3,868_ D7)
- Used on _29,023 sites_ in June 2018 (_9_ D5, _1,853_ D6, _23,602_ D7, _3,559_ D8)
- _#236_ most used module on Drupal.org
- Crucial to preventing regressions when adding new features or fixing bugs
^ Preventing regressions in my additions but also user submitted patches
First module I ported to Drupal 8, aided by tests
---
[.header: alignment(center)]
## _Why_ write tests?
---
## _Why write tests?_
- Catch bugs earlier
- Peace of mind
- Prevent regressions
- Write less code
- Documentation
- Drupal core requirement - _<https://www.drupal.org/core/gates#testing>_
- More important with regular D8 releases
^ 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_
- New features should be accompanied by automated tests.
- If the feature does not have an implementation, provide a test implementation.
- Bug fixes should be accompanied by changes to a test (either modifying an existing test case or adding a new one) that demonstrate the bug.
[.footer: https://www.drupal.org/core/gates#testing]
---
## _Testing in Drupal_
- _Drupal 7_ - SimpleTest (testing) module provided as part of core
- _Drupal 8_ - PHPUnit added as a core dependency
- _PHPUnit Initiative_ - SimpleTest to be deprecated and removed in Drupal 9
---
[.header: #53B0EB]
## 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
- Each method must start with _test_
^ Different to D7
---
[.header: alignment(center)]
## _1._ Arrange
## _2._ Act
## _3._ Assert
---
```php
// modules/example/tests/src/Functional/ExampleTest.php
namespace Drupal\Tests\example\Functional;
use Drupal\Tests\BrowserTestBase;
class ExampleTest extends BrowserTestBase {
public function testSomething() {
// Arrange
// Act
// Assert
}
}
```
^ PHP class
Filename matches class name
Namespace matches directory structure
Extend BrowserTestBase
Add test method
---
[.header: #53B0EB]
## 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
---
[.header: #53B0EB]
## What to test first?
- What is the core piece of functionality?
- What provides the most value to the client?
- What would you not like to be fixing on a Friday afternoon or after hours?
^ Payments! Anything related to money.
What would provide the largest negative impact to the client if it were to fail?
---
[.background-color: #FFFFFF]
![inline 150%](images/matt-stauffer-tweet.png)
---
[.header: #53B0EB]
## What to test first?
- Write a _new test_ when adding any _new functionality_
- Write a _regression test_ when _fixing a bug_
^ Use tests to replicate the bug
Could be a new test, or adding to an existing test
Test passes when the bug is fixed
That issue cannot be re-added without the test failing again
---
[.header: #53B0EB]
## Types of tests
- Unit
- Kernel _(integration)_
- Functional / FunctionalJavascript _(web, browser, feature)_
---
[.header: #53B0EB]
## 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
// tests/src/Unit/JobTest.php
namespace Drupal\Tests\advancedqueue\Unit;
use Drupal\advancedqueue\Job;
use Drupal\Tests\UnitTestCase;
class JobTest extends UnitTestCase {
public function testCreate() {
$job = Job::create('test', ['my' => 'data']);
$this->assertEquals('test', $job->getType());
$this->assertEquals(['my' => 'data'], $job->getPayload());
$this->assertEquals(Job::STATE_QUEUED, $job->getState());
}
}
```
^ Within a Unit directory and namespace
Called JobTest because it's testing the Job class
Called testCreate because it's testing the create method
Create a job with the create method
Retrieve data from the object with getters
---
```php, [.highlight: 11]
// tests/src/Unit/JobTest.php
namespace Drupal\Tests\advancedqueue\Unit;
use Drupal\advancedqueue\Job;
use Drupal\Tests\UnitTestCase;
class JobTest extends UnitTestCase {
public function testCreate() {
$job = Job::create('test', ['my' => 'data']);
$this->assertEquals('test', $job->getType());
$this->assertEquals(['my' => 'data'], $job->getPayload());
$this->assertEquals(Job::STATE_QUEUED, $job->getState());
}
}
```
---
```php, [.highlight: 13-15]
// tests/src/Unit/JobTest.php
namespace Drupal\Tests\advancedqueue\Unit;
use Drupal\advancedqueue\Job;
use Drupal\Tests\UnitTestCase;
class JobTest extends UnitTestCase {
public function testCreate() {
$job = Job::create('test', ['my' => 'data']);
$this->assertEquals('test', $job->getType());
$this->assertEquals(['my' => 'data'], $job->getPayload());
$this->assertEquals(Job::STATE_QUEUED, $job->getState());
}
}
```
---
[.header: #53B0EB]
## Kernel Tests
- Integration tests
- Can install modules, interact with services, container, database
- Minimal Drupal bootstrap
- Faster than functional tests
- More setup required
---
```php
// tests/src/Kernel/ProcessorTest.php
namespace Drupal\Tests\advancedqueue\Kernel;
use Drupal\advancedqueue\Entity\Queue;
use Drupal\advancedqueue\Job;
use Drupal\KernelTests\KernelTestBase;
class ProcessorTest extends KernelTestBase {
...
}
```
---
```php
// tests/src/Kernel/ProcessorTest.php
protected function setUp() {
parent::setUp();
$this->installSchema('advancedqueue', ['advancedqueue']);
$this->queue = Queue::create([
'id' => 'test',
'label' => 'Test queue',
'backend' => 'database',
'backend_configuration' => [
'lease_time' => 5,
],
]);
$this->queue->save();
$this->processor = $this->container->get('advancedqueue.processor');
}
```
---
```php, [.highlight: 6]
// tests/src/Kernel/ProcessorTest.php
protected function setUp() {
parent::setUp();
$this->installSchema('advancedqueue', ['advancedqueue']);
$this->queue = Queue::create([
'id' => 'test',
'label' => 'Test queue',
'backend' => 'database',
'backend_configuration' => [
'lease_time' => 5,
],
]);
$this->queue->save();
$this->processor = $this->container->get('advancedqueue.processor');
}
```
---
```php, [.highlight: 8-16]
// tests/src/Kernel/ProcessorTest.php
protected function setUp() {
parent::setUp();
$this->installSchema('advancedqueue', ['advancedqueue']);
$this->queue = Queue::create([
'id' => 'test',
'label' => 'Test queue',
'backend' => 'database',
'backend_configuration' => [
'lease_time' => 5,
],
]);
$this->queue->save();
$this->processor = $this->container->get('advancedqueue.processor');
}
```
---
```php, [.highlight: 18]
// tests/src/Kernel/ProcessorTest.php
protected function setUp() {
parent::setUp();
$this->installSchema('advancedqueue', ['advancedqueue']);
$this->queue = Queue::create([
'id' => 'test',
'label' => 'Test queue',
'backend' => 'database',
'backend_configuration' => [
'lease_time' => 5,
],
]);
$this->queue->save();
$this->processor = $this->container->get('advancedqueue.processor');
}
```
---
```php
// tests/src/Kernel/ProcessorTest.php
public function testProcessor() {
$first_job = Job::create('simple', [
'test' => '1',
]);
$second_job = Job::create('flexible', [
'expected_state' => Job::STATE_SUCCESS,
'expected_message' => 'Done!',
]);
$third_job = Job::create(
'flexible', ['expected_exception' => 'DB down!'],
);
$fourth_job = Job::create('flexible', [
'expected_state' => Job::STATE_FAILURE,
'expected_message' => 'Failed!',
]);
...
}
```
---
```php, [.highlight: 6-10]
// tests/src/Kernel/ProcessorTest.php
public function testProcessor() {
...
$this->queue->enqueueJob($first_job);
$this->queue->enqueueJob($second_job);
$this->queue->enqueueJob($third_job);
$this->queue->enqueueJob($fourth_job);
$num_processed = $this->processor->processQueue($this->queue);
$this->assertEquals(4, $num_processed);
}
```
---
```php, [.highlight: 11-13]
// tests/src/Kernel/ProcessorTest.php
public function testProcessor() {
...
$this->queue->enqueueJob($first_job);
$this->queue->enqueueJob($second_job);
$this->queue->enqueueJob($third_job);
$this->queue->enqueueJob($fourth_job);
$num_processed = $this->processor->processQueue($this->queue);
$this->assertEquals(4, $num_processed);
}
```
---
[.header: #53B0EB]
## Functional Tests
- Tests end-to-end functionality
- UI testing
- Interacts with database
- Full Drupal installation
- Slower to run
- With/without JavaScript
^ testing profile
---
```php
// tests/src/Functional/QueueTest.php
namespace Drupal\Tests\advancedqueue\Functional;
use Drupal\advancedqueue\Entity\Queue;
use Drupal\advancedqueue\Entity\QueueInterface;
use Drupal\Tests\BrowserTestBase;
class QueueTest extends BrowserTestBase {
...
}
```
---
```php, [.highlight: 6-8]
// tests/src/Functional/QueueTest.php
protected function setUp() {
parent::setUp();
$this->placeBlock('local_tasks_block');
$this->placeBlock('local_actions_block');
$this->placeBlock('page_title_block');
$this->adminUser = $this->drupalCreateUser(['administer advancedqueue']);
$this->drupalLogin($this->adminUser);
}
```
---
```php, [.highlight: 10-11]
// tests/src/Functional/QueueTest.php
protected function setUp() {
parent::setUp();
$this->placeBlock('local_tasks_block');
$this->placeBlock('local_actions_block');
$this->placeBlock('page_title_block');
$this->adminUser = $this->drupalCreateUser(['administer advancedqueue']);
$this->drupalLogin($this->adminUser);
}
```
---
```php, [.highlight: 4-11]
// tests/src/Functional/QueueTest.php
public function testQueueDeletion() {
$queue = Queue::create([
'id' => 'test',
'label' => 'Test',
'backend' => 'database',
'processor' => QueueInterface::PROCESSOR_DAEMON,
'processing_time' => 100,
]);
$queue->save();
$this->drupalGet('admin/config/system/queues/manage/' . $queue->id() . '/delete');
$this->submitForm([], 'Delete');
$this->assertSession()->addressEquals('admin/config/system/queues');
$queue_exists = (bool) Queue::load('test');
$this->assertEmpty($queue_exists, 'The queue has been deleted from the database.');
}
```
---
```php, [.highlight: 12-14]
// tests/src/Functional/QueueTest.php
public function testQueueDeletion() {
$queue = Queue::create([
'id' => 'test',
'label' => 'Test',
'backend' => 'database',
'processor' => QueueInterface::PROCESSOR_DAEMON,
'processing_time' => 100,
]);
$queue->save();
$this->drupalGet('admin/config/system/queues/manage/' . $queue->id() . '/delete');
$this->submitForm([], 'Delete');
$this->assertSession()->addressEquals('admin/config/system/queues');
$queue_exists = (bool) Queue::load('test');
$this->assertEmpty($queue_exists, 'The queue has been deleted from the database.');
}
```
---
```php, [.highlight: 16-17]
// tests/src/Functional/QueueTest.php
public function testQueueDeletion() {
$queue = Queue::create([
'id' => 'test',
'label' => 'Test',
'backend' => 'database',
'processor' => QueueInterface::PROCESSOR_DAEMON,
'processing_time' => 100,
]);
$queue->save();
$this->drupalGet('admin/config/system/queues/manage/' . $queue->id() . '/delete');
$this->submitForm([], 'Delete');
$this->assertSession()->addressEquals('admin/config/system/queues');
$queue_exists = (bool) Queue::load('test');
$this->assertEmpty($queue_exists, 'The queue has been deleted from the database.');
}
```
---
[.header: #FFFFFF, alignment(left)]
### _How do I know_
## Which type of test to use?
---
### _Need a browser?_
## Use a functional test
---
### _Interact with other services?_
## Use a kernel test
---
### _Isolated PHP code?_
## Use a unit test
---
## _Should you test that_ <br>a block is rendered correctly?
---
## _Or should you test_ <br>your render array to generate the block?
^ The answer might be 'both'.
The right type of test to use might not be that obvious.
You may be able to use a different type of test if you take a different approach.
---
[.header: #53B0EB]
## Setup (functional)
```
drupalCreateUser()
drupalCreateRole()
drupalLogin()
drupalLogout()
drupalGet()
drupalPost()
drupalPostForm()
```
---
## _Setup (kernel)_
```php
# UserCreationTrait
createUser()
createAdminRole()
createRole()
checkPermissions()
# CommentTestTrait
addDefaultCommentField()
# AssertMailTrait
getMails()
assertMail()
```
---
[.header: #53B0EB]
## Assertions
```php
assertTrue()
assertFalse()
assertEquals()
assertSame()
assertNull()
assertNotNull()
assertCount()
assertEmpty()
assertArraySubset()
```
---
[.header: #53B0EB]
## Assertions (functional)
```php
assertSession()
pageTextContains()
pageTextNotContains()
linkByHrefExists()
linkByHrefNotExists()
statusCodeEquals()
statusCodeNotEquals()
```
---
[.header: alignment(center)]
# _Real life_ example
---
[.background-color: #FFFFFF]
[.footer-style: #2F2F2F]
![inline 150%](images/broadbean.png)
---
[.header: #53B0EB]
## 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-1.png)
---
[.background-color: #FFFFFF]
![inline 125%](images/broadbean-drupal-flow-2.png)
---
```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',
];
```
---
[.header: #53B0EB]
## 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*
---
[.header: #53B0EB]
## 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
---
[.header: #53B0EB]
## 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_
---
[.header: #53B0EB]
## Types of tests
- _Unit:_ ensure number of days are converted to timestamps correctly
- _Kernel:_ job nodes can be added and deleted, expired job nodes are deleted, application URL is generated correctly
- _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)
---
[.header: #53B0EB]
## Results
- _0 bugs!_
- Reduced debugging time
- Easier to identify where issues occurred and responsibilities
---
[.header: alignment(center)]
## Running Tests
---
### _Option 1_
## SimpleTest module (UI)
---
![fit](images/d8-simpletest-1.png)
---
![fit](images/d8-simpletest-2.png)
---
![fit](images/d8-simpletest-3.png)
---
![fit](images/d8-simpletest-4.png)
---
![fit](images/d8-simpletest-5.png)
---
![fit](images/d8-simpletest-6.png)
---
![fit](images/d8-simpletest-7.png)
---
### _Option 2_
## Core script
---
```
$ php core/scripts/run-tests.sh
$ php core/scripts/run-tests.sh --module example
$ php core/scripts/run-tests.sh --class ExampleTest
```
---
### _Option 3_
## PHPUnit
---
## 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"`
---
```
cd web
../vendor/bin/phpunit -c core \
modules/contrib/examples/phpunit_example
```
---
```
cd web/core
../../vendor/bin/phpunit \
../modules/contrib/examples/phpunit_example
```
---
```
--filter
--testsuite
--group
--colors
--stop-on-failure
--verbose --debug
```
---
![fit](images/phpstorm-integration.png)
---
[.header: alignment(center)]
## _Test Driven_<br> Development
---
[.header: #53B0EB]
## 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
---
[.header: #53B0EB]
## Porting Modules to Drupal 8
- Make a new branch
- Add/update the tests
- Write code to make the tests pass
- Refactor
- Repeat
---
[.header: #53B0EB]
## 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)]
## [fit] _Building a new Drupal 8 Module with_
## [fit] test driven development
---
[.header: #53B0EB]
## Acceptance criteria
- As a site visitor
- I want to see a list of published articles at /blog
- Ordered by post date
---
[.header: #53B0EB]
## Tasks
- Ensure the blog page exists
- Ensure only published articles are shown
- Ensure the articles are shown in the correct order
---
[.header: #53B0EB]
## Implementation
- Use views module
- Do the mininum amount at each step, make no assumptions, let the tests guide us
- Start with functional test
---
### _Step 1_
## Create the module
---
```yml
# tdd_blog.info.yml
name: 'TDD Blog'
core: '8.x'
type: 'module'
```
---
### _Step 2_
## Ensure the blog page exists
---
```php
// tests/src/Functional/BlogPageTest.php
namespace Drupal\Tests\tdd_blog\Functional;
use Drupal\Tests\BrowserTestBase;
class BlogPageTest extends BrowserTestBase {
protected static $modules = ['tdd_blog'];
public function testBlogPageExists() {
$this->drupalGet('/blog');
$this->assertSession()->statusCodeEquals(200);
}
}
```
---
```php, [.highlight: 3]
// tests/src/Functional/BlogPageTest.php
namespace Drupal\Tests\tdd_blog\Functional;
use Drupal\Tests\BrowserTestBase;
class BlogPageTest extends BrowserTestBase {
protected static $modules = ['tdd_blog'];
public function testBlogPageExists() {
$this->drupalGet('/blog');
$this->assertSession()->statusCodeEquals(200);
}
}
```
---
```php, [.highlight: 5-7]
// tests/src/Functional/BlogPageTest.php
namespace Drupal\Tests\tdd_blog\Functional;
use Drupal\Tests\BrowserTestBase;
class BlogPageTest extends BrowserTestBase {
protected static $modules = ['tdd_blog'];
public function testBlogPageExists() {
$this->drupalGet('/blog');
$this->assertSession()->statusCodeEquals(200);
}
}
```
---
```php, [.highlight: 9]
// tests/src/Functional/BlogPageTest.php
namespace Drupal\Tests\tdd_blog\Functional;
use Drupal\Tests\BrowserTestBase;
class BlogPageTest extends BrowserTestBase {
protected static $modules = ['tdd_blog'];
public function testBlogPageExists() {
$this->drupalGet('/blog');
$this->assertSession()->statusCodeEquals(200);
}
}
```
---
```php, [.highlight: 11-15]
// tests/src/Functional/BlogPageTest.php
namespace Drupal\Tests\tdd_blog\Functional;
use Drupal\Tests\BrowserTestBase;
class BlogPageTest extends BrowserTestBase {
protected static $modules = ['tdd_blog'];
public function testBlogPageExists() {
$this->drupalGet('/blog');
$this->assertSession()->statusCodeEquals(200);
}
}
```
---
```bash, [.highlight: 1]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 19.31 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13
ERRORS!
Tests: 1, Assertions: 3, Errors: 1.
```
---
```bash, [.highlight: 4]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 19.31 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13
ERRORS!
Tests: 1, Assertions: 3, Errors: 1.
```
---
```bash, [.highlight: 5-13]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 19.31 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13
ERRORS!
Tests: 1, Assertions: 3, Errors: 1.
```
---
```bash, [.highlight: 14-16]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 19.31 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13
ERRORS!
Tests: 1, Assertions: 3, Errors: 1.
```
---
```bash, [.highlight: 18-19]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 19.31 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13
ERRORS!
Tests: 1, Assertions: 3, Errors: 1.
```
---
- _The view has not been created_
- Create a new view, page display
- Set the path
- Export the config
- Copy it into the module's `config/install` directory
---
![fit](images/tdd-blog-1.png)
---
![fit](images/tdd-blog-2.png)
---
```
drush cex -y
cp ../config/default/views.view.blog.yml \
modules/custom/tdd_blog/config/install
```
---
```diff
# views.view.blog.yml
- uuid: 84305edf-7aef-4109-bc93-e87f685fb678
langcode: en
status: true
dependencies:
config:
- node.type.article
module:
- node
- user
- _core:
- default_config_hash: iGZkqLWpwWNORq6_fy6v_Kn_KE4BjYHqj9vpgQsWJCs
id: blog
...
```
---
```[.highlight: 11-13]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 16.02 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by <em class="placeholder">tdd_blog</em>
have unmet dependencies: <em class="placeholder">views.view.blog (node.type.article, node, views)</em>
/var/www/web/core/lib/Drupal/Core/Config/UnmetDependenciesException.php:98
/var/www/web/core/lib/Drupal/Core/Config/ConfigInstaller.php:469
/var/www/web/core/lib/Drupal/Core/ProxyClass/Config/ConfigInstaller.php:132
/var/www/web/core/lib/Drupal/Core/Extension/ModuleInstaller.php:145
/var/www/web/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php:83
/var/www/web/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:437
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:1055
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:490
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
```
---
```yml,[.highlight: 1, 7-10]
# tdd_blog.info.yml
name: 'TDD Blog'
description: 'A demo module to show test driven module development.'
core: 8.x
type: module
dependencies:
- 'drupal:node'
- 'drupal:views'
```
---
```[.highlight: 10-13]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
E 1 / 1 (100%)
Time: 20 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by <em class="placeholder">tdd_blog</em>
have unmet dependencies: <em class="placeholder">views.view.blog (node.type.article)</em>
/var/www/web/core/lib/Drupal/Core/Config/UnmetDependenciesException.php:98
/var/www/web/core/lib/Drupal/Core/Config/ConfigInstaller.php:469
/var/www/web/core/lib/Drupal/Core/ProxyClass/Config/ConfigInstaller.php:132
/var/www/web/core/lib/Drupal/Core/Extension/ModuleInstaller.php:145
/var/www/web/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php:83
/var/www/web/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:437
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:1055
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:490
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
```
---
- Add the article content type
---
```[.highlight: 5, 9]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog
. 1 / 1 (100%)
Time: 23.36 seconds, Memory: 6.00MB
OK (1 test, 3 assertions)
```
---
[.build-lists: false]
## _Tasks_
- ~~Ensure the blog page exists~~
- Ensure only published articles are shown
- Ensure the articles are shown in the correct order
---
### _Step 3_
## Ensure only published articles are shown
---
```php
public function testOnlyPublishedArticlesAreShown() {
// Given I have a mixture of published and unpublished articles,
// as well as other types of content.
// When I view the blog page.
// I should only see the published articles.
}
```
---
### _Option 1_
## Functional tests
---
```php,[.highlight: 1, 4-8]
// modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php
public function testOnlyPublishedArticlesAreShown() {
// Given I have a mixture of published and unpublished articles,
// as well as other types of content.
$node1 = $this->drupalCreateNode(['type' => 'page', 'status' => 1]);
$node2 = $this->drupalCreateNode(['type' => 'article', 'status' => 1]);
$node3 = $this->drupalCreateNode(['type' => 'article', 'status' => 0]);
// When I view the blog page.
$this->drupalGet('/blog');
// I should only see the published articles.
$assert = $this->assertSession();
$assert->pageTextContains($node2->label());
$assert->pageTextNotContains($node1->label());
$assert->pageTextNotContains($node3->label());
}
```
^ Different ways to achieve this. This is taking the functional test approach.
---
```php,[.highlight: 10-12]
// modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php
public function testOnlyPublishedArticlesAreShown() {
// Given I have a mixture of published and unpublished articles,
// as well as other types of content.
$node1 = $this->drupalCreateNode(['type' => 'page', 'status' => 1]);
$node2 = $this->drupalCreateNode(['type' => 'article', 'status' => 1]);
$node3 = $this->drupalCreateNode(['type' => 'article', 'status' => 0]);
// When I view the blog page.
$this->drupalGet('/blog');
// I should only see the published articles.
$assert = $this->assertSession();
$assert->pageTextContains($node2->label());
$assert->pageTextNotContains($node1->label());
$assert->pageTextNotContains($node3->label());
}
```
---
```php, [.highlight: 13-17]
// modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php
public function testOnlyPublishedArticlesAreShown() {
// Given I have a mixture of published and unpublished articles,
// as well as other types of content.
$node1 = $this->drupalCreateNode(['type' => 'page', 'status' => 1]);
$node2 = $this->drupalCreateNode(['type' => 'article', 'status' => 1]);
$node3 = $this->drupalCreateNode(['type' => 'article', 'status' => 0]);
// When I view the blog page.
$this->drupalGet('/blog');
// I should only see the published articles.
$assert = $this->assertSession();
$assert->pageTextContains($node2->label());
$assert->pageTextNotContains($node1->label());
$assert->pageTextNotContains($node3->label());
}
```
---
### _Option 2_
## Kernel tests
---
```php
namespace Drupal\Tests\tdd_blog\Kernel;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
class BlogPageTest extends EntityKernelTestBase {
use NodeCreationTrait;
public static $modules = ['node'];
public function testOnlyPublishedArticlesAreShown() {
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
}
}
```
^ Kernel test approach
Dropping down a level
No need for the brower, not asserting against HTML
Faster to run
---
```php, [.highlight: 1-6]
namespace Drupal\Tests\tdd_blog\Kernel;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
class BlogPageTest extends EntityKernelTestBase {
use NodeCreationTrait;
public static $modules = ['node'];
public function testOnlyPublishedArticlesAreShown() {
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
}
}
```
---
```php, [.highlight: 8]
namespace Drupal\Tests\tdd_blog\Kernel;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
class BlogPageTest extends EntityKernelTestBase {
use NodeCreationTrait;
public static $modules = ['node'];
public function testOnlyPublishedArticlesAreShown() {
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
}
}
```
---
```php, [.highlight: 12-16]
namespace Drupal\Tests\tdd_blog\Kernel;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
class BlogPageTest extends EntityKernelTestBase {
use NodeCreationTrait;
public static $modules = ['node'];
public function testOnlyPublishedArticlesAreShown() {
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
}
}
```
---
```[.highlight: 9-16]
docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog/tests/src/Kernel/
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing modules/custom/tdd_blog/tests/src/Kernel/
E 1 / 1 (100%)
Time: 6.22 seconds, Memory: 6.00MB
There was 1 error:
1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testOnlyPublishedArticlesAreShown
Error: Call to a member function id() on boolean
/var/www/web/core/modules/filter/filter.module:212
/var/www/web/core/modules/node/tests/src/Traits/NodeCreationTrait.php:73
/var/www/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:13
ERRORS!
Tests: 1, Assertions: 2, Errors: 1.
```
---
```php, [.highlight: 2]
public function testOnlyPublishedArticlesAreShown() {
$this->installConfig(['filter']);
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
}
```
---
```php, [.highlight: 8]
public function testOnlyPublishedArticlesAreShown() {
$this->installConfig(['filter']);
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
$results = views_get_view_result('blog');
}
```
---
```php, [.highlight: 3]
...
public static $modules = ['node', 'tdd_blog', 'views'];
public function testOnlyPublishedArticlesAreShown() {
$this->installConfig(['filter', 'tdd_blog']);
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
$results = views_get_view_result('blog');
$this->assertCount(1, $results);
$this->assertEquals(2, $results[0]->_entity->id());
}
```
---
```php, [.highlight: 6]
...
public static $modules = ['node', 'tdd_blog', 'views'];
public function testOnlyPublishedArticlesAreShown() {
$this->installConfig(['filter', 'tdd_blog']);
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
$results = views_get_view_result('blog');
$this->assertCount(1, $results);
$this->assertEquals(2, $results[0]->_entity->id());
}
```
---
```php, [.highlight: 8-15]
...
public static $modules = ['node', 'tdd_blog', 'views'];
public function testOnlyPublishedArticlesAreShown() {
$this->installConfig(['filter', 'tdd_blog']);
$this->createNode(['type' => 'page', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 1]);
$this->createNode(['type' => 'article', 'status' => 0]);
$results = views_get_view_result('blog');
$this->assertCount(1, $results);
$this->assertEquals(2, $results[0]->_entity->id());
}
```
^ Assert
Should only be one result, should be node 2
Node IDs are reset on each test method
---
```
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing web/modules/custom/tdd_blog/tests/src/Kernel
F 1 / 1 (100%)
Time: 2.16 seconds, Memory: 6.00MB
There was 1 failure:
1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testOnlyPublishedArticlesAreShown
Failed asserting that actual size 2 matches expected size 1.
/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:23
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
```
---
```[.highlight: 8-13]
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing web/modules/custom/tdd_blog/tests/src/Kernel
F 1 / 1 (100%)
Time: 2.16 seconds, Memory: 6.00MB
There was 1 failure:
1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testOnlyPublishedArticlesAreShown
Failed asserting that actual size 2 matches expected size 1.
/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:23
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
```
---
![inline](images/tdd-blog-3.png)
---
>- _There is no content type filter on the view_
- Add the filter
- Re-export and save the view
---
![inline](images/tdd-blog-4.png)
---
```[.highlight: 3-8]
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing web/modules/custom/tdd_blog/tests/src/Kernel
. 1 / 1 (100%)
Time: 2.02 seconds, Memory: 6.00MB
OK (1 test, 6 assertions)
```
---
[.build-lists: false]
## _Tasks_
- ~~Ensure the blog page exists~~
- ~~Ensure only published articles are shown~~
- Ensure the articles are shown in the correct order
---
### _Step 4_
## Ensure the articles are ordered by date
---
```php
// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php
public function testArticlesAreOrderedByDate() {
// Given that I have numerous articles with different post dates.
// When I go to the blog page.
// The articles are ordered by post date.
}
```
---
```php, [.highlight: 4-9]
// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php
public function testArticlesAreOrderedByDate() {
// Given that I have numerous articles with different post dates.
$this->createNode(['type' => 'article', 'created' => (new \DateTime('+1 day'))->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime('+1 month'))->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime('+3 days'))->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime('+1 hour'))->getTimestamp()]);
// When I go to the blog page.
// The articles are ordered by post date.
}
```
---
```php
$this->createNode([
'type' => 'article',
'created' => (new \DateTime())->modify('+1 day')->getTimestamp(),
]);
```
^ Array of default values
---
```php, [.highlight: 10-11]
// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php
public function testArticlesAreOrderedByDate() {
// Given that I have numerous articles with different post dates.
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);
// When I go to the blog page.
$results = views_get_view_result('blog');
// The articles are ordered by post date.
}
```
---
```php, [.highlight:10-15]
// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php
public function testArticlesAreOrderedByDate() {
// Given that I have numerous articles with different post dates.
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);
// When I go to the blog page.
$results = views_get_view_result('blog');
$nids = array_map(function(ResultRow $result) {
return $result->_entity->id();
}, $results);
// The articles are ordered by post date.
}
```
---
```php, [.highlight: 5-9, 17-18]
// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php
public function testArticlesAreOrderedByDate() {
// Given that I have numerous articles with different post dates.
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
$this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);
// When I go to the blog page.
$results = views_get_view_result('blog');
$nids = array_map(function(ResultRow $result) {
return $result->_entity->id();
}, $results);
// The articles are ordered by post date.
$this->assertEquals([4, 1, 3, 2], $nids);
}
```
---
```
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing web/modules/custom/tdd_blog/tests/src/Kernel
F 1 / 1 (100%)
Time: 1.42 seconds, Memory: 6.00MB
There was 1 failure:
1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testArticlesAreOrderedByDate
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
- 0 => 4
- 1 => 1
- 2 => 3
- 3 => 2
+ 0 => '1'
+ 1 => '2'
+ 2 => '3'
+ 3 => '4'
/Users/opdavies/Code/drupal-testing-workshop/web/core/tests/Drupal/KernelTests/KernelTestBase.php:1114
/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:43
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
```
---
[.text: comic sans]
```[.highlight: 8-26]
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing web/modules/custom/tdd_blog/tests/src/Kernel
F 1 / 1 (100%)
Time: 1.42 seconds, Memory: 6.00MB
There was 1 failure:
1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testArticlesAreOrderedByDate
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
- 0 => 4
- 1 => 1
- 2 => 3
- 3 => 2
+ 0 => '1'
+ 1 => '2'
+ 2 => '3'
+ 3 => '4'
/Users/opdavies/Code/drupal-testing-workshop/web/core/tests/Drupal/KernelTests/KernelTestBase.php:1114
/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:43
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
```
---
![inline](images/tdd-blog-4.png)
---
- _There is no sort order defined on the view_
- Add the sort order
- Re-export the view
---
![inline](images/tdd-blog-5.png)
---
```[.highlight:3-8]
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
Testing web/modules/custom/tdd_blog/tests/src/Kernel
. 1 / 1 (100%)
Time: 1.74 seconds, Memory: 6.00MB
OK (1 test, 5 assertions)
```
---
[.build-lists: false]
## _Tasks_
- ~~Ensure the blog page exists~~
- ~~Ensure only published articles are shown~~
- ~~Ensure the articles are shown in the correct order~~
---
![inline fit](images/tdd-blog-directories.png)
---
![fit](images/tdd-blog-installed.png)
^ Using the minimal installation profile
Post 3 is unpublished
---
![inline 75%](images/tada.png)
---
[.header: alignment(center)]
## Take Aways
---
- Testing has made me a _better developer_
- Testing can produce _better quality code_
- Use the _right type of test_ for the right situation
- Use the _right base class_, use available _traits_
- Writing tests is an _investment_
- OK to _start small_, introduce tests gradually
- Easier to _refactor_
- Tests can pass, but things can _still be broken_. Tests only report on what they cover.
^ Made me think about how I'm going to do something more starting to do it
Less cruft, only write code that serves a purpose
Spending time writing tests pays dividends later on
Start by introducing tests for new features or regression tests when fixing bugs
If you know things pass, then you can refactor code knowing if something is broken
Manual testing is still important
---
[.header: alignment(center)]
## [fit] _Having tests does not mean_
## [fit] there will be no bugs
^ Only means that the tests you wrote are passing
You may not have included a certain use case
Be sure to test in the UI!
We can test what happens in a test when a user has a permission, but in our site we still need to assign the permission to a role and the role to a user.
---
### _You might be testing the wrong thing_
## Maybe it doesn't work the way you think it does
---
### _Have you written enough assertions?_
## Have you only covered the 'happy path' scenarios?
^ If your tests are passing but there is an issue, maybe you haven't written enough assertions
Be sure to check for the negative use cases too
Check that something is not included as well as what should be included
What if you pass in an incorrect value?
---
### _Other modules can affect things_
## Tests may pass, but fail when other modules are enabled
---
[.header: alignment(center)]
## [fit] _Testing may add time now_
## [fit] but save more time in the future
---
[.header: alignment(center)]
## [fit] _How do you get quicker at writing tests?_
# [fit] By writing more tests
^ Practice makes perfect
Become more familar with and knowledge of recurring errors
Find better practices and approaches. Different base classes? Less setup steps. Less time, more productive.
---
## _Start small_
## Some tests are better than no tests
---
[.background-color: #FFFFFF]
![140%](images/tawny-tweet-1.png)
---
[.background-color: #FFFFFF]
![150%](images/tawny-tweet-2.png)
---
[.text: alignment(center)]
> ![inline 150%](images/when-you-do-things-right.jpg)
---
[.header: alignment(center)]
# Questions?
---
[.header: alignment(center)]
# Thanks
### _@opdavies_
### _oliverdavies.uk_