Slide 1

Slide 1 text

WRITING BEAUTIFUL JAVASCRIPT
 TESTS @KIMJOAR

Slide 2

Slide 2 text

#exampletime

Slide 3

Slide 3 text

I get to work early monday morning, pull the changes from friday, run the tests

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

WHO PUSHED A FAILING TEST!?

Slide 6

Slide 6 text

, however, is green

Slide 7

Slide 7 text

, however, is green WTF?

Slide 8

Slide 8 text

describe('cart', function() { beforeEach(function() { // called before every test // (often called setup) }); ! it('calculates correct total price', function() { var cart = new Cart(); ! cart.addItem({ name: 'Sleeping bag', price: 299, quantity: 2 }); cart.addItem({ name: 'Tent', price: 499, quantity: 1 }); ! expect(cart.totalPrice()).toEqual(1097); }); ! afterEach(function() { // called after every test // (often called teardown) }); }); EXAMPLE OF WHAT A TEST MIGHT LOOK LIKE

Slide 9

Slide 9 text

describe('cart', function() { var cart; ! beforeEach(function() { cart = new Cart(); }); ! describe('with two orders', function() { beforeEach(function() { cart.addItem({ name: 'Sleeping bag', price: 299, quantity: 2 }); cart.addItem({ name: 'Tent', price: 499, quantity: 1 }); }); ! it('calculates correct total price', function() { expect(cart.totalPrice()).toEqual(1097); }); }); }); EXAMPLE OF WHAT A TEST MIGHT LOOK LIKE

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

it('validates', function() { expect(order.isValid()).toBe(true); }); WHY DID IT FAIL?

Slide 15

Slide 15 text

beforeEach(function() { var ci = new Product({ type: 'CI', price: 105 }); cart.addItem(ci); }); WHY DID IT FAIL? DOES THIS CHANGE THE ORDER? CHILD INSURANCE

Slide 16

Slide 16 text

beforeEach(function() { beneficiary1.amount = 1500000; beneficiary2.amount = 500000; ! var li = new Product({ type: 'LI', price: 399, amount: beneficiary1.amount + beneficiary2.amount }); ! cart.addItem(li); order.setPolicyHolder(policyHolder2); order.addBeneficiary(beneficiary1); order.addBeneficiary(beneficiary2); }); WHY DID IT FAIL?

Slide 17

Slide 17 text

beforeEach(function() { policyHolder1 = new PolicyHolder({ name: "Ola Nordmann", ssn: 07099451429, telephoneNumber: "+4795732501", email: "[email protected]", address: { street: "Toftes gate 17", postCode: 0556, postalArea: "Oslo" } }); policyHolder2 = new PolicyHolder({ name: "Testy Testeson", ssn: 17099926629, telephoneNumber: "+4743032501", email: "[email protected]", address: { street: "Ravnkollbakken 3", postCode: 0970, postalArea: "Oslo" } }); ! beneficiary1 = { name: "Testy2", ssn: "04037335466" } beneficiary2 = { name: "Testy3", ssn: "18049938744" } beneficiary3 = { name: "Testy4", ssn: "21050682312" } ! cart = new Cart(); ! order = new Order({ cart: cart, policyHolder: policyHolder1, withdrawalDay: 15 }); }); I HAVE NO IDEA

Slide 18

Slide 18 text

beforeEach(function() { // ... ! policyHolder2 = new PolicyHolder({ name: "Testy Testeson", ssn: 17099926629, telephoneNumber: "+4743032501", email: "[email protected]", address: { street: "Ravnkollbakken 3", postCode: 0970, postalArea: "Oslo" } }); ! // ... }); THE PROBLEM? He got too old to have child insurance

Slide 19

Slide 19 text

You will, at some point, HATE your tests

Slide 20

Slide 20 text

We need to learn more about writing tests

Slide 21

Slide 21 text

WHY DO WE TEST?

Slide 22

Slide 22 text

because testing in production sucks

Slide 23

Slide 23 text

IT DEPENDS But seriously,

Slide 24

Slide 24 text

I'M A CONSULTANT AT BEKK IN OSLO, NORWAY HI! I'M @KIMJOAR I WORK IN 5-10 PERSON TEAMS FOR 3 MONTHS TO SEVERAL YEARS FOR LARGE COMPANIES ON INTERACTIVE AND COMPLEX APPS

Slide 25

Slide 25 text

TODAY: PART PHILOSOPHY PART TECHNIQUES SHARING EXPERIENCES BOTH GOOD AND BAD

Slide 26

Slide 26 text

WHY DO I TEST?

Slide 27

Slide 27 text

REGRESSIONS COMMUNICATION DESIGN

Slide 28

Slide 28 text

REGRESSIONS

Slide 29

Slide 29 text

we're building more complex and interactive apps

Slide 30

Slide 30 text

often in larger teams

Slide 31

Slide 31 text

less afraid of changing code

Slide 32

Slide 32 text

less afraid of changing other people's code

Slide 33

Slide 33 text

because debugging in production sucks

Slide 34

Slide 34 text

fix a bug once

Slide 35

Slide 35 text

COMMUNICATION

Slide 36

Slide 36 text

why did we do it like this?

Slide 37

Slide 37 text

what should it do?

Slide 38

Slide 38 text

DESIGN

Slide 39

Slide 39 text

immediate feedback

Slide 40

Slide 40 text

keep the code simple

Slide 41

Slide 41 text

REGRESSIONS COMMUNICATION DESIGN

Slide 42

Slide 42 text

so … what's the problem?

Slide 43

Slide 43 text

it's easy to write really bad tests

Slide 44

Slide 44 text

HOW I WRITE MY TESTS TRY TO

Slide 45

Slide 45 text

FOCUS ON THE READER

Slide 46

Slide 46 text

TIME SPENT READING CODE > TIME SPENT WRITING CODE

Slide 47

Slide 47 text

IN A TEAM YOU SPEND A LOT OF TIME READING OTHERS' CODE

Slide 48

Slide 48 text

NO SETUP

Slide 49

Slide 49 text

DRY ISN'T ALWAYS THE ANSWER

Slide 50

Slide 50 text

DRYING UP LINES vs DRYING UP CONCEPTS

Slide 51

Slide 51 text

"create tiny universes with minimal conceptual overhead" –Jay Fields

Slide 52

Slide 52 text

CREATION HELPERS

Slide 53

Slide 53 text

it('is valid when policy holder is young enough', function() { var order = createOrder({ cart: createCart([{ type: 'CI' }]), policyHolder: createPolicyHolder({ age: 14 }) }); ! expect(order.isValid()).toBe(true); });

Slide 54

Slide 54 text

it('is not valid when policy holder is too old', function() { var order = createOrder({ cart: createCart([{ type: 'CI' }]), policyHolder: createPolicyHolder({ age: 15 }) }); ! expect(order.isValid()).toBe(false); });

Slide 55

Slide 55 text

WITH GOOD DEFAULTS CREATION HELPERS

Slide 56

Slide 56 text

REVEAL INTENT

Slide 57

Slide 57 text

it('validates', function() { expect(order.isValid()).toBe(true); }); WHAT ARE WE TESTING HERE?

Slide 58

Slide 58 text

FOCUS ON WHY, NOT HOW REVEAL INTENT

Slide 59

Slide 59 text

DETERMINISTIC

Slide 60

Slide 60 text

beforeEach(function() { policyHolder1 = new PolicyHolder({ name: "Ola Nordmann", ssn: 07099451429, telephoneNumber: "+4795732501", email: "[email protected]", address: { street: "Toftes gate 17", postCode: 0556, postalArea: "Oslo" } }); policyHolder2 = new PolicyHolder({ name: "Testy Testeson", ssn: 17099926629, telephoneNumber: "+4743032501", email: "[email protected]", address: { street: "Ravnkollbakken 3", postCode: 0970, postalArea: "Oslo" } }); ! beneficiary1 = { name: "Testy2", ssn: "04037335466" } beneficiary2 = { name: "Testy3", ssn: "18049938744" } beneficiary3 = { name: "Testy4", ssn: "21050682312" } ! cart = new Cart(); ! order = new Order({ cart: cart, policyHolder: policyHolder1, withdrawalDay: 15 }); }); REMEMBER THIS ONE?

Slide 61

Slide 61 text

NO CONDITIONALS NO FOR, IF OR WHILE

Slide 62

Slide 62 text

Noon 12:01 pm 1:00 pm 11:59 pm Midnight 12:01 am 1:00 am 11:00 am

Slide 63

Slide 63 text

it("handles dates correctly", function() { for (var mins = 0; mins < 1440; mins++) { var d = new Date(2014, 1, 1, 0, 0, mins, 0) var event = new Event({ date: d }); ! if (d.getHours() == 0 && d.getMinutes() == 0) { expect(event.date).toEqual("Midnight"); } else if (d.getHours() == 12 && d.getMinutes() == 0) { expect(event.date).toEqual("Noon"); } else { expect(event.date).toEqual(moment(d).format("h:mm a")); } } });

Slide 64

Slide 64 text

it("handles dates correctly", function() { for (var mins = 0; mins < 1440; mins++) { var d = new Date(2014, 1, 1, 0, 0, mins, 0) var event = new Event({ date: d }); ! if (d.getHours() == 0 && d.getMinutes() == 0) { expect(event.date).toEqual("Midnight"); } else if (d.getHours() == 12 && d.getMinutes() == 0) { expect(event.date).toEqual("Noon"); } else { expect(event.date).toEqual(moment(d).format("h:mm a")); } } }); CAN YOU SPOT THE BUG?

Slide 65

Slide 65 text

it("handles dates correctly", function() { for (var mins = 0; mins < 1440; mins++) { var d = new Date(2014, 1, 1, 0, 0, mins, 0) var event = new Event({ date: d }); ! if (d.getHours() == 0 && d.getMinutes() == 0) { expect(event.date).toEqual("Midnight"); } else if (d.getHours() == 12 && d.getMinutes() == 0) { expect(event.date).toEqual("Noon"); } else { expect(event.date).toEqual(moment(d).format("h:mm a")); } } }); This is seconds, not minutes CAN YOU SPOT THE BUG?

Slide 66

Slide 66 text

it("handles noon", function() { var event = new Event({ date: createDate({ hours: 12, mins: 0 }) }); ! expect(event.date).toEqual("Noon"); }); ! it("handles midnight", function() { var event = new Event({ date: createDate({ hours: 0, mins: 0 }) }); ! expect(event.date).toEqual('Midnight'); });

Slide 67

Slide 67 text

SEE IT FAIL YOU DON'T WANT TO FAIL AT FAILING

Slide 68

Slide 68 text

THIS TEST WILL NEVER FAIL

Slide 69

Slide 69 text

THIS TEST WILL NEVER FAIL

Slide 70

Slide 70 text

ASYNC TESTS NEED TO TELL WHEN THEY ARE DONE

Slide 71

Slide 71 text

FAIL Remember, the most important thing your tests do is

Slide 72

Slide 72 text

BEWARE OF ASYNC

Slide 73

Slide 73 text

ARRANGE ACT ASSERT it('calculates total price', function() { // ARRANGE var cart = new Cart(); ! // ACT cart.addItem(...); cart.addItem(...); ! // ASSERT expect(cart.totalPrice()).toEqual(1097); });

Slide 74

Slide 74 text

and perhaps most importantly …

Slide 75

Slide 75 text

Tests SHOULD to be stupidly simple

Slide 76

Slide 76 text

What happens when we don't understand our tests?

Slide 77

Slide 77 text

Write tests you'd be happy to debug yourself

Slide 78

Slide 78 text

MAINTAINING YOUR TESTS

Slide 79

Slide 79 text

POOR QUALITY TESTS CAN SLOW DEVELOPMENT TO A CRAWL

Slide 80

Slide 80 text

FEEL THE PAIN IF IT DOESN'T FEEL GOOD, FIX IT!

Slide 81

Slide 81 text

REFACTOR YOUR TESTS

Slide 82

Slide 82 text

3:1

Slide 83

Slide 83 text

15000:5000 LINES OF TESTS LINES OF APP CODE

Slide 84

Slide 84 text

"… test the areas that you are most worried about going wrong" – Martin Fowler

Slide 85

Slide 85 text

WHAT IS THE COST OF TESTING? ! WHAT IS THE VALUE OF TESTING?

Slide 86

Slide 86 text

LESS DOGMA-DRIVEN TESTING, PLEASE

Slide 87

Slide 87 text

DON'T ACCEPT INCOMPREHENSIBLE TESTS

Slide 88

Slide 88 text

KEEP 'EM SIMPLE

Slide 89

Slide 89 text

TESTS CAN BE AWESOME

Slide 90

Slide 90 text

HOW CAN WE INCREASE THE VALUE?

Slide 91

Slide 91 text

SO — WHAT IS A BEAUTIFUL TEST?

Slide 92

Slide 92 text

I've got no definitive answer

Slide 93

Slide 93 text

FAST MAINTAINABLE UNDERSTANDABLE FAILS (when it should) REVEALS INTENT NO SETUP DETERMINISTIC NO CONDITIONALS FOCUSED ON THE READER

Slide 94

Slide 94 text

Focus on the craft, not the tools

Slide 95

Slide 95 text

Fuss about 'em Keep 'em simple Talk about 'em Don't let them deteriorate They need to run automatically A failing test fails your build

Slide 96

Slide 96 text

WANT TO LEARN MORE?

Slide 97

Slide 97 text

THANK YOU I'm here until Sunday — let me know if you want to talk JavaScript kimjoar.net