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

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.
  2. Outline • Background • Problem • AAA pattern • Smells

    and solutions • Questions in the break
  3. Givens • PEP008 is beneficial • PEP020 is our mantra

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

    Tests of OSS projects are often hard to understand • Tests are non-uniform
  5. Docstring • Follow existing style of your project • First

    line positive statement of tested action • Extra info about test, conditions
  6. Arrange • Single block of code • No assertions •

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

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

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

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

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

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

    Member() person.first_name = 'René' person.last_name = ' 北京 ' result = person.printable_name() # Assert
  13. 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é'
  14. 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
  15. def test_account_member(): """Member can join account """ person = Member()

    account = Account() person.join(account) result = account.members assert person in result
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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()
  27. 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()
  28. 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
  29. Addendum: TestCase.setUp • A pure Arrange block • Docstring if

    required • Assert valid with a special test called test_set_up
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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.