Hi Tom, On Fri, May 29, 2009 at 4:11 AM, Tom Stuart <[email protected]> wrote: > Hi, > > One of the problems with mocks, as far as I can tell, is that they might go > out of sync with the real object they're mocking.
Have you read "Mock Roles, not Objects"? http://mockobjects.com/files/mockrolesnotobjects.pdf > Is it possible and sane to > detect this by running each spec against its corresponding mock? Does anyone > already do this? "it's corresponding mock" suggests a single mock object for each real implementation. There's nothing stopping you from writing your own mock objects to play this role. IMO, this is not what a dynamic mock objects framework is for. > For example: your Account object has a particular behavioural specification; > you perhaps have some convenient helper method for creating a mock Account > which conforms to that specification with various stubbed responses; and the > specifications of other objects use that mock Account when describing > behaviours which involve interaction with Accounts. What happens when you > want to change the behaviour of Account? Naively, you update the > specification and implementation of Account, your specs all pass, and you > think you're done, except you're not: the Account mock is now > misrepresenting the behaviour of Account all over the system, with the > result that you've got failures that the specs don't reveal. First of all, let's make a distinction between behaviour and method signatures. Just because two objects sport the same methods doesn't mean they behave the same way. In order to verify that a mock Account honors the same contract as a real Account, you'd have to have some very general specs like "Account balance should be an instance of the Money class." This would not really get you very far in terms of specifying the real account, and it's exactly the opposite of what we're getting at with BDD - focus on what an object does (behaviour), not what it is (structure). So really, what we're talking about is concern over method signatures straying. Theoretically, this could be solved with some audit mechanism, but then we're tying mocks to specific objects. What about when we use mocks in their most powerful way, to specify an object's contract with polymorphic collaborators? Consider the case of an Account in a banking application. Checking accounts and savings accounts have some significant differences in terms of their behaviour, yet a Transfer will probably only use the parts that are common to both: def transfer(source_account, target_account, amount) source_account.debit(amount) target_account.credit(amount) end an admittedly naive implementation, but it demonstrates the point. The source account may be a checking account. It may be a savings account. It may be a checking account with overdraw privileges. It may be a checking account with overdraw privileges that draw on the target account. Etc, etc. What mocks/stubs let us do here is set up the context for each example exactly how we want it to be, with objects that respond how we program them to, but aren't necessarily of a specific class. > Of course you have integration tests that will show these failures at a > higher level but it doesn't feel fundamentally like a problem that RSpec has > no business solving. My personal opinion is that RSpec has no business solving this :) Personal opinion aside, let's say we set out to solve this. We'd need some auditing mechanism that says the object being mocked has all the same APIs as the mock object. For RSpec, or any other Ruby framework, we have a serious obstacle to being able to do this universally and reliably: Ruby We're dealing with a language that lets us modify an object's behaviour on the fly. This means that it is entirely possible (and quite common) for a given method not to exist after a class definition is loaded, but to be added later in the process via a dynamic method definition or a mixin. If the point at which we do the audit, that method has not yet been added to the object, we're screwed. Would that be a problem all the time? Certainly not. But it means we'd be depending on something that is not very reliable and/or restricting our use of Ruby's most powerful features. > Why not establish a base case for RSpec's inductive > demonstration of correctness by running the Account spec against the > standard Account mock? That way you'll automatically be told, by RSpec, > whenever the actual behaviour of your mock doesn't match the specified > behaviour of the mocked object, and you can let your integration tests > concentrate on the hard stuff (stateful interactions between many objects) > instead of checking that you haven't forgotten to keep your specs > up-to-date. > > This assumes a priori that it makes engineering sense to have a > globally-available DRY mock helper for each object (i.e. class) rather than > building up your mocks piecemeal in every spec; I've found that I always > start off a project by doing the latter but end up refactoring into the > former once I get sick of mocking the same stuff over and over again. Maybe > that's the wrong way to do things altogether, or maybe this is a process > issue that I've misunderstood (i.e. the answer is always to use integration > tests to detect this kind of failure), so stop me if I'm being stupid. The most powerful use of mocks is as a design tool, allowing you to focus on the object at hand and invent its collaborators as you go, without having to go out and develop them just yet. As you suggest above, even in your own process, you tend to build up mocks piecemeal and then refactor towards a single, global helper to create a mock Account (for example). This lets you keep focus on the task at hand, and refactor to eliminate duplication as the duplication appears. Seems like a perfectly viable approach to me. Of course that doesn't solve the auditing problem I describe above, and it also pushes you towards a one to one mapping between mocked object and mock object, which reduces the power of mocks in terms of polymorphism. So that's my 1.8 cents (recession, and all). Looking forward to some other opinions. Cheers, David > Cheers, > -Tom _______________________________________________ rspec-users mailing list [email protected] http://rubyforge.org/mailman/listinfo/rspec-users
