Pro Yearly is on sale from $80 to $50! »

Cleaner unit testing with the Arrange Act Assert pattern

B3c6f332ab773b78f4ebd72d56d7b6df?s=47 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

B3c6f332ab773b78f4ebd72d56d7b6df?s=128

James Cooke

September 18, 2016
Tweet

Transcript

  1. Cleaner unit testing with the Arrange Act Assert pattern James

    Cooke Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
  2. Outline • Background • Problem • AAA pattern • Smells

    and solutions • Questions in the break
  3. Background • Python developer • TDD with AAA • github.com/txels

  4. Givens • PEP008 is beneficial • PEP020 is our mantra

    • We all TDD • Every test is a function, uncoupled • Simplicity trumps performance • Eliminate duplication
  5. Problem • Pythonic code in project, carnage in tests •

    Tests of OSS projects are often hard to understand • Tests are non-uniform
  6. Example Member Account Membership 1 1 m m

  7. The Pattern def test_x(*args, **kwargs): """__docstring__ """ # Arrange #

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

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

  10. Docstring • Follow existing style of your project • First

    line positive statement of tested action • Extra info about test, conditions
  11. def test_printable_name(): """__docstring__ """ # Arrange # Act # Assert

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

    # Act # Assert
  13. Arrange • Single block of code • No assertions •

    Only prepare non-deterministic results not available after Act • Should not require comments
  14. def test_printable_name(): """New members have printable names """ # Arrange

    # Act # Assert
  15. def test_printable_name(): """New members have printable names """ person =

    Member() person.first_name = 'René' person.last_name = ' 北京 ' # Act # Assert
  16. Act • `result = ` format • One line only

    • Can be wrapped in `with raises` for expected exceptions
  17. def test_printable_name(): """New members have printable names """ person =

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

    Member() person.first_name = 'René' person.last_name = ' 北京 ' result = person.printable_name() # Assert
  19. Assert • Single block of code • No actions should

    happen • Test result first, then side effects • Use simple blocks of assertions
  20. def test_printable_name(): """New members have printable names """ person =

    Member() person.first_name = 'René' person.last_name = ' 北京 ' result = person.printable_name() # Assert
  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é'
  22. Smell: Assertions in Arrange

  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
  24. person = Member() account = Account() person.join(account) assert person in

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

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

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

    account = Account() person.join(account) result = account.members assert person in result
  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
  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
  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
  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
  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
  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
  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
  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
  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
  37. Smell: Duplication between tests

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

    2 Account 3
  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
  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()
  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()
  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
  43. Addendum: TestCase.setUp • A pure Arrange block • Docstring if

    required • Assert valid with a special test called test_set_up
  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
  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
  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
  47. Addendum: test_set_up • A pure Assert block • Docstring if

    required
  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
  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
  50. Summary • Demonstrated AAA • Small and simple tests •

    Opens the door to DRY and clarity
  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.