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

TDD anti patterns - episode 4 - with Javier Martínez

TDD anti patterns - episode 4 - with Javier Martínez

Have you ever seen those anti patterns in your code base?

The greedy catcher, The sequencer, Hidden dependency and The enumerator.

More than 45% of developers haven't, join us to find out more.

Ed39ca0d44a6e6cdefc76ac548de5f41?s=128

Marabesi

March 17, 2022
Tweet

More Decks by Marabesi

Other Decks in Programming

Transcript

  1. TDD - EP 4 codurance.com Testing anti-patterns - The greedy

    catcher, The sequencer, Hidden dependency and The enumerator
  2. Matheus Marabesi Hello there, you can call me Marabesi, But

    my name is Matheus Marabesi, I work at Codurance as a Software Craftsperson. I enjoy talking about anything related to: testing, patterns and gamification. You can find me at @MatheusMarabesi or https://marabesi.com Codurance Crafting Code
  3. 1. Intro - Recap 2. The greedy catcher 3. The

    sequencer 4. Hidden dependency 5. The enumerator 6. Wrapping up Crafting code Agenda
  4. 1. Recap Episode 1, Episode 2, Episode 3 Getting started

  5. The Liar The Giant The Mockery The Inspector Generous Leftovers

    The Local Hero The Nitpicker The Secret Catcher The Dodger The Loudmouth Anti patterns The Greedy Catcher Excessive Setup The Sequencer Hidden Dependency The Enumerator The Stranger The Operating System Evangelist Success Against All Odds The Free Ride The One The Peeping Tom The Slow Poke James Carr - TDD Anti-Patterns
  6. The Liar 4 The Giant 5 The Mockery The Inspector

    Generous Leftovers The Local Hero The Nitpicker The Secret Catcher The Dodger The Loudmouth Anti patterns The Greedy Catcher Excessive Setup 3 The Sequencer Hidden Dependency The Enumerator The Stranger The Operating System Evangelist Success Against All Odds The Free Ride The One The Peeping Tom The Slow Poke 6
  7. Anti patterns - Survey takeaways 1. Survey notes: Javascript, PHP

    and Java were the most used programming languages 2. Survey notes: Practitioners usually informally learn TDD 3. The anti patterns covered were related to test last 4. Subjects we touched around testability: SOLID, Object calisthenics, Non-determinism and the test pyramid
  8. 2. Anti-patterns - Episode 4 The greedy catcher, The sequencer,

    Hidden dependency and The enumerator Getting started
  9. The Liar The Giant The Mockery The Inspector Generous Leftovers

    The Local Hero The Nitpicker The Secret Catcher The Dodger The Loudmouth Anti patterns The Greedy Catcher Excessive Setup The Sequencer Hidden Dependency The Enumerator The Stranger The Operating System Evangelist Success Against All Odds The Free Ride The One The Peeping Tom The Slow Poke James Carr - TDD Anti-Patterns
  10. The Liar The Giant The Mockery The Inspector Generous Leftovers

    The Local Hero The Nitpicker The Secret Catcher The Dodger The Loudmouth Anti patterns The Greedy Catcher 8 Excessive Setup The Sequencer 8 Hidden Dependency 2 The Enumerator 9 The Stranger The Operating System Evangelist Success Against All Odds The Free Ride The One The Peeping Tom The Slow Poke James Carr - TDD Anti-Patterns
  11. Challenge time Crafting code Which one if faster to understand?

  12. describe('Request.headers', function () { it('should work', async () => {

    const { page, server, isChrome } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); if (isChrome) expect(response.request().headers()['user-agent']).toContain('Chrome'); else expect(response.request().headers()['user-agent']).toContain('Firefox'); }); }); describe('Request.headers', function () { itChromeOnly('should define Chrome as user agent header', async () => { const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().headers()['user-agent']).toContain('Chrome'); }); itFirefoxOnly('should define Firefox as user agent header', async () => { const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().headers()['user-agent']).toContain('Firefox'); }); }); Puppeteer A
  13. describe('Request.headers', function () { it('should work', async () => {

    const { page, server, isChrome } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); if (isChrome) expect(response.request().headers()['user-agent']).toContain('Chrome'); else expect(response.request().headers()['user-agent']).toContain('Firefox'); }); }); describe('Request.headers', function () { itChromeOnly('should define Chrome as user agent header', async () => { const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().headers()['user-agent']).toContain('Chrome'); }); itFirefoxOnly('should define Firefox as user agent header', async () => { const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().headers()['user-agent']).toContain('Firefox'); }); }); Puppeteer B
  14. describe('Request.headers', function () { it('should work', async () => {

    const { page, server, isChrome } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); if (isChrome) expect(response.request().headers()['user-agent']).toContain('Chrome'); else expect(response.request().headers()['user-agent']).toContain('Firefox'); }); }); describe('Request.headers', function () { itChromeOnly('should define Chrome as user agent header', async () => { const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().headers()['user-agent']).toContain('Chrome'); }); itFirefoxOnly('should define Firefox as user agent header', async () => { const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().headers()['user-agent']).toContain('Firefox'); }); }); Puppeteer A B
  15. • Could you spot something that seems to be a

    smell? Anti-patterns?
  16. • Could you spot something that seems to be a

    smell? • Were there anti patterns? Anti-patterns?
  17. 2. The greedy catcher - 🏆 8 Crafting code

  18. 2. The greedy catcher - 🏆 8 A unit test

    which catches exceptions and swallows the stack trace, sometimes replacing it with a less informative failure message, but sometimes even just logging (c.f. Loudmouth) and letting the test pass. Crafting code
  19. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  20. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  21. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  22. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  23. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  24. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  25. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  26. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  27. public function test_retrieve_the_latest_payment_for_a_subscription() { $user = $this->createCustomer('retrieve_the_latest_payment_for_a_subscription'); try { $user->newSubscription('main',

    static::$priceId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.IncompletePayment::class.' was not thrown.'); } catch (IncompletePayment $e) { $subscription = $user->refresh()->subscription('main'); $this->assertInstanceOf(Payment::class, $payment = $subscription->latestPayment()); $this->assertTrue($payment->requiresAction()); } } PHP - Laravel/Cashier stripe
  28. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  29. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  30. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  31. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  32. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  33. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  34. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  35. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  36. try { const token = jwt_decode(req?.cookies['token']); if (token) { return

    null; } else { return await logout($auth, redirect); } } catch (e) { return await logout($auth, redirect); } Jest
  37. it('should logout when token is invalid', async () => {

    const redirect = jest.fn(); const serverParameters: Partial<IContextCookie> = { route: currentRoute as Route, $auth, redirect }; await actions.nuxtServerInit( actionContext as ActionContext, serverParameters as IContextCookie ); expect($auth.logout).toHaveBeenCalled(); }); Jest
  38. it('should logout when token is invalid', async () => {

    const redirect = jest.fn(); const serverParameters: Partial<IContextCookie> = { route: currentRoute as Route, $auth, redirect }; await actions.nuxtServerInit( actionContext as ActionContext, serverParameters as IContextCookie ); expect($auth.logout).toHaveBeenCalled(); }); Jest
  39. it('should logout when token is invalid', async () => {

    const redirect = jest.fn(); const serverParameters: Partial<IContextCookie> = { route: currentRoute as Route, $auth, redirect }; await actions.nuxtServerInit( actionContext as ActionContext, serverParameters as IContextCookie ); expect($auth.logout).toHaveBeenCalled(); }); Jest
  40. it('should logout when token is invalid', async () => {

    const redirect = jest.fn(); const serverParameters: Partial<IContextCookie> = { route: currentRoute as Route, $auth, redirect }; await actions.nuxtServerInit( actionContext as ActionContext, serverParameters as IContextCookie ); expect($auth.logout).toHaveBeenCalled(); }); Jest
  41. it('should logout when token is invalid', async () => {

    const redirect = jest.fn(); const serverParameters: Partial<IContextCookie> = { route: currentRoute as Route, $auth, redirect }; await actions.nuxtServerInit( actionContext as ActionContext, serverParameters as IContextCookie ); expect($auth.logout).toHaveBeenCalled(); }); Jest
  42. None
  43. Secret catcher, silent catcher Relates to

  44. 3. The sequencer - 🏆8 Crafting code

  45. 3. The sequencer - 🏆8 A unit test that depends

    on items in an unordered list appearing in the same order during assertions. Crafting code
  46. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  47. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  48. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  49. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  50. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  51. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  52. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  53. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  54. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  55. test(contains three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('contains three fruits', () => { const expectedFruits = ['mango', 'watermelon', 'banana'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Jest - arrays / primitives
  56. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert list(result.columns) == ["id", "quantity", "country", "form", "order_id"] Python
  57. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert list(result.columns) == ["id", "quantity", "country", "form", "order_id"] Python
  58. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert list(result.columns) == ["id", "quantity", "country", "form", "order_id"] Python
  59. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert list(result.columns) == ["id", "quantity", "country", "form", "order_id"] Python
  60. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert list(result.columns) == ["id", "quantity", "country", "form", "order_id"] Python
  61. tests/test_predictions.py::TestPredictions::test_predictions_returns_a_dataframe_with_automatic_pr edictions FAILED [100%] tests/test_predictions.py:16 (TestPredictions.test_predictions_returns_a_dataframe_with_automatic_predictions) ['id', 'quantity', 'form', 'country',

    'order_id'] != ['id', 'quantity', 'country', 'form', 'order_id'] Expected :['id', 'quantity', 'country', 'form', 'order_id'] Actual :['id', 'quantity', 'form', 'country', 'order_id'] Python
  62. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert list(result.columns) == ["id", "quantity", "country", "form", "order_id"] Python
  63. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert ("id" in result.columns and "quantity" in result.columns and "country" in result.columns and "form" in result.columns and "order_id" in result.columns) Python
  64. def test_predictions_returns_a_dataframe_with_automatic_predictions(self,form): # Given order_id = "51a64e87-a768-41ed-b6a5-bf0633435e20" order_info = pd.DataFrame({"order_id":

    [order_id], "form": [form],}) file_path = Path("tests/data/prediction_data.csv") service = FileRepository(file_path) # When result = get_predictions(main_service=service, order_info=order_info) # Then assert set(result.columns) == {"id", "quantity", "country", "form", "order_id"} Python
  65. Points of attention • Know your data structures • Think

    about the role the order plays in a collection
  66. 4. Hidden dependency - 🏆2 Crafting code

  67. 4. Hidden dependency - 🏆2 A unit test that requires

    some existing data to have been populated somewhere before the test runs. If that data wasn’t populated, the test will fail and leave little indication to the developer what it wanted, or why… forcing them to dig through acres of code to find out where the data it was using was supposed to come from. Crafting code
  68. query: str = """ select product.id po.order_id, po.quantity, product.country from

    product join purchased as pur on pur.product_id = product.id join purchased_order as po on po.purchase_id = cur.id where product.completed is true and pur.type = 'MANUAL' and product.is_test is true ; """ Python
  69. def test_dbdatasource_is_able_to_load_products_related_only_to_manual_purchase( self, db_resource ): # Given config_file_path = Path("./tests/data/configs/docker_config.json")

    expected_result = pd.read_csv("./tests/data/manual_product_info.csv") datasource = DBDataSource(config_file_path=config_file_path) # When result = datasource.get_manual_purchases() # Then assert result.equals(expected_result) Python
  70. def test_dbdatasource_is_able_to_load_products_related_only_to_manual_purchase( self, db_resource ): # Given config_file_path = Path("./tests/data/configs/docker_config.json")

    expected_result = pd.read_csv("./tests/data/manual_product_info.csv") datasource = DBDataSource(config_file_path=config_file_path) # When result = datasource.get_manual_purchases() # Then assert result.equals(expected_result) Python
  71. def test_dbdatasource_is_able_to_load_products_related_only_to_manual_purchase( self, db_resource ): # Given config_file_path = Path("./tests/data/configs/docker_config.json")

    expected_result = pd.read_csv("./tests/data/manual_product_info.csv") datasource = DBDataSource(config_file_path=config_file_path) # When result = datasource.get_manual_purchases() # Then assert result.equals(expected_result) Python
  72. def test_dbdatasource_is_able_to_load_products_related_only_to_manual_purchase( self, db_resource ): # Given config_file_path = Path("./tests/data/configs/docker_config.json")

    expected_result = pd.read_csv("./tests/data/manual_product_info.csv") datasource = DBDataSource(config_file_path=config_file_path) # When result = datasource.get_manual_purchases() # Then assert result.equals(expected_result) Python
  73. def test_dbdatasource_is_able_to_load_products_related_only_to_manual_purchase( self, db_resource ): # Given config_file_path = Path("./tests/data/configs/docker_config.json")

    expected_result = pd.read_csv("./tests/data/manual_product_info.csv") datasource = DBDataSource(config_file_path=config_file_path) # When result = datasource.get_manual_purchases() # Then assert result.equals(expected_result) Python
  74. E assert False E + where False = <bound method

    NDFrame.equals of product_id ... country\n0 851799e1-9b28-4210-5c44-30ddf031ad20 ... 652400 181\n\n[1 rows x 5 columns]>( product_id ... country\n0 2893abc0-eab0-7223-a73d-e39060a7eabe ... 652400 099\n1 4760ff15-52af-1638-0c61-0ebecefc3eb0 ... 652400 130\n2 328f6852-9bf1-e1ce-cf44-8f680537adc6 ... 652400 148\n3 851799e1-9b28-4210-5c44-30ddf031ad20 ... 652400 181\n4 00ab74ed-bd99-63af-ca0c-4f10c8c090db ... 652400 249\n5 2893abc0-eab0-7223-a73d-e39060a7eabe ... 652400 099\n6 4760ff15-52af-1638-0c61-0ebecefc3eb0 ... 652400 130\n7 328f6852-9bf1-e1ce-cf44-8f680537adc6 ... 652400 148\n8 851799e1-9b28-4210-5c44-30ddf031ad20 ... 652400 181\n9 00ab74ed-bd99-63af-ca0c-4f10c8c090db ... 652400 249\n\n[10 rows x 5 columns]) E + where <bound method NDFrame.equals of product_id ... country\n0 851799e1-9b28-4210-5c44-30ddf031ad20 ... 652400 181\n\n[1 rows x 5 columns]> = fragrance_id ... country\n0 851799e1-9b28-4210-5c44-30ddf031ad20 ... 652400 181\n\n[1 rows x 5 columns].equals Python
  75. is_test boolean DEFAULT false NOT NULL … and we forgot

    to fulfill it.. Python
  76. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  77. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  78. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  79. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  80. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  81. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  82. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store(); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  83. export const Store = () => ({ modules: { user:

    { namespaced: true, state: { currentAdmin: { email: 'fake@fake.com', }, }, getters: userGetters, }, admin: adminStore(adminList).modules.admin, }, }); Jest
  84. export const Store = () => ({ modules: { user:

    { namespaced: true, state: { currentAdmin: { email: 'fake@fake.com', }, }, getters: userGetters, }, admin: adminStore(adminList).modules.admin, }, }); Jest
  85. it('should list admins in the administrator field to be able

    to pick one up', async () => { const store = Store({ admin: { name: 'Admin' } }); const { findByTestId, getByText } = render(AdminPage as any, { store, mocks: { $route: { query: {}, }, }, }); await fireEvent.click(await findByTestId('admin-list')); await waitFor(() => { expect(getByText('Admin')).toBeInTheDocument(); }); }); Jest
  86. • Use some libraries that generates fake data • Test

    data adapters as soon as possible • If possible try to avoid data from external sources Points of attention
  87. 5. The enumerator - 🏆9 Crafting code

  88. 5. The enumerator - 🏆9 A unit test with each

    test case method name is only an enumeration, i.e. test1, test2, test3. As a result, the intention of the test case is unclear, and the only way to be sure is to read the test case code and pray for clarity. Crafting code
  89. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  90. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  91. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  92. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  93. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  94. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  95. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  96. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  97. from status_processor import StatusProcessor def test_set_status(): row_with_status_inactive_1 = dict( row_with__status_inactive_2

    = dict( row_with_status_inactive_3 = dict( row_with_status_inactive_3b = dict( row_with_status_inactive_4 = dict( row_with_status_inactive_5 = dict( The enumerator - Python
  98. The enumerator - typescript + php

  99. The enumerator - typescript + php

  100. The enumerator - typescript + php

  101. The enumerator - typescript + php

  102. The enumerator - typescript + php

  103. The enumerator - typescript + php

  104. The enumerator - typescript + php

  105. The enumerator - typescript + php

  106. The enumerator - typescript + php

  107. The enumerator - typescript + php

  108. The enumerator - typescript + php

  109. The enumerator - typescript + php

  110. • Are we using 1, 2, 3? • The test

    that failed was easy to understand why? Points of attention
  111. 6. Wrapping up We are almost done! Crafting code

  112. • The Greedy Catcher • The Sequencer • Hidden Dependency

    • The Enumerator • and many more! What we covered
  113. https://www.codurance.com/publications/building-testing-culture https://www.codurance.com/publications/tdd-anti-patterns-chapter-1

  114. https://www.codurance.com/publications/building-testing-culture https://www.codurance.com/publications/building-testing-culture

  115. Matheus Marabesi Hello there, you can call me Marabesi, But

    my name is Matheus Marabesi, I work at Codurance as a Software craftsperson. I enjoy talking about anything related to: testing, patterns and gamification. You can find me at @MatheusMarabesi or https://marabesi.com Codurance Crafting Code