layout: true class: title name: title ??? --- layout: true class: content name: content --- layout: true class: content, level-1 name: level-1 --- layout: true class: content, level-2 name: level-2 --- layout: true class: content, level-3 name: level-3 --- layout: true class: image name: image --- layout: true class: bg-cover, image name: full-screen --- layout: true class: double-wide name: double-wide --- layout: true class: double-stack name: double-stack --- layout: true class: code name: code --- layout: true class: title, conversation name: conversation --- layout: true class: title, inverse name: about-title --- layout: true class: title, module, inverse name: module-title --- layout: true template: level-1 name: module --- layout: true template: level-2 name: module-section --- layout: true name: cover-art template: title background-image: linear-gradient(to bottom,rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%), url(images/title.jpg) class: bg-cover, inverse, cover-art, no-footer ??? --- layout: false--- template: cover-art # Building Quality JavaScript With Test-Driven Development .about.left[ ## Steven Hicks ###
[@pepopowitz](https://twitter.com/pepopowitz) ###
steven.j.hicks@gmail.com ###
[stevenhicks.me/tdd](https://stevenhicks.me/tdd) ] .while-waiting[ ## While we're waiting: ### [stevenhicks.me/tdd-setup](https://stevenhicks.me/tdd-setup) ### WIFI pw: ???? ] ??? **Contact Info** **Thanks:** * conference * organizers * ~~sponsors~~ * you! timekillers: * stickers! * where from? * favorite talks * favorite speakers * stand up & stretch * high fives --- layout: false template: about-title # About Me ??? intro & setup: 0:00 - 0:15 (or 0:20 if not a lot people did setup) (1:00 - 1:15 or 1:20) --- layout: false template: full-screen background-image: url(images/star-student.jpg) class: no-footer --- layout: false background-color: black background-image: url(images/artsy.svg) class: inverse, bg-center, bg-40 --- layout: false template: about-title # About This Workshop --- layout: true template: level-1 name: about # About This Workshop --- class: collaborative ## Collaborative -- - You + me -- - You + your neighbor ??? Teaching is one of the best ways to learn & We're going to pair! --- ## Framework-agnostic --- class: schedule ## Agenda - .highlight[Module 0:] Intro & Setup - .highlight[Module 1:] Jest - .highlight[Module 2:] Intro to TDD - .highlight[Module 3:] Code Katas - .highlight[Module 4:] Real-life TDD - .highlight[Module 5:] Solving Problems Together - .highlight[Wrapping Up:] Good Practices ??? Now is your chance to break! 1:20 if people didn't do the setup --- template: module-title # Feedback ## Green = Good; Yellow = Okay; Pink = Bad ??? explain the system red, yellow, green cards put in bag leave comments if you'd like --- layout: false template: about-title # About You ??? JS devs? unit testing experience? solid tdd experience? have tried tdd but it hasn't stuck --- template: level-1 # About You ## Meet your neighbors! -- - Name - Where you're from - What you do - What you're excited about --- template: module-title # Module 0 ## Setup --- template: module layout: true # 0: Setup --- ## [https://stevenhicks.me/tdd-setup](https://stevenhicks.me/tdd-setup) ??? intro & setup: 0:00 - 0:15 (or 0:20 if not a lot people did setup) **8:00 - 8:15/8:20** If you cloned before this morning - pull latest! If you've not gotten things set up yet, I'm going to ask that you do this on your own time. Go to this url, and follow the instructions to get set up. *If lots didn't do it:* We'll take 5 minutes to do this now. I'll walk around and help where I can, but please don't hesitate to ask your neighbors if you're having problems. TIMEBOXED TO 5 MINUTES. --- template: module-title # Module 1 ## Jest ??? instruction: 0:15 - 0:30 **8:15 - 8:30** --- layout: true template: module name: module-1 # 1: Jest --- ## Delightful JavaScript Testing ### [facebook.github.io/jest](http://facebook.github.io/jest) ??? self-proclaimed Jasmine/mocha/chai? --- ## Why? --- layout: true template: module-section name: module-1-section # 1: Jest --- layout: true template: module-1-section name: why ## Why? --- ### Easy to set up ??? Generally only a couple of steps All are really well documented --- ### Zero configuration ??? If you **follow the conventions** for naming your test files, Jest will just find them. --- ### Fast ??? Especially given a large test suite. Slower by comparison for a small test suite. (Parallelization) Others scale linearly; Jest scales logarithmically. --- ### Helpful error messages ??? This is one of my favorite things happening in dev right now Error messages that tell you **what you did wrong** instead of just **you did something wrong** --- layout: true --- class: bg-contain background-image: url(images/jest-error-1.jpg) --- class: bg-contain background-image: url(images/jest-error-2.jpg) --- class: bg-contain background-image: url(images/jest-error-3.jpg) --- layout: true template: why --- ### Interactive watch mode ??? By default, runs **only tests affected by changes** since your most recent commit. (Great for TDD) Other options: 1. run all tests 2. filter tests by name or file path --- layout: false template: module-1 ## Writing tests --- layout: true template: module-1-section ## Writing tests --- ### Where to put them? ??? I mentioned that Jest has conventions for naming/locating your tests These are your options -- - \_\_tests\_\_ folder -- - \*.spec.js -- - \*.test.js ??? In all cases, the best place to put those test files is right along side of the code being tested. --- layout: false template: module-1 ## Jest API --- layout: true template: module-1-section ## Jest API --- ### describe() ??? describe is how you define a **set of tests** -- .dim-2[ ```javascript describe("first-name-translator", () => { // tests go in here }); ``` ] ??? ## It's kind of like **namespacing** your tests ### describe() .dim-3[ ```javascript describe("first-name-translator", () => { describe("invalid requests", () => { // ....invalid request tests.... }); }); ``` ] ??? can be nested --- ### it() or test() ??? use it or test to define an individual test. -- .dim-1.dim-3.dim-5[ ```javascript describe("first-name-translator", () => { it("translates my name", () => { // test goes here }); }); ``` ] ??? give it a name and a function to execute. --- ### expect() ??? assertions -- .dim-1.dim-2.dim-3.dim-6.dim-7[ ```javascript describe("first-name-translator", () => { it("translates my name", () => { const result = translateFirstName("Steve"); expect(result).toEqual("Sassy"); }); }); ``` ] --- ### expect() ```javascript expect(a).toEqual(b); expect(a).not.toEqual(b); expect(() => a()).toThrowError(b); ``` ??? they aren't much different than other JS test frameworks and if you're new to JS testing, they read very fluently. --- ### Putting it all together ```javascript describe("first-name-translator", () => { it("translates my name", () => { const result = translateFirstName("Steve"); expect(result).toEqual("Sassy"); }); }); ``` --- layout: false template: module-1 ## Running tests --- layout: true template: module-1-section ## Running tests -- .centered-image[ ![Interactive watch mode](images/interactive-watch-mode.png) ] --- template: module-title # Exercise: Module 1 ## Jest ### `module-1/README.md` ??? get comfortable with Jest JS Unicorn Name Generator ! show them how to preview markdown in vscode! exercise: 0:30 - 0:50 **8:30 - 8:50** --- template: module-title # Recap: Module 1 ## Jest ??? summary: 0:50 - 0:55 **8:50 - 8:55** --- layout: true template: module-1-section ## Recap --- ### How did it go? * What went well? * What didn't? * What stuck with you? ??? REPEAT THEIR QUESTIONS/ANSWERS --- ### Suggestions --- layout: true template: level-3 name: module-1-suggestions # 1: Jest ## Recap ### Suggestions --- #### Mostly run “changes only" (“o") --- #### Run all tests before commit (“a") --- #### Commit small units of work ??? frequently! (even if it you aren't pushing) --- #### Learn more about Jest ##### http://facebook.github.io/jest --- template: module-title # Module 2 ## Intro To Test-Driven Development ??? instruction: 0:55 - 1:15 **8:55 - 9:15** --- layout: true template: module # 2: Test-Driven Development (TDD) --- > A discipline in which any system change requires a failing test to be written first ??? also called Test-Driven Design emphasis on design it helps you design the system --- layout: true template: module name: module-2 # 2: TDD --- ## TDD tightens your feedback loop. ??? - **definition**: self-correction in a system to maintain a desired state - **simple example**: thermostat - means to measure (thermometer) - means to adjust (furnace) - **loose**: make changes, wait til prod release to find out if it worked - **tight**: make changes, see within seconds if you broke something Q: what else tightens development feedback loop? A: CI/CD --- class: all-changes-require ## All Changes Require: -- 1. Write a failing unit test -- 2. Write just enough code to pass the test -- 3. Refactor the code just enough ??? to meet the desired level of quality Hardest part: writing a failing test first. --- ## Red/Green/Refactor ??? aka r/g/r Red & Green are pretty obvious - failing test, passing test But refactoring needs some explanation. --- layout: true template: module-section name: module-2-section # 2: TDD --- layout: true template: module-2-section name: module-2-refactoring ## Refactoring --- > Refactoring is the process of restructuring code without affecting the external behavior --- ### Goals: * Improve readability and maintainability * Remove “code smells” * Remove duplication ??? --- ### Refactoring works best when you have unit tests covering the code you are refactoring ??? How else will you know if you alter the external behavior? Imagine you have a complex system with good unit test coverage You make changes and break something, you get errors! Without unit tests, you'd have to manually test, and you'd assuredly miss something. --- template: module-2 ## Why TDD? --- layout: true template: module-2-section ## Benefits --- ### TDD may lead to... * Simpler designs * Smaller units of code * Looser coupling * Cleaner interfaces * Improved code coverage ??? emphasis on _may_ --- ### TDD may also... * Question your understanding of the requirements * Document the requirements * Guide code reviews * Give you code confidence * Reduce mental clutter ??? Also some effects beyond the code --- template: module-2 ## How Do I TDD? --- layout: true template: module-2-section ## The Process --- ### .highlight[Step 0:] Accept that TDD is hard ??? It will probably not feel natural at first For me, it took two years of failed attempts before it felt natural --- ### .highlight[Step 1:] Identify the test ??? You can identify tests one at a time, as you write them I usually prefer to brainstorm all my tests for a class before I start I write them in my test file as comments This is a great time to decide what you do and don’t know about the requirements And discuss this with your team! Also a great time to define the API to the thing you're building --- ### .highlight[Step 2:] Write the failing test ??? But you're not just writing the test!!!! You're also writing the interface to what you're testing!!!! **THIS** is what makes it test-driven DESIGN This is the time to decide what the interface to your function will look like --- ### .highlight[Step 3:] Write as little code as possible to make the test pass ??? The easiest way to do this is to “fake it” i.e. hardcode the expected result It doesn’t matter that this code is clearly wrong. The important thing is that you now have a passing test, and you can refactor the code under the safety net of that test, until the code is right. --- ### .highlight[Step 4:] Refactor the code until you're content ??? That means making small changes, while keeping your tests passing --- template: module-2 ## Tips --- layout: true template: module-2-section ## Tips --- ### Stay “green” while refactoring ??? If you leave the “green” state for a long time, it can be tough to get back --- ### Work in small iterations ??? You are looking for a tight feedback loop --- ### Unexpected passing tests are ### bad luck ??? * Find out why they're passing. * Make them fail to prove they're working. --- template: module-title # Exercise: Module 2 ## Intro To Test-Driven Development ### `module-2/README.md` ??? exercise: 1:15 - 1:25 **9:15 - 9:25** FizzBuzz Demo - instructor-led (next slide for preview) --- template: module-2-section ## FizzBuzz ``` > FizzBuzz(15) 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz ``` ??? --- template: module-title # Recap: Module 2 ## Intro To Test-Driven Development ??? summary: 1:25 - 1:30 **9:25 - 9:30** --- layout: true template: module-2-section ## Recap --- ### How did it go? * What made sense? * What didn't? * What stuck with you? --- ### Suggestions --- layout: true template: level-3 name: module-2-suggestions # 2: TDD ## Recap ### Suggestions --- #### Stay Green ??? Open-water swimming & sighting --- #### Handle validation first ??? easy wins --- #### Design your interfaces ??? Make it simple Make it intuitive Make it readable --- #### Triangulate ??? Add a second similar test Make both tests pass at the same time --- #### Tests aren't permanent ??? Use them to navigate to the solution Delete them when they no longer serve you You can write tests that aren't production worthy. --- #### Refactor ??? Sometimes it's tempting to move on as soon as you make it pass It's the easiest step to forget Also - refactoring means refactoring TEST CODE, too - not just tested code. --- template: module-title # Module 3 ## Code Katas ??? Should have taken 1st break by now! instruction: 1:40 - 1:45 **9:40 - 9:45** --- layout: true template: module name: module-3 # 3: Code Katas --- layout: true template: module-section name: module-3-section # 3: Code Katas --- template: module-3 layout: true --- > A code kata is an exercise in programming which helps programmers hone their skills through practice and repetition. --- ## Usually not a real-world problem ??? A canned problem, a specific scenario with specific rules --- ## Katas are about practice ??? Not the problem itself. --- ## Build a testing habit ??? The more you do it, the more it becomes your default response to a problem Under pressure, habits remain I'm pretty good about eating a salad with my lunch when things are going smoothly But when things get busy & hectic, I fall back to my habits - fast food, soda, no salad. --- template: module-title # Exercise: Module 3 ## Code Katas ### `module-3/README.md` ??? String calculator kata 1:45-2:10 **9:45 - 10:10** Rules: - No code without tests - Don't expect to finish! There's a lot here. --- template: module-title # Recap: Module 3 ## Code Katas ??? 2:10-2:15 **10:10 - 10:15** --- layout: true template: module-3-section ## Recap --- ### How did it go? - What went well? - What didn't? - What stuck with you? --- ### Suggestions --- layout: true template: level-3 name: module-3-suggestions # 3: Code Katas ## Recap ### Suggestions --- #### Write small methods ??? Break things into small pieces It's easier to understand & manage your code --- #### Don't look too far ahead ??? Leads to overarchitecting Building things you don't need --- #### Use katas to build habits ??? You can practice them on your own some people like to start their day with a kata if you've read about habits: - power of habit - charles duhigg - atomic habits - james clear - any modern non-fiction on habits you're familiar with the habit loop cue/routine/reward **cue**: logic-intensive problem **routine**: tdd **reward**: code I'm proud of, well-tested, small methods, ... --- #### [codekata.com](http://codekata.com/) ??? more katas --- template: module-title # Module 4 ## Real-life TDD ??? instruction: 2:15 - 2:25 **10:15 - 10:25** --- layout: true template: module name: module-4 # 4: Real-life TDD --- layout: true template: module-section name: module-4-section # 4: Real-life TDD --- template: module-4 ## Tests Aren't Always Easy ??? It's nice to write these simple tests that we've been writing but sometimes life & code aren't so pleasant --- template: module-4 ## Mocking --- template: module-4-section ## Mocking ### Replacing **actual** dependencies with **fake** ones ??? --- layout: true template: module-4-section name: module-4-mocking ## Mocking --- ### When? ??? When would we do this? --- layout: true template: level-3 name: module-4-mocking-when # 4: Real-life TDD ## Mocking ### When? --- #### The actual dependency has side-effects ??? API endpoint that deletes a user --- #### The actual dependency is not deterministic. ??? A deterministic function returns the same results given the same input, every single time. Non-deterministic: (e.g. calls against a **database**, looking for specific records). Intrinsically non-deterministic: (e.g. a function that gets the **current time**). It is much easier to write tests against these dependencies when they are mocked - we can easily control the values they are returning, and verify our system is handling all the possible values properly. --- #### The actual dependency is expensive to test. ??? DOM manipulation (our app) Isolate the things that are difficult to test, TDD all of the things that are easier to test, separately. --- #### You aren't sure what the dependency does yet. ??? When you have a large problem that you need to break into smaller problems. Mock the "difficult" stuff, TDD the orchestration of those dependencies, Then apply TDD to the "difficult" functions in isolation. I call this strategy **"procrastination."** --- layout: false template: module-4-mocking ### Jest mocks ??? Several pieces involved in mocking with Jest --- layout: true template: level-3 name: module-4-mocking-jest # 4: Real-life TDD ## Mocking ### Jest --- #### jest.fn() ??? jest.fn helper returns a new mock function that we can substitute for a real function -- ```javascript const e = { preventDefault = jest.fn(); } // act... expect(e.preventDefault).toHaveBeenCalledTimes(1) ``` --- #### jest.mock() ??? another helper - jest.mock - replaces an imported dependency with a fully/automatically mocked one -- ```javascript jest.mock("axios"); ``` --- #### beforeEach() & afterEach() ??? Global functions we can use to execute code before or after each test runs -- ```javascript jest.mock("axios"); beforeEach(() => { axios.get.mockReset(); }); ``` --- #### Faking Implementation -- ```javascript jest.mock("axios"); axios.get.mockResolvedValue({ data: [ { name: "first item", hills: "Easy" } ] }); ``` --- #### Assertions -- ```javascript jest.mock("axios"); it("calls axios", () => { // act... expect(axios.get).toHaveBeenCalledTimes(1); expect(axios.get).toHaveBeenLastCalledWith("/api/trails?hills=Easy"); }); ``` --- 1. Import the dependency 2. Mock the dependency 3. Reset the dependency before each test 4. Provide a fake implementation 5. Assert against the fake dependency ??? Within a test file... 1 - as you normally would for calling the dependency 2 - using jest.mock() 3 - in your beforeEach 4 - if you need to 5 - optional --- class: medium ```javascript import axios from "axios"; jest.mock("axios"); describe("search-form/call-api", () => { beforeEach(() => { axios.get.mockReset(); }); it("calls the api", async () => { axios.get.mockResolvedValue(aFakeResponse()); const form = aFakeForm(); await callApi(form); expect(axios.get).toHaveBeenCalledTimes(1); expect(axios.get).toHaveBeenLastCalledWith("/api/trails?hills=Easy"); }); }); ``` --- layout: false template: module-4 ## Brownfield Development ??? TDD seems easy to introduce to a new codebase But there are also some great places to introduce TDD in an existing codebase --- layout: true template: module-4-section name: module-4-brownfield ## Brownfield --- ### Business Logic ??? One great place to introduce TDD into your app is business logic. Business logic is generally not related to UI, so it's easier to test. Also easily isolated to small functions, which makes it easier to write, read, and maintain tests. --- template: level-3 # 4: Real-life TDD ## Brownfield ### Business Logic ```javascript get: function(req, response) { let results = trailData; if (req.query){ results = maybeFilterBySport(results, req.query.sport); results = maybeFilterByHills(results, req.query.hills); } response.json(results); }, ``` ??? You can imagine a function like this Which does some filtering of data, based on a request Would be a pretty great place to test-drive your code --- layout: false template: module-4-brownfield ### Bug Fixes ??? Another great place to introduce TDD into a project is by fixing bugs. When you get a bug, you write a test proving that the bug exists - and therefore a failing test And then make the test pass, to fix the bug --- template: level-3 # 4: Real-life TDD ## Brownfield ### Bug Fixes ```javascript it("translates Zeke's name", () => { const result = translateFirstName("Zeke"); expect(result).toEqual("Dashing"); }); ``` ??? In the exercise you're going to work through, you're going to get a handful of bug reports Like this one These are tailor-made for TDD --- template: module-title # Exercise: Module 4 ## Real-life TDD ### `module-4ab/README.md` & `module-4c/README.md` ??? 2:25 - 2:50 **10:25 - 10:50** 10 minute break before, during, or after --- template: module-title # Recap: Module 4 ## Real-life TDD ??? 3:00 - 3:05 **11:00 - 11:05** Should have taken break 2 by now! --- layout: true template: module-4-section ## Recap --- ### How did it go? - What went well? - What didn't? - What stuck with you? --- ### Suggestions --- layout: true template: level-3 name: module-4-suggestions # 4: Real-life TDD ## Recap ### Suggestions --- #### Introduce TDD in small doses ??? Bug fixes A function that calculates something A simple, small, defined set of business rules --- #### Procrastinate Complexity ??? It's okay to not know how your code is going to look keep chipping away the easy things, and testing those --- #### Be Pragmatic ??? Some things are just too impure/nondeterministic for TDD Identify the impure, unpredictable, & things out of your control i.e. current date/time, api's Extract & mock them Test your thing in isolation --- template: module-title # Module 5 ## Solving Problems Together ??? instruction: 3:05 - 3:10 **11:05 - 11:10** --- layout: true template: module name: module-5 # 5: Solving Problems Together --- layout: true template: module-section name: module-5-section # 5: Solving Problems Together --- template: module-5 layout: true --- ## Conway's Game Of Life --- template: full-screen background-image: url(images/game-of-life.gif) animated-background: images/game-of-life.gif class: no-footer name: game-of-life ??? Cells die, stay alive, or revive based on the states of their neighbors 4 rules They seem simple...but they are challenging. You're going to build the logic that makes the cells follow the 4 simple rules. --- ## Pair Up! ??? Choose a laptop --- ## Workflow 1. Person 1: Write a failing test 2. Person 2: Make the test pass 3. Person 1&2: Refactor together 4. Person 2: Write a failing test 5. Person 1: Make the test pass 6. Person 1&2: Refactor together 7. Repeat --- ## Rules - No code without tests - Collaborate to define the test cases - If you're typing, you write the code - Keep all methods below 5 lines - Do one task at a time ??? 3 - You may ask for the other’s opinion 3 - But the other may not force you to write the code a certain way 5 - Try not to read ahead --- ## Things To Note --- layout: true template: module-5-section ## Notes --- ### This problem is hard ??? I don't expect you to finish --- ### Each cell has 8 neighbors .centered-image[ ![Each cell has 8 neighbors](images/all-8-neighbors.png) ] ??? Not 4 Diagonals are included! --- ### Start with specs that won't ship ??? Remember that tests don't have to be permanent Use them to guide you on the right path, and delete them when they no longer serve you -- * The next value of a cell depends on its current value -- * The next value of a cell depends on the cell directly to the left --- ### The Blinker .centered-image[ ![Blinker pattern](images/blinker.gif) ] ??? Once you support "the blinker" you're probably done oscillator --- ### Refactor to make space ??? Sometimes you can see a change coming up And you can identify that your system can make room for it In these situations, it's helpful to refactor BEFORE writing your failing test --- ### You're amazing and I believe in you 💪🏼 --- template: module-title # Exercise: Module 5 ## Solving Problems Together ### `module-5/README.md` ??? 3:10 - 3:45 **11:10 - 11:45** --- template: module-title # Recap: Module 5 ## Solving Problems Together ??? 3:45 - 3:50 **11:45 - 11:50** --- layout: true template: module-5-section ## Recap --- ### How did it go? - What went well? - What didn't? - What stuck with you? --- ### Suggestions --- layout: true template: level-3 name: module-5-suggestions # 5: Solving Problems... ## Recap ### Suggestions --- #### Test the interface, not the implementation ??? Only test things that this thing's "client" would care about. i.e. not "it puts this value in state" but instead "when I do this thing to it, this other thing shows up." --- #### Tests are not forever ??? They don't have to be permanent You can write tests that you know aren't going to ship And then delete them as soon as they become wrong --- #### Refactor to make space .centered-image[ ![Kent Beck Tweet](images/kent-beck.png) ] ??? --- template: module-title # Wrapping Up ## Good Practices ??? 3:50 - 4:00 **11:50 - 12:00** Let's wrap up with a few good practices for keeping your code friendly to TDD --- layout: true template: module name: good-practices # Good Practices --- ## Treat test code like tested code ??? Focus on maintainability & readability Name appropriately with intention & clarity --- ## Tests are not forever ### Delete them when they no longer serve you ??? They aren't eternal Use them to drive you to a design/solution Delete them or modify them when they aren't serving you anymore --- ## Make things small ??? tests! test files! tested code! --- ## Optimize tests for time of failure, not time of writing ??? Assert in ways that the error will be helpful Use clear test describes & names Keep things organized! Keep things small! You have context when writing. You don't when the test fails. --- template: module-title # Feedback ## Green = Good; Yellow = Okay; Pink = Bad --- layout: false template: cover-art # THANK YOU! .about.left[ ## Steven Hicks ###
[@pepopowitz](https://twitter.com/pepopowitz) ###
steven.j.hicks@gmail.com ###
[stevenhicks.me/tdd](https://stevenhicks.me/tdd) ] ??? I want to thank you for your time I really appreciate it. Survey Questions after Thank you! --- template: level-1 class: double-wide, resources # Resources - [Workshop materials](https://stevenhicks.me/tdd) - [Jest](https://facebook.github.io/jest/) - [Code katas](https://codekatas.com) --- template: level-1 class: double-wide, resources # Images - [Ryan Lange](https://unsplash.com/photos/mAcDHok1t8k) - Title