Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting Started With TDD + Travis CI

Liam Norman
September 27, 2018

Getting Started With TDD + Travis CI

A lot of developers are developing applications without using tests and this is a problem that many software companies face.

Tests can improve the reliability of your codebase and also safeguard it against bugs before your code reaches production. We will introduce TDD and how to implement TDD into your project workflow using Travis CI.

We will look at a sample Laravel application and how to implement TDD. The topics that will be covered on the TDD front are feature tests vs unit tests, testing events, working with testing databases, implementing PHPUnit and using Laravel Dusk to implement browser testing.

The Travis CI portion of the talk will focus on how to specifically implement it into your project workflow, we will cover what is CI and what is Travis CI, benefits of using Travis CI, how the build system works, setting up Travis and running builds and announcing build status via notifications.

By the end, you will be ready to leverage TDD and Travis CI in your own applications!

Liam Norman

September 27, 2018
Tweet

More Decks by Liam Norman

Other Decks in Programming

Transcript

  1. Getting Started With Getting Started With TDD + Travis CI

    TDD + Travis CI By Liam Norman | Superbalist.com 1 . 1
  2. Who Am I? Who Am I? Software Engineer at Superbalist.com

    Organiser of PHP Cape Town Blog at www.liamnorman.com On twitter as @liamjnorman 1 . 2
  3. Objectives Objectives What is Testing and TDD? Why is testing

    useful? Write our first tests with PHPUnit in Laravel 5 1 . 3
  4. Objectives Objectives What is Testing and TDD? Why is testing

    useful? Write our first tests with PHPUnit in Laravel 5 Implement Laravel Dusk to implement our first browser tests. 1 . 3
  5. Objectives Objectives What is Testing and TDD? Why is testing

    useful? Write our first tests with PHPUnit in Laravel 5 Implement Laravel Dusk to implement our first browser tests. How to simply use Travis CI 1 . 3
  6. What is Software Testing? What is Software Testing? It's an

    activity to check whether the actual results match the expected results and to ensure that the software system is Defect free. 1 . 4
  7. “People often underestimate the time they spend debugging. They underestimate

    how much time they can spend chasing a long bug. With testing, I know straight away when I have added a bug. That lets me fix the bug immediately. There are few things more frustrating or time wasting than debugging. Wouldn’t it be a hell of a lot quicker if we just didn’t create the bugs in the first place” - Martin Fowler 1 . 5
  8. Why Test? Why Test? It saves time. It increases confidence

    in your codebase. Points out defects and errors that were made during the development phase of a project or task. 1 . 6
  9. Why Test? Why Test? It saves time. It increases confidence

    in your codebase. Points out defects and errors that were made during the development phase of a project or task. Open Source Software 1 . 6
  10. What makes a good test suite? What makes a good

    test suite? Confidence system works 1 . 8
  11. What makes a good test suite? What makes a good

    test suite? Confidence system works Reliable 1 . 8
  12. What makes a good test suite? What makes a good

    test suite? Confidence system works Reliable Fast 1 . 8
  13. What makes a good test suite? What makes a good

    test suite? Confidence system works Reliable Fast Simple 1 . 8
  14. What is TDD? What is TDD? "Test-driven development (TDD) is

    an evolutionary approach to development which combines test-first development where you write a test before you write just enough production code to fulfill that test and refactoring." (Beck 2003; Astels 2003) 1 . 9
  15. Learn testing in one day! Learn testing in one day!

    That title is wrong... Learn to test certain elements 1 . 12
  16. Learn testing in one day! Learn testing in one day!

    That title is wrong... Learn to test certain elements Any tests are better than none, aim for 100% coverage 1 . 12
  17. Arrange - Act - Assert Arrange - Act - Assert

    1. Arrange - Set up the test's initial state 1 . 16
  18. Arrange - Act - Assert Arrange - Act - Assert

    1. Arrange - Set up the test's initial state 2. Act - Perform the action that you wish to test 1 . 16
  19. Arrange - Act - Assert Arrange - Act - Assert

    1. Arrange - Set up the test's initial state 2. Act - Perform the action that you wish to test 3. Assert - Inspect the system or results 1 . 16
  20. Test Databases Test Databases <?php return [ ... 'testing' =>

    [ 'driver' => 'mysql', 'host' => env('DB_TEST_HOST', '127.0.0.1'), 'port' => env('DB_TEST_PORT', '3306'), 'database' => env('DB_TEST_DATABASE', 'forge'), 'username' => env('DB_TEST_USERNAME', 'forge'), 'password' => env('DB_TEST_PASSWORD', ''), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ], 1 . 18
  21. Configuring PHPUnit Configuring PHPUnit <?xml version="1.0" encoding="UTF-8"?> ... <testsuites> <testsuite

    name="Application Test Suite"> <directory suffix="Test.php">./tests</directory> </testsuite> </testsuites> <filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">./app</directory> </whitelist> </filter> <php> <env name="APP_ENV" value="testing"/> <env name="CACHE_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/> <env name="QUEUE_DRIVER" value="sync"/> <env name="DB_CONNECTION" value="testing"/> </php> </phpunit> 1 . 19
  22. Our 1st Unit Test Our 1st Unit Test <?php use

    Tests\TestCase; use App\Podcast; class PodcastTest extends TestCase { /** @test */ function can_create_podcast() { $params = [ 'name' => 'My Test Podcast', 'description' => 'Awesome Test Podcast', 'web_url' => 'https://MyTestPodCast', 'feed_url' => 'http://feeds.com/MyTestPodCast', 'feed_thumbnail_location' => 'images/mytestpodcast.png', ]; Podcast::create($params); $this->assertDatabaseHas('podcasts', $params); } } 1 . 22
  23. Going Green Going Green <?php use Tests\TestCase; use App\Podcast; use

    Illuminate\Foundation\Testing\RefreshDatabase; class PodcastTest extends TestCase { use RefreshDatabase; /** @test */ function can_create_podcast() { ... 1 . 25
  24. PHPUnit (Basic) Assertions PHPUnit (Basic) Assertions <?php $this->assertEquals(expected, actual); //

    e.g. $this->assertEquals($podcast->name, 'my podcast'); $this->assertTrue(condition); // e.g. $this->assertTrue($podcast->active); $this->assertContains(needle, haystack); // e.g. $this->assertContains($podcast->id, $podcasts); 1 . 27
  25. Database Assertions Database Assertions <?php $this->assertDatabaseHas($table, array $data); // e.g.

    $this->assertDatabaseHas('podcasts', ['name' => 'laravel podcast'] ); $this->assertDatabaseMissing($table, array $data); // e.g. $this->assertDatabaseMissing('podcasts', ['name' => 'deleted podcast'] ); $this->assertSoftDeleted($table, array $data); // e.g. $this->assertSoftDeleted('podcasts', ['id' => '1'] ); 1 . 28
  26. JSON API Assertions JSON API Assertions <?php $this->assertStatus($statusCode) // e.g.

    $this->assertStatus(200); $this->assertExactJson($expected); // e.g. $this->assertExactJson([ 'name' => 'My Podcast', 'is_active' => true ]); $this->assertJsonFragment($expected); // e.g. $this->assertJsonFragment([ 'name' => 'My Podcast' ]); 1 . 29
  27. Minimising Details Minimising Details <?php use Tests\TestCase; use App\Podcast; class

    PodcastTest extends TestCase { /** @test */ function can_create_podcast() { $params = [ 'name' => 'My Test Podcast', 'description' => 'Awesome Test Podcast', 'web_url' => 'https://MyTestPodCast', 'feed_url' => 'http://feeds.com/MyTestPodCast', 'feed_thumbnail_location' => 'images/mytestpodcast.png', ]; Podcast::create($params); $this->assertDatabaseHas('podcasts', $params); } } 1 . 30
  28. Model Factories Model Factories Allows us to quickly create dummy

    data. Cleans up our testing implementations. 1 . 31
  29. Model Factories Model Factories Allows us to quickly create dummy

    data. Cleans up our testing implementations. 1 . 31
  30. Model Factories Model Factories <?php use Faker\Generator as Faker; $factory->define(Podcast::class,

    function (Faker $faker) { return [ 'name' => 'My Test Podcast', 'description' => 'Awesome Test Podcast', 'web_url' => 'https://MyTestPodCast', 'feed_url' => 'http://feeds.com/MyTestPodCast', 'feed_thumbnail_location' => 'testpodcast.png', 'active' => true, ]; }); 1 . 32
  31. Refactoring with Model Factories Refactoring with Model Factories <?php /**

    @test */ function can_create_podcast() { $podcast = factory(Podcast::class)->create(); $this->assertDatabaseHas( 'podcasts', $podcast->toArray() ); } 1 . 33
  32. Feature Testing Feature Testing Features are functionality that are added

    or modified to software. Feature testing involves testing that a feature works such as favouriting podcasts. 1 . 34
  33. Unit Tests vs Feature Tests Unit Tests vs Feature Tests

    Unit Tests are written to ensure that a particular method (or a unit) of a class performs a set of specific tasks. 1 . 35
  34. Unit Tests vs Feature Tests Unit Tests vs Feature Tests

    Unit Tests are written to ensure that a particular method (or a unit) of a class performs a set of specific tasks. Feature Tests ensure that the system is functioning as users are expecting it to. 1 . 35
  35. Our First Feature Test Our First Feature Test <?php class

    FavouritePodcastTest extends TestCase { /** @test */ public function can_favourite_podcasts() { $podcast = factory(Podcast::class)->create(); $response = $this->get("/podcasts/{$podcast->id}/favourite"); $this->assertDatabaseHas('podcasts', [ 'id' => $podcast->id, 'is_favourite' => true, ]); $response->assertStatus(200); } } 1 . 37
  36. Analysing Test Failures Analysing Test Failures Since Laravel 5.5, we

    can toggle exception handling within our tests. <?php /** @test */ function cannot_view_missing_podcast() { $invalidId = 991239123912999193192391; $response = $this->get("/podcasts/{$invalidId}"); $response->assertOk(); } 1 . 38
  37. PHPUnit 7.3.5 by Sebastian Bergmann and contributors. .F 2 Time:

    211 ms, Memory: 16.00MB There was 1 failure: 1) PodcastTest::cannot_view_missing_podcast() Response status code [404] does not match expected 200 status code. Failed asserting that false is true. ... 1 . 39
  38. <?php /** @test */ function cannot_view_missing_podcast() { $this->withoutExceptionHandling(); $invalidId =

    991239123912999193192391; $response = $this->get("/podcasts/{$invalidId}"); $response->assertOk(); } Analysing Test Failures Analysing Test Failures 1 . 40
  39. PHPUnit 7.3.5 by Sebastian Bergmann and contributors. .E Time: 191

    ms, Memory: 16.00MB There was 1 error: 1) PodcastTest::cannot_view_missing_podcast() Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Podcast] 9.91239123913E+23 ... 1 . 41
  40. <?php /** @test */ function cannot_view_missing_podcast() { $this->withoutExceptionHandling(); $invalidId =

    991239123912999193192391; $response = $this->get("/podcasts/{$invalidId}") $response->assertStatus(404); } 1 . 42
  41. Laravel Dusk Laravel Dusk Easy Installation No JDK or Selenium

    installs Standalone ChromeDriver 1 . 43
  42. Laravel Dusk Laravel Dusk Easy Installation No JDK or Selenium

    installs Standalone ChromeDriver Easy Browser Testing 1 . 43
  43. Our First Browser Test Our First Browser Test <?php class

    PodcastBrowserTest extends DuskTestCase { use RefreshDatabases; /** @test */ public function can_view_podcasts() { // create 2 podcasts $podcasts = factory(Podcast::class, 2)->create(); foreach ($podcasts as $podcast) { // visit podcasts route and ensure podcast name is visible $this->browse(function (Browser $browser) use ($podcast) { $browser->visit("/podcasts/") ->assertSee($podcast->name); }); } } } 1 . 46
  44. Browser Tests In Detail Browser Tests In Detail Simulates User

    App works end-to-end Slower Need to make assertions through UI 1 . 47
  45. Mocking in Laravel Mocking in Laravel Laravel comes with helpers

    to mock events, storage, jobs You can use Mockery, PHPUnit etc if you wish to become more in-depth with your mocking. 1 . 48
  46. Our Event Our Event <?php namespace App\Events; class PodcastAdded {

    use Dispatchable, InteractsWithSockets, SerializesModels; /** * @var Podcast $podcast */ public $podcast; public function __construct(Podcast $podcast) { $this->podcast = $podcast; // upload logic in listener } } 1 . 49
  47. Testing Events Testing Events <?php /** @test */ function added_event_is_fired_when_podcast_is_added()

    { // mock event Event::fake([PodcastAdded::class]); $podcast = factory(Podcast::class)->make(); $this->json('POST', '/podcasts/store', $podcast->toArray()); Event::assertDispatched(PodcastAdded::class, function ($event) { $podcast = Podcast::firstOrFail(); return $event->podcast->is($podcast); }); } 1 . 50
  48. What is CI? What is CI? “A software development practice

    where members of a team integrate their work frequently … verified by an automated build (including tests) to detect integration errors” - Martin Fowler 1 . 52
  49. What is CI? What is CI? “A software development practice

    where members of a team integrate their work frequently … verified by an automated build (including tests) to detect integration errors” - Martin Fowler Combine the process with automated testing using PHPUnit, Dusk etc then continuous integration can enable your code to be dependable. 1 . 52
  50. What is Travis CI? What is Travis CI? A hosted

    continuous integration platform Open Source .travis.yml details how Travis interacts with project Features an advanced build system 1 . 53
  51. Why Use Travis? Why Use Travis? Nothing to install, Travis

    has a web-based interface. Free to use for open-source projects! 1 . 54
  52. Why Use Travis? Why Use Travis? Nothing to install, Travis

    has a web-based interface. Free to use for open-source projects! Integrates nicely with GitHub without any developer effort 1 . 54
  53. Setup Travis with Laravel & Setup Travis with Laravel &

    PHPUnit PHPUnit Add a .travis.yml file to your application directory 1 . 57
  54. Setup Travis with Laravel & Setup Travis with Laravel &

    PHPUnit PHPUnit Add a .travis.yml file to your application directory Configure .travis.yml 1 . 57
  55. Setup Travis with Laravel & Setup Travis with Laravel &

    PHPUnit PHPUnit Add a .travis.yml file to your application directory Configure .travis.yml We will use the same phpunit and database setup that we did earlier. 1 . 57
  56. Setup Travis with Laravel & Setup Travis with Laravel &

    PHPUnit PHPUnit Add a .travis.yml file to your application directory Configure .travis.yml We will use the same phpunit and database setup that we did earlier. Add various PHP versions to build against (if necessary) 1 . 57
  57. Setup Travis with Laravel & Setup Travis with Laravel &

    PHPUnit PHPUnit Add a .travis.yml file to your application directory Configure .travis.yml We will use the same phpunit and database setup that we did earlier. Add various PHP versions to build against (if necessary) Add services if needed (Redis, Memcached etc…) 1 . 57
  58. .travis.yml .travis.yml language: php php: - 7.1 - 7.2 services:

    - mysql before_script: - cp .env.travis .env - mysql -e 'create database larapod_app_testing;' - composer self-update - composer install --no-interaction - php artisan key:generate - php artisan migrate --seed - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - ./vendor/laravel/dusk/bin/chromedriver-linux & - php artisan serve & script: - vendor/bin/phpunit - php artisan dusk notifications: email: recipients: - [email protected] on_success: always on_failure: always 1 . 59
  59. Testing Tips Testing Tips Follow the TDD Mantra (red-green-refactor) Test

    For Failure Keep Tests Small Descriptive Test Method Signatures 1 . 63
  60. Testing Tips Testing Tips Follow the TDD Mantra (red-green-refactor) Test

    For Failure Keep Tests Small Descriptive Test Method Signatures Practice 1 . 63