On Tue, Jul 21, 2009 at 8:15 AM, Barun Singh<baru...@gmail.com> wrote: > I completely agree that testability is a huge benefit, and if changing code > structure makes that easier then I'm all for it. My statement was only > meant to imply that I didn't find testability to become any easier by > refactoring this particular method, and absent that I didn't see any reason > to split it up. But if testability is actually improved, I'd be eager to > make changes appropriately. > > I looked over your code in the gist (which, by the way I really appreciate > you taking the time to write out), but I don't see how this tests the method > properly. The result of the else condition is never actually tested. > Specifically, I thing the tests would all pass, even if > find_or_add_something looked like this: > > def self.find_or_add_something(inputs) > if x = self.find_something(inputs) > x > else > self.add_something(inputs) > "blah" > end > end > (if it isn't clear, the difference is that I'm not calling find_something in > the else condition and am instead just returning "blah"). > > This is because the last test "#find_or_add_something when find something > returns nil returns the result of find_something" doesn't actually specify > that find_something returns nil the first time it is called. So that last > spec describes what I want to test but what it actually tests is the same > thing as "#find_or_add_something when find something returns a Thing returns > the Thing" [line 39]. > > At least this is what it seems like to me; if I'm missing something > glaringly obvious (possible) please do point it out to me. > > Hopefully with this concrete example of a set of specs that you've provided > my question is a bit clearer.
I changed the very last example in the gist so that the first time it receives find_something it returns nil, then the 2nd time it returns the thing. Try again: http://gist.github.com/151226 > > > > On Tue, Jul 21, 2009 at 5:49 AM, David Chelimsky <dchelim...@gmail.com> > wrote: >> >> 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 > > > _______________________________________________ > 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