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.

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

    View full-size slide

  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

    View full-size slide

  3. 1. Intro - Recap
    2. The greedy catcher
    3. The sequencer
    4. Hidden dependency
    5. The enumerator
    6. Wrapping up
    Crafting code
    Agenda

    View full-size slide

  4. 1. Recap
    Episode 1, Episode 2, Episode 3
    Getting started

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  8. 2. Anti-patterns - Episode 4
    The greedy catcher, The sequencer,
    Hidden dependency and The
    enumerator
    Getting started

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  11. Challenge time
    Crafting code
    Which one if faster to understand?

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  15. ● Could you spot something that seems to be a smell?
    Anti-patterns?

    View full-size slide

  16. ● Could you spot something that seems to be a smell?
    ● Were there anti patterns?
    Anti-patterns?

    View full-size slide

  17. 2. The greedy catcher - 🏆 8
    Crafting code

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. Secret catcher, silent catcher
    Relates to

    View full-size slide

  43. 3. The sequencer - 🏆8
    Crafting code

    View full-size slide

  44. 3. The sequencer - 🏆8
    A unit test that depends on items in an unordered
    list appearing in the same order during
    assertions.
    Crafting code

    View full-size slide

  45. 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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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 = ['mango', 'watermelon', 'banana']
    const actualFruits = ['banana', 'mango', 'watermelon']
    expect(expectedFruits).toEqual(
    expect.arrayContaining(actualFruits)
    )
    })
    Jest - arrays / primitives

    View full-size slide

  55. 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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  60. 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

    View full-size slide

  61. 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

    View full-size slide

  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 ("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

    View full-size slide

  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 set(result.columns) == {"id", "quantity", "country", "form", "order_id"}
    Python

    View full-size slide

  64. Points of attention
    ● Know your data structures
    ● Think about the role the order plays in a collection

    View full-size slide

  65. 4. Hidden dependency - 🏆2
    Crafting code

    View full-size slide

  66. 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

    View full-size slide

  67. 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

    View full-size slide

  68. 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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  73. E assert False
    E + where False = 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 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

    View full-size slide

  74. is_test boolean DEFAULT false NOT NULL

    and we forgot to fulfill it..
    Python

    View full-size slide

  75. 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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  82. export const Store = () => ({
    modules: {
    user: {
    namespaced: true,
    state: {
    currentAdmin: {
    email: '[email protected]',
    },
    },
    getters: userGetters,
    },
    admin: adminStore(adminList).modules.admin,
    },
    });
    Jest

    View full-size slide

  83. export const Store = () => ({
    modules: {
    user: {
    namespaced: true,
    state: {
    currentAdmin: {
    email: '[email protected]',
    },
    },
    getters: userGetters,
    },
    admin: adminStore(adminList).modules.admin,
    },
    });
    Jest

    View full-size slide

  84. 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

    View full-size slide

  85. ● 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

    View full-size slide

  86. 5. The enumerator - 🏆9
    Crafting code

    View full-size slide

  87. 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

    View full-size slide

  88. 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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  97. The enumerator - typescript + php

    View full-size slide

  98. The enumerator - typescript + php

    View full-size slide

  99. The enumerator - typescript + php

    View full-size slide

  100. The enumerator - typescript + php

    View full-size slide

  101. The enumerator - typescript + php

    View full-size slide

  102. The enumerator - typescript + php

    View full-size slide

  103. The enumerator - typescript + php

    View full-size slide

  104. The enumerator - typescript + php

    View full-size slide

  105. The enumerator - typescript + php

    View full-size slide

  106. The enumerator - typescript + php

    View full-size slide

  107. The enumerator - typescript + php

    View full-size slide

  108. The enumerator - typescript + php

    View full-size slide

  109. ● Are we using 1, 2, 3?
    ● The test that failed was easy to understand why?
    Points of attention

    View full-size slide

  110. 6. Wrapping up
    We are almost done!
    Crafting code

    View full-size slide

  111. ● The Greedy Catcher
    ● The Sequencer
    ● Hidden Dependency
    ● The Enumerator
    ● and many more!
    What we covered

    View full-size slide

  112. https://www.codurance.com/publications/building-testing-culture
    https://www.codurance.com/publications/tdd-anti-patterns-chapter-1

    View full-size slide

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

    View full-size slide

  114. 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

    View full-size slide