1
Mutation Testing in Python
Austin Bingham
@austin_bingham
@sixty_north
Sunday, October 4, 15
Mutation Testing in Python Austin Bingham @austin_bingham - - PowerPoint PPT Presentation
Mutation Testing in Python Austin Bingham @austin_bingham @sixty_north 1 Sunday, October 4, 15 2 Sunday, October 4, 15 3 Sunday, October 4, 15 Mutation Testing 4 Sunday, October 4, 15 What is mutation testing? Code under test + test
1
Austin Bingham
@austin_bingham
@sixty_north
Sunday, October 4, 152
Sunday, October 4, 153
Sunday, October 4, 154
5
A nested loop of mutation and testing
6
Sunday, October 4, 157
Tests properly detected the mutation.
Mutation produced code which is inherently fmawed.
Tests failed to detect the mutant! Tests are inadequate for detecting defects in necessary code either Mutated code is extraneous
8
Sunday, October 4, 15Do my tests meaningfully cover my code's functionality
versus
9
Sunday, October 4, 15Survivors can indicate code which is no longer necessary
10
Sunday, October 4, 1511
Replace relational operator
x > 1 x < 1
break/continue replacement
break continue
Sunday, October 4, 15Long test suites, large code bases, and many operators can add up
12
Image credit: John Mainstone (CC BY-SA 3.0)Some incompetent mutants are harder to detect that others
13
Alan Turing (apocryphal)
Sunday, October 4, 15Some mutants have no detectable differences in functionality
14
def consume(iterator, n): """Advance the iterator n-steps ahead. If n is none, consume entirely.""" # Use functions that consume iterators at C speed. if n is None: # feed the entire iterator into a zero-length deque collections.deque(iterator, maxlen=0) else: # advance to the empty slice starting at position n next(islice(iterator, n, n), None)
Sunday, October 4, 1515
Sub-packages and modules are discovered automatically
16
def find_modules(name): module_names = [name] while module_names: module_name = module_names.pop() try: module = importlib.import_module(module_name) yield module if hasattr(module, '__path__'): for _, name, _ in pkgutil.iter_modules(module.__path__): module_names.append('{}.{}'.format(module_name, name)) except Exception: # pylint:disable=broad-except LOG.exception('Unable to import %s', module_name)
Sunday, October 4, 15Support for tests systems are provided by dynamically discovered modules
the _run() method
printable object containing more information
as context
17
cosmic_ray py.test unittest
plugins
my_package my_test_system
Sunday, October 4, 15Standard library abstract syntax tree handling
ast.NodeTransformer
18
Sunday, October 4, 15Operators are responsible for actual AST modifjcation
turn inherits from ast.NodeTransformer
19
+ 1 2
2
Sunday, October 4, 15Converts unary-sub to unary-add
20
class ReverseUnarySub(Operator): def visit_UnaryOp(self, node): if isinstance(node.op, ast.USub): return self.visit_mutation_site(node) else: return node def mutate(self, node): node.op = ast.UAdd() return node
Sunday, October 4, 15Python provides a sophisticated system for performing module imports
Responsible for producing loaders when they recognize a module name
21
Responsible for populating module namespaces on import
A list of finders which are queried in order with module names when import is executed
Sunday, October 4, 15Cosmic Ray implements a custom finder
with ASTs
which are under mutation
22
Sunday, October 4, 15Cosmic Ray implements a custom finder
23
class ASTFinder(MetaPathFinder): def __init__(self, fullname, ast): self._fullname = fullname self._ast = ast def find_spec(self, fullname, path, target=None): if fullname == self._fullname: return ModuleSpec(fullname, ASTLoader(self._ast, fullname)) else: return None
Sunday, October 4, 15Cosmic Ray implements a custom loader
namespace of a new module object
24
Sunday, October 4, 15Cosmic Ray implements a custom loader
25
class ASTLoader: def __init__(self, ast, name): self._ast = ast self._name = name def exec_module(self, mod): exec(compile(self._ast, self._name, 'exec'), mod.__dict__)
Sunday, October 4, 15Mutant isolation with multiple processes
26
cosmic-ray Process 1 Mutant 1 Process 2 Mutant 2 Process 3 Mutant 3
processes
Sunday, October 4, 15Actor model implementation in Python
27
Event loop to drive actors
28
Sunday, October 4, 15Here's how we put all of these pieces together
1.Each test runs in a new process 2.Each run manipulates its location sys.meta_path to inject the correct module
29
Sunday, October 4, 1530
Up to a two line subtitle, generally used to describe the takeaway for the slide
31
Sunday, October 4, 1532
@sixty_north
Austin Bingham
@austin_bingham
Sunday, October 4, 15