Greg Ditrick
2010-Jul-15 17:18 UTC
[rspec-users] Questions about testing a module method that randomly creates stuff from list.
All, I have a method in a shared Module that shuffles data and then output other data. The method is somewhat large (maybe 50 lines) and sometimes calls other sub methods to help to complete the output which must meet a specific criteria or it re-shuffles. My questions are: 1) How do I capture that a sub method is called? Module.should_recieve(:sub_method) ??? 2) How do I capture the rand number that caused a condition (like the :sub_method being called, an Error raised, etc.). I know srand will give me the seed that caused it. But, how do I capture it on the failure? For example: true.should_not be_true || File.open(?capture_rand.out?,?a?) { |f| f.puts ?#{srand}?} Can you do an || (or) statement like that and have it capture the seed and fail the example? Any ideas? Any other ways of capturing data like that? After I capture the seeds, I?m thinking I could than use those seeds to fine tune specific tests. Thanks, GregD
GregD
2010-Jul-15 17:26 UTC
[rspec-users] Questions about testing a module method that randomly creates stuff from list.
All, I have a method in a shared Module that shuffles data and then output other data. The method is somewhat large (maybe 50 lines) and sometimes calls other sub methods to help to complete the output which must meet a specific criteria or it re-shuffles. My questions are: 1) How do I capture that a sub method is called? Module.should_recieve(:sub_method) ??? 2) How do I capture the rand number that caused a condition (like the :sub_method being called, an Error raised, etc.). I know srand will give me the seed that caused it. But, how do I capture it on the failure? For example: true.should_not be_true || File.open(?capture_rand.out?,?a?) { |f| f.puts ?#{srand}?} Can you do an || (or) statement like that and have it capture the seed and fail the example? Any ideas? Any other ways of capturing data like that? After I capture the seeds, I?m thinking I could than use those seeds to fine tune specific tests. Thanks, GregD
David Chelimsky
2010-Jul-16 11:33 UTC
[rspec-users] Questions about testing a module method that randomly creates stuff from list.
On Jul 15, 2010, at 12:18 PM, Greg Ditrick wrote:> All, > > I have a method in a shared Module that shuffles data and then output other data. The method is somewhat large (maybe 50 lines) and sometimes calls other sub methods to help to complete the output which must meet a specific criteria or it re-shuffles.This sounds like a long, procedural method. Can it be broken down any further (i.e. delegating more of its work to other methods)?> My questions are:I''ll try to answer them, but I don''t have much to go on. If you want more specific answers, we''ll need to see the method in question.> 1) How do I capture that a sub method is called? Module.should_recieve(:sub_method) ???I try to avoid message expectations on the same object that I''m spec''ing. That said, here''s how you can do it: object = Object.new.extend(MySharedModule) object.should_receive(:sub_method) object.super_method The problem with this approach, especially in light of what sounds like a very complex method, is that the real sub_method is not called, so you have to set up the proper return value: object.should_receive(:sub_method).and_return(:a_value_that_super_method_can_work_with) object.super_method If the real sub_method sets any internal state (i.e. assigns values to instance variables), then this won''t work at all.> 2) How do I capture the rand number that caused a condition (like the :sub_method being called, an Error raised, etc.). I know srand will give me the seed that caused it. But, how do I capture it on the failure? For example: > true.should_not be_true || File.open(?capture_rand.out?,?a?) { |f| f.puts ?#{srand}?} > Can you do an || (or) statement like that and have it capture the seed and fail the example? Any ideas? Any other ways of capturing data like that?It''s much easier to control state in an example, rather than inspect state and set different expectations based on it. Consider a simulation of a game that involves a die: die = double(''die'') die.stub(:roll).and_return(2) board.set(piece).at(30) board.roll(die) board.square_at(32).should contain(piece) Make sense? HTH, David> After I capture the seeds, I?m thinking I could than use those seeds to fine tune specific tests. > > > Thanks, > > GregD > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Greg Ditrick
2010-Jul-16 16:38 UTC
[rspec-users] Questions about testing a module method that randomly creates stuff from list.
---- David Chelimsky <dchelimsky at gmail.com> wrote:> On Jul 15, 2010, at 12:18 PM, Greg Ditrick wrote: > > > This sounds like a long, procedural method. Can it be broken down any further (i.e. delegating more of its work to other methods)?Yes, it is. The sub methods are the broken down part and not overridden super methods like I guess I made it sound based on your answers below.> I try to avoid message expectations on the same object that I''m spec''ing. That said, here''s how you can do it: > > object = Object.new.extend(MySharedModule) > > object.should_receive(:sub_method) > object.super_method > > The problem with this approach, especially in light of what sounds like a very complex method, is that the real sub_method is not called, so you have to set up the proper return value: > > object.should_receive(:sub_method).and_return(:a_value_that_super_method_can_work_with) > object.super_method > > If the real sub_method sets any internal state (i.e. assigns values to instance variables), then this won''t work at all.Well, they don''t assign instance variables. They are really just methods handling conditions of the randomness in the parent method.> It''s much easier to control state in an example, rather than inspect state and set different expectations based on it. Consider a simulation of a game that involves a die: > > die = double(''die'') > die.stub(:roll).and_return(2) > > board.set(piece).at(30) > board.roll(die) > board.square_at(32).should contain(piece) > > Make sense?Funny you used die when this problem is for a game. Yes, I can stub out the conditions that each child method handles. This procedural method has grown because of the permutations increase exponentially with more data. But, the conditions to handle (defined in the child methods) have become finite and all I have to do is test those with mocks and stubs. I know I put the cart before the horse in TDD/BDD, but this was a very difficult problem to solve: Create a random tournament layout of unique teams of players where a team is matched with another random team of different players and every player plays on teams made up of all the other players to complete the tournament. This way you find the best individual player. Example of this would be euchre, bridge, other team playing card games, etc. But not limited to card games and not limited to 2 man teams. Also, it must allow for uneven number of players in the tournament. i.e. players would have byes throughout the tournament and those byes would be evenly distributed throughout the tournament. For example: a tournament of 2 players per team and 2 teams per match made up of say 15 players would have all players have had the same number of byes at the end of every 5th round. I actually wrote this 20 years ago in ANSI C. Now I have written it in ruby. It has been just about as hard to do, but less lines of code, cleaner and runs faster mainly because in the C code evaluated each team as it was searching for a team to place into a round where is the ruby code it was simple to remove and shrink the possible opponents with one line of code. :) So, while the C code would take maybe hours to figure out a layout of 200+ euchre players. The ruby code does it in a few minutes on the same machine. Not that you would every have that large of a tournament. Just out of curiosity, I tried the a very large size tournament to compare the timing. I do have one more question, how would you write a test for a recursive method? I have a couple of those too. Thanks for the ideas, GregD
David Chelimsky
2010-Jul-17 00:21 UTC
[rspec-users] Questions about testing a module method that randomly creates stuff from list.
On Jul 16, 2010, at 11:38 AM, Greg Ditrick wrote:> ---- David Chelimsky <dchelimsky at gmail.com> wrote: >> On Jul 15, 2010, at 12:18 PM, Greg Ditrick wrote: >> >> >> This sounds like a long, procedural method. Can it be broken down any further (i.e. delegating more of its work to other methods)? > > Yes, it is. The sub methods are the broken down part and not overridden super methods like I guess I made it sound based on your answers below. > > >> I try to avoid message expectations on the same object that I''m spec''ing. That said, here''s how you can do it: >> >> object = Object.new.extend(MySharedModule) >> >> object.should_receive(:sub_method) >> object.super_method >> >> The problem with this approach, especially in light of what sounds like a very complex method, is that the real sub_method is not called, so you have to set up the proper return value: >> >> object.should_receive(:sub_method).and_return(:a_value_that_super_method_can_work_with) >> object.super_method >> >> If the real sub_method sets any internal state (i.e. assigns values to instance variables), then this won''t work at all. > > Well, they don''t assign instance variables. They are really just methods handling conditions of the randomness in the parent method. > > >> It''s much easier to control state in an example, rather than inspect state and set different expectations based on it. Consider a simulation of a game that involves a die: >> >> die = double(''die'') >> die.stub(:roll).and_return(2) >> >> board.set(piece).at(30) >> board.roll(die) >> board.square_at(32).should contain(piece) >> >> Make sense? > > Funny you used die when this problem is for a game. > > Yes, I can stub out the conditions that each child method handles. This procedural method has grown because of the permutations increase exponentially with more data. But, the conditions to handle (defined in the child methods) have become finite and all I have to do is test those with mocks and stubs.Sounds like you''ve got a lot of conditional logic that might be better expressed with polymorphism? I''d need to see the method, but if you expressed the conditions in different objects rather than child methods, you might end up with smaller bits that are easier to test in isolation.> I know I put the cart before the horse in TDD/BDD, but this was a very difficult problem to solve:We all put the cart before the horse from time to time, but your "but" suggests that you believe the TDD is only for easy problems. That couldn''t be farther from the truth. The whole point is to start off with a small bit functionality, drive it out with tests, and slowly build up the complexity.> Create a random tournament layout of unique teams of players where a team is matched with another random team of different players and every player plays on teams made up of all the other players to complete the tournament. This way you find the best individual player. Example of this would be euchre, bridge, other team playing card games, etc. But not limited to card games and not limited to 2 man teams. Also, it must allow for uneven number of players in the tournament. i.e. players would have byes throughout the tournament and those byes would be evenly distributed throughout the tournament. For example: a tournament of 2 players per team and 2 teams per match made up of say 15 players would have all players have had the same number of byes at the end of every 5th round. > > I actually wrote this 20 years ago in ANSI C. Now I have written it in ruby. It has been just about as hard to do, but less lines of code, cleaner and runs faster mainly because in the C code evaluated each team as it was searching for a team to place into a round where is the ruby code it was simple to remove and shrink the possible opponents with one line of code. :) So, while the C code would take maybe hours to figure out a layout of 200+ euchre players. The ruby code does it in a few minutes on the same machine. Not that you would every have that large of a tournament. Just out of curiosity, I tried the a very large size tournament to compare the timing. > > I do have one more question, how would you write a test for a recursive method? I have a couple of those too.I''ve never found a better way than to treat it as a black box and specify how it should respond to different inputs. HTH, David> > Thanks for the ideas, > > GregD