Slide 1

Slide 1 text

good test bad test ! ! ! ! ! dan crosta http://late.am/ @lazlofruvous magnetic

Slide 2

Slide 2 text

virtues of good tests

Slide 3

Slide 3 text

fast selective repeatable reliable helpful

Slide 4

Slide 4 text

fast selective repeatable reliable helpful

Slide 5

Slide 5 text

fast selective repeatable reliable helpful

Slide 6

Slide 6 text

fast selective repeatable reliable helpful

Slide 7

Slide 7 text

fast selective repeatable reliable helpful

Slide 8

Slide 8 text

http://www.free-desktop-backgrounds.net/Miscellaneous-pictures/Signs-wallpaper-images/Caution-sign.html opinions ahead

Slide 9

Slide 9 text

myth 1 100% coverage

Slide 10

Slide 10 text

if every line is covered, it must be correct. ! right?

Slide 11

Slide 11 text

https://www.sparkholder.com/wp-content/uploads/2015/01/sparkholder-api.png (modified)

Slide 12

Slide 12 text

https://s-media-cache-ak0.pinimg.com/originals/ca/4c/56/ca4c56fcdc04212f1bd11eca4a2fca36.jpg "mutually assured destruction"

Slide 13

Slide 13 text

! some code should not
 be tested

Slide 14

Slide 14 text

what would cause
 my test to fail? !

Slide 15

Slide 15 text

keep your “plumbing” dumb. !

Slide 16

Slide 16 text

myth 2 assert about everything

Slide 17

Slide 17 text

more assertions means
 higher confidence ! right?

Slide 18

Slide 18 text

def test_it_parses_log_lines(self): line = '2015-03-11T20:09:25|GET /foo?bar=baz|...' ! parsed = parse_log_line(line) ! ! ! ! ! ! ! ! ! ! ! !

Slide 19

Slide 19 text

def test_it_parses_log_lines(self): line = '2015-03-11T20:09:25|GET /foo?bar=baz|...' ! parsed = parse_log_line(line) ! self.assertEqual({ "date": datetime(2015, 3, 11, 20, 9, 25), "method": "GET", "path": "/foo", "query": "bar=baz", }, parsed) ! ! ! ! !

Slide 20

Slide 20 text

def test_it_parses_log_lines(self): line = '2015-03-11T20:09:25|GET /foo?bar=baz|...' ! parsed = parse_log_line(line) ! self.assertEqual( datetime(2015, 3, 11, 20, 9, 25), parsed["date"], ) self.assertEqual("GET", parsed["method"]) self.assertEqual("/foo", parsed["path"]) self.assertEqual("bar=baz", parsed["query"]) ! ! ! !

Slide 21

Slide 21 text

def test_it_parses_get_request_log_lines(self): # ... self.assertEqual("GET", parsed["method"]) self.assertEqual("/foo", parsed["path"]) self.assertEqual("bar=baz", parsed["query"]) ! def test_it_parses_post_request_log_lines(self): # ... self.assertEqual("POST", parsed["method"]) self.assertEqual("/foo", parsed["path"]) self.assertEqual("bar=baz", parsed["query"]) ! def test_it_parses_log_lines_with_oof_requests(self): # ... self.assertEqual("GET", parsed["method"]) self.assertEqual("/oof", parsed["path"]) self.assertEqual("bar=baz", parsed["query"])

Slide 22

Slide 22 text

what would I do when a test fails? !

Slide 23

Slide 23 text

have your tests tell you what is wrong. !

Slide 24

Slide 24 text

! only make one assertion per test

Slide 25

Slide 25 text

def test_it_parses_request_method(self): # ... self.assertEqual("GET", parsed["method"]) ! def test_it_parses_request_path(self): # ... self.assertEqual("/foo", parsed["path"]) ! def test_it_parses_query_string(self): # ... self.assertEqual("bar=baz", parsed["query"]) ! def test_it_gives_none_when_no_query_string(self): # ... self.assertIsNone(parsed["query"]) !

Slide 26

Slide 26 text

! myth 3 mock makes tests better

Slide 27

Slide 27 text

mock helps you isolate tests to just one layer ! right?

Slide 28

Slide 28 text

def authenticate(username): """Return a True if the user is authenticated """ # ... return True ! ! def login(username): if not authenticate(self.params["username"]): return Response(status=401) ! # ... do more stuff ... ! return Response(status=200) !

Slide 29

Slide 29 text

def test_login_returns_200_on_success(self): with mock.patch(app, "authenticate") as auth: auth.return_value = True ! response = app.login("dcrosta") ! self.assertEqual(200, response.status) ! ! def test_login_returns_401_on_failure(self): with mock.patch(app, "authenticate") as auth: auth.return_value = False ! response = app.login("dcrosta") ! self.assertEqual(401, response.status)

Slide 30

Slide 30 text

def authenticate(username): """Return a tuple of (authenticated, user_roles) """ # ... return (True, ["admin"]) ! ! def login(username): if not authenticate(self.params["username"]): return Response(status=401) ! # ... do more stuff ... ! return Response(status=200) !

Slide 31

Slide 31 text

why might my test pass when it really should fail? !

Slide 32

Slide 32 text

use verified doubles

Slide 33

Slide 33 text

class StubDB(object): ! def __init__(self): self.users = {} ! def register_user(self, username, roles): self.users[username] = roles ! def authenticate(self, username): if username not in self.users: return (False, []) return (True, self.users[username]) ! ! !

Slide 34

Slide 34 text

def test_login_returns_200_on_success(self): db = StubDB() db.register_user("dcrosta", ["admin"]) ! app = MyApp(db) response = app.login("dcrosta") ! self.assertEqual(200, response.status) ! def test_login_returns_401_on_failure(self): db = StubDB() ! app = MyApp(db) response = app.login("dcrosta") ! self.assertEqual(401, response.status)

Slide 35

Slide 35 text

http://gingersnapworks.com/wp-content/uploads/2012/12/facebook_like-hang-tag.jpg library authors: ship test doubles
 with your packages

Slide 36

Slide 36 text

tests can smell, too !

Slide 37

Slide 37 text

design your tests !

Slide 38

Slide 38 text

good tests are your best friends

Slide 39

Slide 39 text

good test bad test ! ! ! ! ! dan crosta http://late.am/ @lazlofruvous magnetic