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 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 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 Slide

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

    View 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 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 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 Slide

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

    View 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 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 Slide

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

    View 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 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Slide

  42. View Slide

  43. Secret catcher, silent catcher
    Relates to

    View Slide

  44. 3. The sequencer - 🏆8
    Crafting code

    View Slide

  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

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

    View Slide

  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

    View 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 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 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 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 Slide

  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

    View Slide

  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

    View 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 list(result.columns) == ["id", "quantity", "country", "form", "order_id"]
    Python

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

  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

    View Slide

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

    View Slide

  66. 4. Hidden dependency - 🏆2
    Crafting code

    View Slide

  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

    View Slide

  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

    View 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 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 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 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 Slide

  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

    View Slide

  74. 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 Slide

  75. is_test boolean DEFAULT false NOT NULL

    and we forgot to fulfill it..
    Python

    View 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 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 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 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 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 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 Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  87. 5. The enumerator - 🏆9
    Crafting code

    View Slide

  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

    View 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 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 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 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 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 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 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 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 Slide

  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

    View Slide

  98. The enumerator - typescript + php

    View Slide

  99. The enumerator - typescript + php

    View Slide

  100. The enumerator - typescript + php

    View Slide

  101. The enumerator - typescript + php

    View Slide

  102. The enumerator - typescript + php

    View Slide

  103. The enumerator - typescript + php

    View Slide

  104. The enumerator - typescript + php

    View Slide

  105. The enumerator - typescript + php

    View Slide

  106. The enumerator - typescript + php

    View Slide

  107. The enumerator - typescript + php

    View Slide

  108. The enumerator - typescript + php

    View Slide

  109. The enumerator - typescript + php

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide