Sustainable way of testing your code by Eugene Amirov Teamlead at - - PowerPoint PPT Presentation

sustainable way of testing your code
SMART_READER_LITE
LIVE PREVIEW

Sustainable way of testing your code by Eugene Amirov Teamlead at - - PowerPoint PPT Presentation

Sustainable way of testing your code by Eugene Amirov Teamlead at Scrapinghub For top 100 most starred Python projects on GitHub the percentage of testing code is a little bit more that 23%. Percentage of tests Lines of code in tests def


slide-1
SLIDE 1
slide-2
SLIDE 2

Sustainable way of testing your code

by Eugene Amirov

Teamlead at Scrapinghub

slide-3
SLIDE 3

For top 100 most starred Python projects on GitHub the percentage of testing code is a little bit more that 23%.

slide-4
SLIDE 4

Percentage of tests

slide-5
SLIDE 5

Lines of code in tests

slide-6
SLIDE 6

def test_adding_same_dsn_multiple_times(self): logger = Mock() logger.handlers = [] logger.addHandler = Mock(wraps=self.logger.handlers.append) dsn = 'http://user:pass@test/1' handler1 = register_sentry_logging(dsn, logger) self.assertIn(handler1, logger.handlers) handler2 = register_sentry_logging(dsn, logger) self.assertIsNone(handler2) self.assertEqual(len(logger.handlers), 1)

slide-7
SLIDE 7

Initial condition: We have some prepared environment which we know to be true

slide-8
SLIDE 8

Initial condition: We have some prepared environment which we know to be true The event: In this environment we execute some action that we want to test

slide-9
SLIDE 9

Initial condition: We have some prepared environment which we know to be true The event: In this environment we execute some action that we want to test Expected outcome: We expect some particular results of this action

slide-10
SLIDE 10

Given a customer previously bought a black sweater from me And I currently have three black sweaters left in stock When he returns the sweater for a refund Then I should have four black sweaters in stock

slide-11
SLIDE 11

def test_adding_same_dsn_multiple_times(self): logger = Mock() logger.handlers = [] logger.addHandler = Mock(wraps=self.logger.handlers.append) dsn = 'http://user:pass@test/1' handler1 = register_sentry_logging(dsn, logger) self.assertIn(handler1, logger.handlers) handler2 = register_sentry_logging(dsn, logger) self.assertIsNone(handler2) self.assertEqual(len(logger.handlers), 1)

slide-12
SLIDE 12

environment

def test_adding_same_dsn_multiple_times(self): logger = Mock() logger.handlers = [] logger.addHandler = Mock(wraps=logger.handlers.append) self.given_logger() dsn = 'http://user:pass@test/1' handler1 = register_sentry_logging(dsn, logger) self.assertIn(handler1, logger.handlers) handler2 = register_sentry_logging(dsn, logger) self.assertIsNone(handler2) self.assertEqual(len(logger.handlers), 1)

  • +
slide-13
SLIDE 13

action

def test_adding_same_dsn_multiple_times(self): self.given_logger() dsn = 'http://user:pass@test/1' handler1 = register_sentry_logging(dsn, logger) self.when_handler_is_registered('http://user:pass@test/1') self.assertIn(handler1, logger.handlers) handler2 = register_sentry_logging(dsn, logger) self.when_handler_is_registered('http://user:pass@test/1') self.assertIsNone(handler2) self.assertEqual(len(logger.handlers), 1)

  • +
  • +
slide-14
SLIDE 14

expectation

def test_adding_same_dsn_multiple_times(self): self.given_logger() self.when_handler_is_registered('http://user:pass@test/1') self.assertIn(handler1, logger.handlers) self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.when_handler_is_registered('http://user:pass@test/1') self.assertIsNone(handler2) self.assertEqual(len(logger.handlers), 1) self.then_n_sentry_handlers_registered(1)

  • +
  • +
slide-15
SLIDE 15

def test_adding_same_dsn_multiple_times(self): self.given_logger() self.when_handler_is_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.when_handler_is_registered('http://user:pass@test/1') self.then_n_sentry_handlers_registered(1)

slide-16
SLIDE 16

def test_sentry_logging_handler(self): self.given_logger() self.when_handler_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.then_n_sentry_handlers_registered(1) def test_adding_same_dsn_multiple_times(self): self.given_logger() self.given_handler_registered('http://user:pass@test/1') self.when_handler_is_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.then_n_sentry_handlers_registered(1)

slide-17
SLIDE 17

def test_sentry_logging_handler(self): self.given_logger() self.when_handler_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.then_n_sentry_handlers_registered(1) self.then_sentry_handlers_are_unique() def test_adding_same_dsn_multiple_times(self): self.given_logger() self.given_handler_registered('http://user:pass@test/1') self.when_handler_is_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.then_n_sentry_handlers_registered(1) self.then_sentry_handlers_are_unique()

  • +
  • +
slide-18
SLIDE 18

def setUp(self): super().setUp() self.given_logger() def test_sentry_logging_handler(self): self.given_logger() self.when_handler_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.then_sentry_handlers_are_unique() def test_adding_same_dsn_multiple_times(self): self.given_logger() self.given_handler_registered('http://user:pass@test/1') self.when_handler_is_registered('http://user:pass@test/1') self.then_sentry_dsn_is_registered('http://user:pass@test/1') self.then_sentry_handlers_are_unique() + + +

slide-19
SLIDE 19
slide-20
SLIDE 20

class Aquarium(object): ... def is_habitable_by(self, fish): ... class MarineAquarium(Aquarium): ... class FreshwaterAquarium(Aquarium): ... class DutchAquarium(FreshwaterAquarium): ...

slide-21
SLIDE 21

def test_habitability(self): self.given_aquarium(FreshwaterAquarium) self.when_checking_habitability_for(Guppi) self.then_aquarium_is_habitable() self.when_checking_habitability_for(Rasbora) self.then_aquarium_is_habitable() self.when_checking_habitability_for(Goldfish) self.then_aquarium_is_habitable() self.when_checking_habitability_for(Leopoldi) self.then_aquarium_is_habitable()

slide-22
SLIDE 22

def test_habitability(self): self.given_aquarium(FreshwaterAquarium) for fish in Guppi, Rasbora, Goldfish, Leopoldi: self.when_checking_habitability_for(fish) self.then_aquarium_is_habitable()

slide-23
SLIDE 23

def test_habitability(self): self.given_aquarium(FreshwaterAquarium) for fish in Guppi, Rasbora, Goldfish, Leopoldi: self.when_checking_habitability_for(fish) self.then_aquarium_is_habitable() def test_habitability(self): for fish in Guppi, Rasbora, Goldfish, Leopoldi: self._test_habitability(fish) def _test_habitability(self, fish): self.given_aquarium(FreshwaterAquarium) self.when_checking_habitability_for(fish) self.then_aquarium_is_habitable()

slide-24
SLIDE 24

from nose_parameterized import parameterized, param ... class TestFreshwaterAquarium(BaseTestCase): @parameterized.expand([ param(Guppi), param(Rasbora), param(Goldfish), param(Leopoldi), ]) def test_habitability(self, fish): self.given_aquarium(FreshwaterAquarium) self.when_checking_habitability_for(fish) self.then_aquarium_is_habitable()

slide-25
SLIDE 25

from nose_parameterized import parameterized, param ... class TestFreshwaterAquarium(BaseTestCase): @parameterized.expand([ param(Guppi), param(Rasbora), param(Goldfish), param(Leopoldi), ]) def test_habitability(self, fish): self.given_aquarium(FreshwaterAquarium) self.when_checking_habitability_for(fish) self.then_aquarium_is_habitable() class TestDutchAquarium(TestFreshwaterAquarium): @parameterized.expand([ param(Guppi), param(Gourami), param(Goldfish), ]) def test_habitability(self, fish): self.given_aquarium(FreshwaterAquarium) self.when_checking_habitability_for(fish) self.then_aquarium_is_habitable()

slide-26
SLIDE 26

$ python -m unittest -v test.py test_habitability_0 (TestFreshwaterAquarium, guppi) ... ok test_habitability_1 (TestFreshwaterAquarium, rasbora) ... ok test_habitability_2 (TestFreshwaterAquarium, goldfish) ... ok test_habitability_3 (TestFreshwaterAquarium, leopoldi) ... ok test_habitability_0 (TestDutchAquarium, guppi) ... ok test_habitability_1 (TestDutchAquarium, gourami) ... ok test_habitability_2 (TestDutchAquarium, goldfish) ... ok test_habitability_3 (TestDutchAquarium, leopoldi) ... FAIL

slide-27
SLIDE 27

Goals

Parameterized tests

slide-28
SLIDE 28

Goals

Parameterized tests Inherited tests data

slide-29
SLIDE 29

Goals

Parameterized tests Inherited tests data No test repetition

slide-30
SLIDE 30

Goals

Parameterized tests Inherited tests data No test repetition Controlled execution

slide-31
SLIDE 31

Requirements

Apply one test method to many data Access to parent class Exclude data

slide-32
SLIDE 32

Tools / approaches

slide-33
SLIDE 33

decorator

slide-34
SLIDE 34

decorator

def new_function(*args, **kwargs): ... result = original_function(*args, **kwargs) ... return result

slide-35
SLIDE 35

decorator

def new_function(*args, **kwargs): ... result = original_function(*args, **kwargs) ... return result

  • riginal function -> anything
slide-36
SLIDE 36

decorator

def new_function(*args, **kwargs): ... result = original_function(*args, **kwargs) ... return result

  • riginal function -> anything
  • riginal class -> anything
slide-37
SLIDE 37

@parameterize_test_case class SomeTestCase(unittest.TestCase): ... @parameterize_test(... data ...) def test_something(self, param1, param2, ...) ...

slide-38
SLIDE 38

@parameterize_test_case class SomeTestCase(unittest.TestCase): ... @parameterize_test(... data ...) def test_something(self, param1, param2, ...) ... @parameterize_test_case class SomeTestCase(unittest.TestCase): ... @parameterize_test(... data ...) def test_something(self, param1, param2, ...) ...

slide-39
SLIDE 39

metaclass

slide-40
SLIDE 40

metaclass

class metacls(type): def __new__(mcs, name, bases, dict): dict.update(...) return type.__new__(mcs, name, bases, dict)

(name, bases, namespace) -> class

slide-41
SLIDE 41

metaclass

class metacls(type): def __new__(mcs, name, bases, dict): dict.update(...) return type.__new__(mcs, name, bases, dict)

(name, bases, namespace) -> class (name, bases, namespace) -> anything

slide-42
SLIDE 42

frames

slide-43
SLIDE 43

frames

Traceback (most recent call last): File "example.py", line 11, in sys.exit(main()) File "example.py", line 8, in main module.function() File "/Users/allactaga/Development/Sources/Miscellaneous/grandson/package/module.py", line 4, in function 1 / 0 ZeroDivisionError: division by zero

slide-44
SLIDE 44

from nose_parameterized import parameterized, param ... class TestFreshwaterAquarium(BaseTestCase): @parameterized.expand([ param(Guppi), param(Rasbora), param(Goldfish), ]) def test_habitability(self, fish): ...

slide-45
SLIDE 45

from nose_parameterized import parameterized, param ... class TestFreshwaterAquarium(BaseTestCase): @parameterized.expand([ param(Guppi), param(Rasbora), param(Goldfish), ]) def test_habitability(self, fish): ... class TestFreshwaterAquarium(BaseTestCase): def test_habitability_0(self, Guppi): ... def test_habitability_1(self, Rasbora): ... def test_habitability_2(self, Goldfish): ... @nottest def test_habitability(self, fish): ...

slide-46
SLIDE 46

custom loader

test case classes -> test suites

slide-47
SLIDE 47

def loadTestsFromTestCase(self, testCaseClass): .... testCaseNames = self.getTestCaseNames(testCaseClass) ... for name in testCaseNames: method = getattr(testCaseClass, name) if self.is_parametrized(method): suites.extend(self.create_test_cases_from(testCaseClass, method) else: suites.append(testCaseClass(name)) return self.suiteClass(suites)

slide-48
SLIDE 48

Skipping inherired data (controlled execution)

slide-49
SLIDE 49

Skipping inherired data (controlled execution)

@parameterized.extend @parameterized.remove @parameterized.replace

slide-50
SLIDE 50

Skipping inherired data (controlled execution)

@parameterized.extend @parameterized.remove @parameterized.replace ... def test_habitability(self, fish): self.assume_is_not_stingray(fish) ... def assume_is_not_stingray(self, fish) if fish.name in ['leopoldi']: self.skipTest("{} is a stingray")

slide-51
SLIDE 51
slide-52
SLIDE 52

git and github

slide-53
SLIDE 53

git and github svn

slide-54
SLIDE 54

git and github svn dated folders (_my_code_2006_05_19_)

slide-55
SLIDE 55

git and github svn dated folders (_my_code_2006_05_19_) changing code live on production server

slide-56
SLIDE 56

Thank you!