1 / 31

dbcbet : Object-Oriented Design by Contract with Declarative Bounded Exhaustive Testing

dbcbet : Object-Oriented Design by Contract with Declarative Bounded Exhaustive Testing. Christopher Coakley and Peter Cappello. Overview. Background Design by Contract Bounded Exhaustive Testing ( Korat ) dbcbet Object Oriented Design by Contract

mary
Download Presentation

dbcbet : Object-Oriented Design by Contract with Declarative Bounded Exhaustive Testing

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. dbcbet: Object-Oriented Design by Contract with Declarative Bounded Exhaustive Testing Christopher Coakley and Peter Cappello

  2. Overview • Background • Design by Contract • Bounded Exhaustive Testing (Korat) • dbcbet • Object Oriented Design by Contract • Declarative Bounded Exhaustive Testing

  3. Background

  4. Design by Contract • Est. 1986, Bertrand Meyer, Eiffel • Precondition – Violation = bug in caller • Postcondition – Violation = bug in callee • Invariant – Violation = bug in callee • Liskov Substitution Principle • Preconditions may be weakened by subclasses • Postconditions and Invariants may be strengthened by subclasses

  5. Design by Contract • Contracts have native support • Spec#, Clojure, Racket, Eiffel • Contracts can be added via libraries/tools • C++, Java, Python, Ruby • Eiffel made guarantees about exception types, but this is not typically viewed as part of a contract • Contracts are orthogonal to the type system

  6. Bounded Exhaustive Testing • Test a model of the software • TestEra • Alloy • Test the code • Korat – Finitization

  7. dbcbet

  8. Python Decorators for Syntax # Python decorator @foo def bar(): pass # Is equivalent to def bar(): pass bar = foo(bar) # foo can redefine bar

  9. Syntax Example @inv(invariant) class BusMonitor(object): @pre(precondition1) @pre(precondition2) @post(postcondition) @throws(IOError, CustomException) @finitize_method([device(1),device(2)],range(-1,10)) def attach(self, device, priority): …

  10. How it works • First applied component (@pre, @post, @throws, @inv) wraps the method with a contract invoker • Each component creates or appends to a list of preconditions, postconditions, invariants, or throws • Inheritance is managed by @inv or @dbc • Postcondition parameter: old

  11. Invoker def create_invoker(method): """invoker checks all contract components and invokes the method.""" @wraps(method) def invoker(s, *args, **kwargs): check_preconditions(wrapped_method, s, *args, **kwargs) o = old(method, s, args, kwargs) try: ret = method(s, *args, **kwargs) check_postconditions(wrapped_method, s, o, ret, *args, **kwargs) check_invariants(wrapped_method, s, *args, **kwargs) except Exception as ex: if check_throws(wrapped_method, ex, s, *args, **kwargs): raise return ret return invoker

  12. Contract Components are Objects • Predicates can provide custom error messages • Allows for stateful guards • Ex. precondition: can’t call me more than once • State belongs to contract, not guarded object • Composable and Resusable • dbcbet.helpers contains reusable primitives

  13. Finitization • A python dict for classes • { fieldname: [assignments] } • A sequence of sequences for methods • Positional arguments • Syntax: @finitize({'re':xrange(-1,1),'img':[None,-1,0,1]}) @finitize_method(enumerate(Person))

  14. Ported JML Example Complex ComplexOps Rectangular Polar

  15. Some Contract Definitions def real_part_post(self, old, ret): return approx_equal(self._magnitude() * math.cos(self._angle()), ret, tolerance) def angle_post(self, old, ret): return approx_equal(math.atan2(self._imaginary_part(), self._real_part()), ret, tolerance) def arg_not_none(self, b): """This is a custom error message""" return bis not None

  16. Example @dbc class Complex(object): @post(real_part_post) def real_part(self): pass @post(imaginary_part_post) defimaginary_part(self): pass @post(magnitude_post) def magnitude(self): pass @post(angle_post) def angle(self): pass

  17. @dbc class ComplexOps(Complex): @pre(argument_types(Complex)) @post(add_post) @finitize_method(complex_gen) def add(self, b): return Rectangular(self.real_part() + b.real_part(), self.imaginary_part() + b.imaginary_part()) @post(mul_post) @finitize_method(complex_gen()) def mul(self, b): try: return Polar(self.magnitude() * b.magnitude(), self.angle() + b.angle()) except ValueError: return Rectangular(float('nan'))

  18. @inv(polar_invariant) @finitize(finitize_polar) class Polar(ComplexOps): @pre(argument_types(Number, Number)) @finitize_method([-1,0,1], [-math.pi,0,math.pi/4.0,math.pi/2.0]) @throws(ValueError) def __init__(self, mag, angle): if math.isnan(mag): raise ValueError() if mag < 0: mag = -mag; angle += math.pi; self.mag = mag; self.ang = standardize_angle(angle) def _real_part(self): return self.mag * math.cos(self.ang) # specification inherited real_part = _real_part

  19. Helper Examples def immutable(self, old, ret, *args, **kwargs): """Object immutability was violated by the method call (did you forget to override __eq__?)""" return old.self == self # use: @post(immutable)

  20. class argument_types(object): """DBC helper for reusable, simple predicates for argument-type tests used in preconditions""" def __init__(self, *typelist): self.typelist = typelist self.msg = "implementation error in argument_types" def __call__(self, s, *args, **kwargs): for typ, argin zip(self.typelist, args): if not isinstance(arg, typ) and argis not None: self.msg = "argument %s was not of type %s" % (arg, typ.__name__) return False return True def error(self): return self.msg # use: @pre(argument_types(Number, Number))

  21. Testing 42 instances 750 tests real 0m0.286s user 0m0.255s sys 0m0.021s 258 instances 4854 tests real 0m1.376s user 0m1.338s sys 0m0.022s >>> from dbcbet import bet >>> for typ in [Polar, Rectangular]: ... bet(typ).run() Summary: Instance Candidates: 12 Invariant Violations: 0 Method Call Candidates: 180 Precondition Violations: 0 Failures: 0 Successes: 180 Summary: Instance Candidates: 30 Invariant Violations: 0 Method Call Candidates: 570 Precondition Violations: 0 Failures: 0 Successes: 570

  22. How bet.run works • Construct an Object • Class or constructor finitization • For each method, construct an argument list • If the precondition fails, skip • Execute method • Test succeeds if postcondition and invariant hold • Do this for all object * method * argument combinations

  23. Future Work • Rewrite BET code to use object pooling • Makes testing self-referential structures significantly easier • Eliminate helpers like enumerate(Person) • Add metaclass option for contract inheritance

  24. References • Meyer, Bertrand: Design by Contract, Technical Report TR-EI-12/CO, Interactive Software Engineering Inc., 1986 • pyContract: http://www.wayforward.net/pycontract/ • pyDBC: http://www.nongnu.org/pydbc/ • Gary T. Leavens, YoonsikCheon. Design by Contract with JML., 2006 • AleksandarMilicevic, SasaMisailovic, DarkoMarinov, and SarfrazKhurshid. 2007. Korat: A Tool for Generating Structurally Complex Test Inputs. In Proceedings of the 29th international conference on Software Engineering (ICSE '07).

  25. Thank You Code available at: https://github.com/ccoakley/dbcbet

  26. Backup Slides

  27. Existing Contract Libraries • Python • pyContract • pyDBC

  28. pyContract • Contracts are part of documentation strings • PEP 316

  29. def sort(a): """Sort a list *IN PLACE*. pre: # must be a list isinstance(a, list) # all elements must be comparable with all other items forall(range(len(a)), lambda i: forall(range(len(a)), lambda j: (a[i] < a[j]) ^ (a[i] >= a[j]))) post[a]: # length of array is unchanged len(a) == len(__old__.a) # all elements given are still in the array forall(__old__.a, lambda e: __old__.a.count(e) == a.count(e)) # the array is sorted forall([a[i] >= a[i-1] for i in range(1, len(a))]) """

  30. pyDBC • Metaclass based • Metaclasses are inherited properly • pyDBC inheritance works properly (it was fixed) • Separate methods for contracts • non-reusable due to naming requirements

  31. import dbc __metaclass__ = dbc.DBC class Foo: def __invar(self): assert isinstance(self.a, int) def __init__(self, a): self.a = a def foo(self, a): self.a *= a def foo__pre(self, a): assert a > 0 def foo__post(self, rval): assert rval is None

More Related