Hi Barun, On Tue, Jul 21, 2009 at 1:05 AM, Barun Singh<baru...@gmail.com> wrote: > I certainly appreciate the thoughtful and lengthy reply, but I think it > misses the main part of my problem -- perhaps my original explanation of my > issue was lacking in clarity. This isn't a question of refactoring; I can > easily refactor the method as you describe but this doesn't resolve the > issue (indeed, it just leads to an increased number of public methods in the > model with no real benefit).
You want to know your code works, right? Testability _is_ a real benefit. Obviously everything needs to be balanced, and if you can do things in a pure API, non-invasive way, you're going to end up with a more flexible result. But sometimes we have to make tradeoffs. > So, to more clearly illustrate my question, > forget about my original method and instead let's say I have three methods > as follows: > > def self.find_something(inputs) > .. do stuff here > end > > def self.add_something(inputs) > .. do stuff here > end > > def self.find_or_add_something(inputs) > x = self.find_something(inputs) > if !x > self.add_something(inputs) > x = self.find_something(inputs) > end > x > end > > I can easily spec the find_something and add_something methods so I know > that they work correctly. My question is, how do I write a spec for > find_or_add_something in a way where I don't have to re-test the behavior of > the individual find_something and add_something methods a second time, and > can just test the overall logic of that method? This is _very_ invasive and brittle, and I'd likely not approach it this way myself, but it works: http://gist.github.com/151226 HTH, David > > It's helpful to know that Mocha is available to assist with this, but I'd > rather not switch to an entirely different framework for mocks & stubs just > because of this one method... > > > > On Mon, Jul 20, 2009 at 11:45 PM, Stephen Eley <sfe...@gmail.com> wrote: >> >> On Mon, Jul 20, 2009 at 8:33 PM, Barun Singh<baru...@gmail.com> wrote: >> > >> > I want to find a way to write a spec for this method that does both of >> > these >> > things: >> > (1) stub out the do_something method (that method has its own specs) >> > (2) not stub out the logic in the else statement, (there is some >> > complex >> > logic in there involving sql queries on multiple tables and i explicitly >> > want to make it touch the database so that I can examine the state of >> > the >> > database after the method is run in my spec. a lot of complex stubs >> > would >> > be required to do this while also having the spec that is actually >> > useful) >> >> Okay, first off -- I think part of your problem is that your focus is >> too short. It sounds like you're asking how to write different specs >> for various lines of the _internals_ of this method. That's too >> fine-grained. Spec the method from the _outside:_ write specs that >> say "If I give the method these inputs, this should happen." Then >> test that the end result of the method's execution was what you >> expect. >> >> I'm betting that probably sounds really difficult, given the "complex >> logic" you're describing. And that would be my second, more important >> tip: if your method is so complicated that you can't spec it from the >> outside, you should take that as a sign. Spaghetti code is usually >> very hard to spec. >> >> Reconsider your problem. Try breaking it down into smaller, more >> easily specified pieces, and then recompose them into the logic that >> you need. I don't know your business rules; it looks to me like the >> basic task here is "Keep twiddling with the database until I get what >> I'm looking for, as many times as that takes." I don't know why >> that's necessary, but if it is, recursion doesn't seem like the most >> elegant answer to me. Recursive calls are usually made in small >> functions that don't have side effects. This is the opposite. >> >> What would I do instead? Again, without knowing your specific >> business, I'd probably break it into pieces, and then turn the >> requirement inside out and do it with a loop: >> >> def self.find_something(inputs) >> <find something in db based on inputs> >> end >> >> def self.add_something(inputs) >> <add something to db based on inputs> >> end >> >> And then, wherever you *would* have called mymethod(inputs), you can >> instead write this, which will loop zero or more times: >> >> add_something_to_db(inputs) until x = find_something(inputs) >> x.do_something >> >> ...Whether or not you still want to wrap that in a method probably >> depends on whether you need to call it more than once. But I'll bet >> you that the "add_something" and "find_something" methods are both >> easier to spec from the outside. If they're still too complex, then >> break them down too. >> >> >> Oh, and final trivia note: >> >> > any thoughts on how to accomplish this? two approaches that would make >> > sense but don't seem to be possible as far as i know are: >> > (A) stub out the do_something method for any instance of the class (i >> > can't >> > stub it out for a particular record because the records don't exist at >> > the >> > time of calling the method and I'm not stubbing the creation process) >> > (B) stub out "mymethod" only for the second time it is called >> > (obviously i >> > can't stub the method out entirely since that's the method i'm testing) >> >> You can do both of these with the Mocha mocking library, if you really >> want to. It puts an "any_instance" method on Class that you can use >> to mock or stub; and you can specify ordered chains of results to >> return. >> >> But again, you shouldn't need to if your methods are atomic enough. >> If 'do_something' properly belongs outside the scope of what you're >> testing, then maybe that's a sign that you shouldn't put it in the >> same block of code. And if you find a need to declare that your >> method should behave entirely differently on a second invocation, >> maybe it shouldn't be one method. >> >> Simplify relentlessly. Once I figured out how to make my code >> simpler, I found that I wasn't mocking nearly as much as I used to. >> >> >> -- >> Have Fun, >> Steve Eley (sfe...@gmail.com) >> ESCAPE POD - The Science Fiction Podcast Magazine >> http://www.escapepod.org >> _______________________________________________ >> rspec-users mailing list >> rspec-users@rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users > > > _______________________________________________ > rspec-users mailing list > rspec-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users > _______________________________________________ rspec-users mailing list rspec-users@rubyforge.org http://rubyforge.org/mailman/listinfo/rspec-users