$30 off During Our Annual Pro Sale. View Details »

Cleaner unit testing with the Arrange Act Assert pattern

James Cooke
September 18, 2016

Cleaner unit testing with the Arrange Act Assert pattern

My name is James and I've been using the Arrange Act Assert pattern as part of my daily Test Driven Development practice for a number of years over multiple projects with different development teams. During this time I've worked to make my tests as readable, simple and generally Pythonic as possible - and I firmly believe that the AAA pattern can help us all with this quest.

See my blog post for more info: http://jamescooke.info/cleaner-unit-testing-with-the-arrange-act-assert-pattern.html

James Cooke

September 18, 2016
Tweet

More Decks by James Cooke

Other Decks in Programming

Transcript

  1. Cleaner unit testing with the
    Arrange
    Act
    Assert
    pattern
    James Cooke
    Licensed under the
    Creative Commons Attribution-ShareAlike 3.0 Unported License.

    View Slide

  2. Outline

    Background

    Problem

    AAA pattern

    Smells and solutions

    Questions in the break

    View Slide

  3. Background

    Python developer

    TDD with AAA

    github.com/txels

    View Slide

  4. Givens

    PEP008 is beneficial

    PEP020 is our mantra

    We all TDD

    Every test is a function, uncoupled

    Simplicity trumps performance

    Eliminate duplication

    View Slide

  5. Problem

    Pythonic code in project, carnage
    in tests

    Tests of OSS projects are often
    hard to understand

    Tests are non-uniform

    View Slide

  6. Example
    Member
    Account
    Membership
    1 1
    m m

    View Slide

  7. The Pattern
    def test_x(*args, **kwargs):
    """__docstring__
    """
    # Arrange
    # Act
    # Assert
    AKA ‘AAA’ or ‘3A’

    View Slide

  8. def test_x(*args, **kwargs):
    """__docstring__
    """
    # Arrange
    # Act
    # Assert

    View Slide

  9. def test_printable_name():
    """__docstring__
    """
    # Arrange
    # Act
    # Assert

    View Slide

  10. Docstring

    Follow existing style of your
    project

    First line positive statement of
    tested action

    Extra info about test, conditions

    View Slide

  11. def test_printable_name():
    """__docstring__
    """
    # Arrange
    # Act
    # Assert

    View Slide

  12. def test_printable_name():
    """New members have printable names
    """
    # Arrange
    # Act
    # Assert

    View Slide

  13. Arrange

    Single block of code

    No assertions

    Only prepare non-deterministic
    results not available after Act

    Should not require comments

    View Slide

  14. def test_printable_name():
    """New members have printable names
    """
    # Arrange
    # Act
    # Assert

    View Slide

  15. def test_printable_name():
    """New members have printable names
    """
    person = Member()
    person.first_name = 'René'
    person.last_name = ' 北京 '
    # Act
    # Assert

    View Slide

  16. Act

    `result = ` format

    One line only

    Can be wrapped in `with
    raises` for expected exceptions

    View Slide

  17. def test_printable_name():
    """New members have printable names
    """
    person = Member()
    person.first_name = 'René'
    person.last_name = ' 北京 '
    # Act
    # Assert

    View Slide

  18. def test_printable_name():
    """New members have printable names
    """
    person = Member()
    person.first_name = 'René'
    person.last_name = ' 北京 '
    result = person.printable_name()
    # Assert

    View Slide

  19. Assert

    Single block of code

    No actions should happen

    Test result first, then side effects

    Use simple blocks of assertions

    View Slide

  20. def test_printable_name():
    """New members have printable names
    """
    person = Member()
    person.first_name = 'René'
    person.last_name = ' 北京 '
    result = person.printable_name()
    # Assert

    View Slide

  21. def test_printable_name():
    """New members have printable names
    """
    person = Member()
    person.first_name = 'René'
    person.last_name = ' 北京 '
    result = person.printable_name()
    assert result == ' 北京 , René'

    View Slide

  22. Smell: Assertions in Arrange

    View Slide

  23. def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    assert person in account.members
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    View Slide

  24. person = Member()
    account = Account()
    person.join(account)
    assert person in account.members

    View Slide

  25. person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in account.members

    View Slide

  26. person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result

    View Slide

  27. def test_account_member():
    """Member can join account
    """
    person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result

    View Slide

  28. def test_account_member():
    """Member can join account
    """
    person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    assert person in account.members
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    View Slide

  29. def test_account_member():
    """Member can join account
    """
    person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    Eliminate duplication

    View Slide

  30. def test_account_member():
    """Member can join account
    """
    person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    result = person.account_list()
    assert len(result) == 1
    assert account in result
    person = Member()
    account = Account()
    person.join(account)
    return person, account

    View Slide

  31. def test_account_member():
    """Member can join account
    """
    person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    result = person.account_list()
    assert len(result) == 1
    assert account in result
    def account_member_factory():
    """Create Member and Account pair
    """
    person = Member()
    account = Account()
    person.join(account)
    return person, account

    View Slide

  32. def account_member_factory():
    """Create Member and Account pair
    """
    person = Member()
    account = Account()
    person.join(account)
    return person, account
    def test_account_member():
    """Member can join account
    """
    person = Member()
    account = Account()
    person.join(account)
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    View Slide

  33. def account_member_factory():
    """Create Member and Account pair
    """
    person = Member()
    account = Account()
    person.join(account)
    return person, account
    def test_account_list():
    """Member can list their accounts
    """
    person = Member()
    account = Account()
    person.join(account)
    result = person.account_list()
    assert len(result) == 1
    assert account in result
    def test_account_member():
    """Member can join account
    """
    person, account = account_member_factory()
    result = account.members
    assert person in result

    View Slide

  34. def account_member_factory():
    """Create Member and Account pair
    """
    person = Member()
    account = Account()
    person.join(account)
    return person, account
    def test_account_member():
    """Member can join account
    """
    person, account = account_member_factory()
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person, account = account_member_factory()
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    View Slide

  35. def account_member_factory():
    """Create Member and Account pair
    """
    person = Member()
    account = Account()
    person.join(account)
    return person, account
    def test_account_member_factory():
    """Member can join account
    """
    person, account = account_member_factory()
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person, account = account_member_factory()
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    View Slide

  36. def account_member_factory():
    """Create Member and Account pair
    """
    person = Member()
    account = Account()
    person.join(account)
    return person, account
    def test_account_member_factory():
    """account_member_factory creates joined Member Account pair
    """
    person, account = account_member_factory()
    result = account.members
    assert person in result
    def test_account_list():
    """Member can list their accounts
    """
    person, account = account_member_factory()
    result = person.account_list()
    assert len(result) == 1
    assert account in result

    View Slide

  37. Smell: Duplication between
    tests

    View Slide

  38. Example
    Member A
    Account 1
    Member B
    Member C
    Account 2
    Account 3

    View Slide

  39. def test_a_transfer_b():
    """Member A in Account 1 can transfer to B
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert
    def test_a_not_transfer_c():
    """Member A in Account 1 can not tranfer to C
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert
    def test_c_not_transfer_a():
    """Member C in Account 3 can not tranfer to A
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()

    Eliminate duplication

    View Slide

  40. class TestTranfers(unittest.TestCase):
    def test_a_transfer_b():
    """Member A in Account 1 can transfer to B
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert
    def test_a_not_transfer_c():
    """Member A in Account 1 can not tranfer to C
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert
    def test_c_not_transfer_a():
    """Member C in Account 3 can not tranfer to A
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()

    View Slide

  41. class TestTranfers(unittest.TestCase):
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert
    def test_a_not_transfer_c(self):
    """Member A in Account 1 can not tranfer to C
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert
    def test_c_not_transfer_a(self):
    """Member C in Account 3 can not tranfer to A
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()

    View Slide

  42. class TestTranfers(unittest.TestCase):
    def setUp(self):
    """
    """
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert

    View Slide

  43. Addendum: TestCase.setUp

    A pure Arrange block

    Docstring if required

    Assert valid with a special test
    called test_set_up

    View Slide

  44. class TestTranfers(unittest.TestCase):
    def setUp(self):
    """Create three Accounts and three members
    --------+---------------------------
    Member | Accounts
    --------+---------------------------
    user_a | account_one
    user_b | account_one, account_two
    user_c | account_three
    --------+---------------------------
    """
    super().setUp()
    self.user_a, self.account_one = account_member_factory()
    self.user_b, self.account_two = account_member_factory()
    self.user_b.join(self.account_one)
    self.user_c, self.account_three = account_member_factory()
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    user_a, account_one = account_member_factory()
    user_b, account_two = account_member_factory()
    user_b.join(account_one)
    user_c, account_three = account_member_factory()
    # Act
    # Assert

    View Slide

  45. class TestTranfers(unittest.TestCase):
    def setUp(self):
    """Create three Accounts and three members
    --------+---------------------------
    Member | Accounts
    --------+---------------------------
    user_a | account_one
    user_b | account_one, account_two
    user_c | account_three
    --------+---------------------------
    """
    super().setUp()
    self.user_a, self.account_one = account_member_factory()
    self.user_b, self.account_two = account_member_factory()
    self.user_b.join(self.account_one)
    self.user_c, self.account_three = account_member_factory()
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    # Act
    # Assert
    def test_a_not_transfer_c(self):
    """Member A in Account 1 can not tranfer to C
    """
    # Act

    View Slide

  46. class TestTranfers(unittest.TestCase):
    def setUp(self):
    """Create three Accounts and three members
    --------+---------------------------
    Member | Accounts
    --------+---------------------------
    user_a | account_one
    user_b | account_one, account_two
    user_c | account_three
    --------+---------------------------
    """
    super().setUp()
    self.user_a, self.account_one = account_member_factory()
    self.user_b, self.account_two = account_member_factory()
    self.user_b.join(self.account_one)
    self.user_c, self.account_three = account_member_factory()
    def test_set_up(self):
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    # Act
    # Assert

    View Slide

  47. Addendum: test_set_up

    A pure Assert block

    Docstring if required

    View Slide

  48. class TestTranfers(unittest.TestCase):
    def setUp(self):
    """Create three Accounts and three members
    --------+---------------------------
    Member | Accounts
    --------+---------------------------
    user_a | account_one
    user_b | account_one, account_two
    user_c | account_three
    --------+---------------------------
    """
    super().setUp()
    self.user_a, self.account_one = account_member_factory()
    self.user_b, self.account_two = account_member_factory()
    self.user_b.join(self.account_one)
    self.user_c, self.account_three = account_member_factory()
    def test_set_up(self):
    assert self.user_a.account_list() == [self.account_one]
    assert self.user_b.account_list() == [self.account_one, self.account_two
    assert self.user_c.account_list() == [self.account_three]
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    # Act
    # Assert

    View Slide

  49. class TestTranfers(unittest.TestCase):
    def setUp(self):
    """Create three Accounts and three members
    --------+---------------------------
    Member | Accounts
    --------+---------------------------
    user_a | account_one
    user_b | account_one, account_two
    user_c | account_three
    --------+---------------------------
    """
    super().setUp()
    self.user_a, self.account_one = account_member_factory()
    self.user_b, self.account_two = account_member_factory()
    self.user_b.join(self.account_one)
    self.user_c, self.account_three = account_member_factory()
    def test_set_up(self):
    assert self.user_a.account_list() == [self.account_one]
    assert self.user_b.account_list() == [self.account_one, self.account_two]
    assert self.user_c.account_list() == [self.account_three]
    def test_a_transfer_b(self):
    """Member A in Account 1 can transfer to B
    """
    # Act
    # Assert
    def test_a_not_transfer_c(self):
    """Member A in Account 1 can not tranfer to C
    """
    # Act
    # Assert
    def test_c_not_transfer_a(self):
    """Member C in Account 3 can not tranfer to A
    """
    # Act
    # Assert

    View Slide

  50. Summary

    Demonstrated AAA

    Small and simple tests

    Opens the door to DRY and clarity

    View Slide

  51. Thanks!

    Resources:
    http://jamescooke.info/cleaner...

    @jamesfublo

    Come ask questions

    Let’s talk more about testing

    Thanks for listening!
    Licensed under the
    Creative Commons Attribution-ShareAlike 3.0 Unported License.

    View Slide