Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

Old Solutions to New Testing Problems

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Wird geladen in …3
×

Hier ansehen

1 von 96 Anzeige

Old Solutions to New Testing Problems

Herunterladen, um offline zu lesen

JavaScript test tooling has advanced a lot in the last few years, but tooling can’t solve everything—we still have decisions to make about how to optimally set up our tests. It would be great if we could learn from experienced testers who came before us, but it can be difficult to follow writing about testing in a programming language we aren’t familiar with.

Luckily, there’s one book in particular that has a lot of language-agnostic testing wisdom to share: xUnit Test Patterns. We’ll walk through some of the “test smells” it describes and see examples of how they commonly arise in JavaScript, then we’ll apply the principles from the book to solve these problems. You’ll walk away from this session with more tools in your tool belt to solve testing problems, and clearer language to talk about the tools you already have.

JavaScript test tooling has advanced a lot in the last few years, but tooling can’t solve everything—we still have decisions to make about how to optimally set up our tests. It would be great if we could learn from experienced testers who came before us, but it can be difficult to follow writing about testing in a programming language we aren’t familiar with.

Luckily, there’s one book in particular that has a lot of language-agnostic testing wisdom to share: xUnit Test Patterns. We’ll walk through some of the “test smells” it describes and see examples of how they commonly arise in JavaScript, then we’ll apply the principles from the book to solve these problems. You’ll walk away from this session with more tools in your tool belt to solve testing problems, and clearer language to talk about the tools you already have.

Anzeige
Anzeige

Weitere Verwandte Inhalte

Anzeige

Aktuellste (20)

Old Solutions to New Testing Problems

  1. 1. Old Solutions to New Testing Problems Josh Justice 1 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  2. 2. 2 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  3. 3. JavaScript test tooling has improved a lot. 3 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  4. 4. The problem now: Test Maintainability 4 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  5. 5. How can we learn how to write more maintainable tests? 5 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  6. 6. 6 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  7. 7. "Well actually, in Ruby we write tests like this—" 7 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  8. 8. 8 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  9. 9. PATTERNS 9 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  10. 10. 10 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  11. 11. Five Patterns 11 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  12. 12. Takeaways 1. New testing approaches 2. Clearer explanation of the benefits 3. Shared language and support 12 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  13. 13. ! "Code Smells" 13 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  14. 14. "Code Smells" ⚠ Warning Signs 14 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  15. 15. 115 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  16. 16. import formatAddress from '../formatAddress'; import addresses from '../__sampleData__/addresses'; describe('formatAddress', () => { it('formats addresses correctly', () => { let expectedResult; for (const address of addresses) { if (address.street2) { expectedResult = `${address.street1} ${address.street2} ${address.city}, ${address.province} ${address.postalCode}`; } else { expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; } expect(formatAddress(address)).toEqual(expectedResult); } }); }); 16 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  17. 17. Problems • Tests can be hard to understand • Tests might have a bug, and you don't test your tests • If test data changes, not all cases might be executed 17 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  18. 18. ⚠ Flexible Test 18 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  19. 19. ⚠ Flexible Test "Using conditional logic to reuse a single test to verify several different cases." 19 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  20. 20. Solution: Simple Tests Split the tests into simpler cases, controlling for specific situations. 20 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  21. 21. it('formats an address with two lines correctly', () => { const address = addresses[0]; const expectedResult = `${address.street1} ${address.street2} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 21 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  22. 22. it('formats an address with one line correctly', () => { const address = addresses[1]; const expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 22 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  23. 23. Warning Sign: ⚠ Flexible Test Solution: Simple Tests 23 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  24. 24. 224 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  25. 25. import formatAddress from '../formatAddress'; import addresses from '../__sampleData__/addresses'; describe('formatAddress', () => { it('formats an address with one line correctly', () => { const address = addresses[1]; const expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); }); 25 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  26. 26. import formatAddress from '../formatAddress'; import addresses from '../__sampleData__/addresses'; describe('formatAddress', () => { it('formats an address with one line correctly', () => { const address = addresses[1]; const expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); }); 26 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  27. 27. Problems • Can't see the data relevant to what's being tested, have to look in another file • Test harder to understand, easier for bugs to sneak through • Coupled to shared test data; if it changes, test could break or give a false positive 27 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  28. 28. ⚠ Mystery Guest 28 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  29. 29. ⚠ Mystery Guest “The test reader is not able to see the cause and effect between fixture and verification logic because part of it is done outside the Test Method.” 29 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  30. 30. ⚠ Mystery Guest “The test reader is not able to see the cause and effect between FIXTURE and verification logic because part of it is done outside the Test Method.” 30 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  31. 31. Fixture “everything we need in place to exercise the [production code]”...“the pre-conditions of the test” 31 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  32. 32. Shared Fixture “…reusing the same fixture for several or many tests.” 32 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  33. 33. Fresh Fixture “Each test constructs its own brand-new test fixture for its own private use.” 33 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  34. 34. ⚠ Mystery Guest “The test reader is not able to see the cause and effect between fixture and verification logic because part of it is done outside the Test Method.” 34 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  35. 35. Solution: set up the data closer to the test Several possible approaches using Fresh Fixtures 35 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  36. 36. Approach 1: In-Line Setup “Each Test Method creates its own Fresh Fixture by [building] exactly the test fixture it requires.” 36 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  37. 37. it('formats an address with one line correctly', () => { const address = { street1: '101 College Street', city: 'Toronto', province: 'ON', postalCode: 'M5G 1L7' }; const expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 37 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  38. 38. Approach 2: Delegated Setup 38 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  39. 39. Approach 2: Delegated Setup 39 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  40. 40. Approach 2: Delegated Setup “Each Test Method creates its own Fresh Fixture by calling Creation Methods from within the Test Methods.” 40 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  41. 41. function createAddress({ hasStreet2 = false }) { const address = { street1: '101 College Street', city: 'Toronto', province: 'ON', postalCode: 'M5G 1L7' }; if (hasStreet2) { address.street2 = 'Suite 123'; } return address; }; 41 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  42. 42. it('formats an address with one line correctly', () => { const address = createAddress({ hasStreet2: false }); const expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 42 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  43. 43. it('formats an address with two lines correctly', () => { const address = createAddress({ hasStreet2: true }); const expectedResult = `${address.street1} ${address.street2} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 43 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  44. 44. Approach 3: Implicit Setup “We build the test fixture common to several tests in the setUp method." 44 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  45. 45. let address; beforeEach(() => { address = { street1: '101 College Street', city: 'Toronto', province: 'ON', postalCode: 'M5G 1L7' }; }); 45 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  46. 46. it('formats an address with one line correctly', () => { const expectedResult = `${address.street1} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 46 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  47. 47. it('formats an address with two lines correctly', () => { address.street2 = 'Apt. 317'; const expectedResult = `${address.street1} ${address.street2} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 47 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  48. 48. ⚠ Mystery Guest Solutions: 1. In-Line Setup 2. Delegated Setup 3. Implicit Setup 48 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  49. 49. 349 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  50. 50. it('formats an address correctly', () => { const address = { firstName: 'Sally', lastName: 'Smith', street1: '80 Spanida Avenue', street2: '4th Floor', city: 'Toronto', province: 'ON', postalCode: 'M5V 2J4', country: 'CA' }; const expectedResult = `${address.street1} ${address.street2} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 50 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  51. 51. Problems • Hard to see which fields matter and which don't • Tests get long 51 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  52. 52. ⚠ Irrelevant Information 52 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  53. 53. ⚠ Irrelevant Information “The test exposes a lot of irrelevant details about the fixture that distract the test reader from what really affects the behavior of the [system under test].” 53 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  54. 54. General Fixture “The test builds or references a larger fixture than is needed to verify the functionality in question.” 54 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  55. 55. Solution 1: Minimal Fixture “We use the smallest and simplest fixture possible for each test.” 55 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  56. 56. it('formats an address correctly', () => { const address = { - firstName: 'Sally', - lastName: 'Smith', street1: '80 Spanida Avenue', street2: '4th Floor', city: 'Toronto', province: 'ON', postalCode: 'M5V 2J4', - country: 'CA' }; 56 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  57. 57. it('formats an address correctly', () => { const address = { street1: '80 Spanida Avenue', street2: '4th Floor', city: 'Toronto', province: 'ON', postalCode: 'M5V 2J4' }; 57 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  58. 58. What if that data is still needed for the code to run? • Type verification • Class instance requiring constructor args • Fields validated by code unrelated to the test 58 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  59. 59. Solution 2: Parameterized Creation Method “We set up the test fixture by calling methods that hide the mechanics of building ready-to-use objects behind Intent-Revealing Names.” “A Parameterized Creation Method allows the test to pass in some attributes to be used in the creation of the object. In such a case, we should pass only those attributes that are expected to affect…the test's outcome” 59 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  60. 60. const defaultAddress = { firstName: 'Sally', lastName: 'Smith', street1: '80 Spanida Avenue', street2: '4th Floor', city: 'Toronto', province: 'ON', postalCode: 'M5V 2J4', country: 'CA' }; function createAddress(overrides = {}) { return { ...defaultAddress, ...overrides }; }; 60 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  61. 61. it('formats an address correctly', () => { const address = createAddress({ street1: '80 Spanida Avenue', street2: '4th Floor', city: 'Toronto', province: 'ON', postalCode: 'M5V 2J4' }); const expectedResult = `${address.street1} ${address.street2} ${address.city}, ${address.province} ${address.postalCode}`; expect(formatAddress(address)).toEqual(expectedResult); }); 61 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  62. 62. Solution 3: Test Data Creation Libraries 62 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  63. 63. Test Data Bot ! const { build, sequence } = require('test-data-bot') const addressBuilder = build('address').fields({ street1: '80 Spanida Avenue', street2: sequence(x => `Suite ${x}`), city: 'Toronto', province: 'ON', postalCode: 'M5V 2J4', }) 63 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  64. 64. Test Data Bot ! const user = addressBuilder({ street2: null }) 64 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  65. 65. ⚠ Irrelevant Information Solutions: 1. Minimal Fixture 2. Parameterized Creation Method 3. Test Data Creation Library 65 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  66. 66. 466 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  67. 67. import totalOrder from '../totalOrder'; import orders from '../__sampleData__/orders'; describe('totalOrder', () => { it('calculates the correct total values when there are no line items', () => { const order = orders[0]; order.lines = []; const totalValues = totalOrder(order); expect(totalValues.subtotal).toEqual(0); expect(totalValues.tax).toEqual(0); expect(totalValues.total).toEqual(0); }); 67 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  68. 68. it('calculates the correct total values', () => { const order = orders[0]; const totalValues = totalOrder(order); expect(totalValues.subtotal).toEqual(7); expect(totalValues.tax).toBeCloseTo(1.05); expect(totalValues.total).toBeCloseTo(8.05); }); }); 68 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  69. 69. ⚠ Interacting Tests 69 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  70. 70. ⚠ Interacting Tests "Tests depend on other tests in some way…[for example,] a test can be run as part of a suite but cannot be run by itself" In our case, a test can be run by itself but cannot be run as part of a suite. 70 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  71. 71. ⚠ Shared Fixture 71 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  72. 72. Solution: Fresh Fixture 72 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  73. 73. Solution: Fresh Fixture “Each test constructs its own brand-new test fixture for its own private use.” 73 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  74. 74. it('calculates the correct total values when there are no line items', () => { const order = _.cloneDeep(orders[0]); order.lines = []; const totalValues = totalOrder(order); expect(totalValues.subtotal).toEqual(0); expect(totalValues.tax).toEqual(0); expect(totalValues.total).toEqual(0); }); 74 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  75. 75. it('calculates the correct total values', () => { const order = _.cloneDeep(orders[0]); const totalValues = totalOrder(order); expect(totalValues.subtotal).toEqual(7); expect(totalValues.tax).toBeCloseTo(1.05); expect(totalValues.total).toBeCloseTo(8.05); }); }); 75 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  76. 76. Warning Sign: ⚠ Interacting Tests Solution: Fresh Fixture 76 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  77. 77. 577 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  78. 78. const TAX_PERCENT = 0.15; function totalOrder(order) { const lineTotals = order.lines.map(line => ( line.pricePerItem * line.quantity )); const subtotal = lineTotals.reduce((acc, val) => acc + val, 0); const tax = subtotal * TAX_PERCENT; const total = subtotal + tax; return { subtotal, tax, total }; } 78 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  79. 79. describe('totalOrder', () => { it('calculates the correct total values', () => { const order = { lines: [ { pricePerItem: 5.99, quantity: 3 }, { pricePerItem: 7.99, quantity: 4 } ] }; 79 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  80. 80. const lineTotals = order.lines.map( line => line.pricePerItem * line.quantity ); const subtotal = lineTotals.reduce((acc, val) => acc + val); const tax = subtotal * 0.15; const total = subtotal + tax; const totalValues = totalOrder(order); expect(totalValues.subtotal).toEqual(subtotal); expect(totalValues.tax).toBeCloseTo(tax); expect(totalValues.total).toBeCloseTo(total); }); }); 80 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  81. 81. Problems • If the logic is wrong in one place, it's wrong in both. Is it really testing? • Doesn't let you see the intended result at a glance 81 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  82. 82. ⚠ Production Logic in Test 82 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  83. 83. ⚠ Production Logic in Test “[If we] use a Calculated Value based on the inputs…we find ourselves replicating the expected [production] logic inside our test to calculate the expected values for assertions.” 83 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  84. 84. Solution: Hard-Coded Test Data 84 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  85. 85. it('calculates the correct total values', () => { const order = { lines: [ { pricePerItem: 2, quantity: 2 }, { pricePerItem: 3, quantity: 1 } ] }; const totalValues = totalOrder(order); expect(totalValues.subtotal).toEqual(7); expect(totalValues.tax).toBeCloseTo(1.05); expect(totalValues.total).toBeCloseTo(8.05); }); 85 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  86. 86. Solution: Hard-Coded Test Data 86 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  87. 87. Solution: Hard-Coded Test Data ! 87 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  88. 88. Warning Sign ⚠ Production Logic in Test Solution Hard-Coded Test Data 88 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  89. 89. Review: Test Patterns 1. Flexible Test → Simple Tests 2. Mystery Guest → In-Line Setup 3. Irrelevant Information → Minimal Fixture 4. Interacting Tests → Fresh Fixture 5. Production Logic in Test → Hard-Coded Test Data 89 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  90. 90. Takeaways 90 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  91. 91. 91 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  92. 92. Let's talk about test patterns! 92 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  93. 93. Let's talk about test patterns! And let's use shared language! 93 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  94. 94. The deeper takeaway… 94 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  95. 95. Let's connect people with our rich testing history. 95 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong
  96. 96. @CodingItWrong jjustice@bignerdranch.com Photos: https://unsplash.com/collections/ 8294370/old-solutions-talk 96 Old Solutions to New Testing Problems - Assert(js) 2019 - @CodingItWrong

×