Testing methods that depend on an essentially random instance variable - ruby

I'm working on a Blackjack game. My Game object contains a deck object that gets shuffled after the deck reaches a certain level of penetration. Many of my methods depend on this deck object. I don't see any reason for the deck object to be accessible through setter methods. I'm having trouble testing the methods on my Game class, since they depend on the order of the deck, which is random.
For instance, I have the deal_hand method.
def deal_hand(player)
reset_deck if #deck.size < 2
player.hands.push(Hand.new(*#deck.pop(2)))
end
How should I test a method like this? I was thinking I could just manually create a Deck object that gets used in the #deck instance variable. Unfortunately, I can't set the instance variable, and I don't really want to add a setter, since there's no reason for that to be "settable" except to test. Should I monkey patch the class from my test-file and add a setter?
As an aside--I mostly write scripts--I decided I needed to start writing tests after this project got out of hand. Is there any canonical resource for "testing patterns?"
edit:
I am using MiniTest, which supports stubbing/mocking. Though as far as I can tell, it only lets you set expected return values for method calls on the mock object. If I made a Mock deck, the actual deck object also depends on an internal array. None of the code that calls the deck accesses the array directly.

Use a mock library. RSpec comes built with one, but I dislike it, so I'll show you what it might look like with Surrogate, the one I wrote:
class Deck
def pop(n) end
def reset() end
def size() end
end
class Game
def initialize(deck)
#deck = deck
end
def deal_hand(player)
reset_deck if #deck.size < 2
player.hands.push(Hand.new(*#deck.pop(2)))
end
def reset_deck
#deck.reset
end
end
Hand = Struct.new :card1, :card2
class Player
def hands
#hands ||= []
end
end
require 'surrogate/rspec'
class MockDeck
Surrogate.endow self
define(:reset)
define(:pop) { |n| n.times.to_a }
define(:size) { 1 }
end
describe Game, 'deal_hand' do
let(:deck) { MockDeck.new }
let(:player) { Player.new }
let(:game) { Game.new deck }
it 'resets the deck if there are less than 2 cards' do
deck.will_have_size 2 # set the return value of deck.size
game.deal_hand player
deck.was_not told_to :reset # assert what happened to the deck
deck.will_have_size 1
game.deal_hand player
deck.was told_to :reset
end
it 'deals the top 2 cards to the player' do
deck.will_pop [:card1, :card2]
game.deal_hand player
deck.was told_to(:pop).with(2)
player.hands.last.should == Hand.new(:card1, :card2)
end
end
describe Deck do
it 'is substitutable for the mock' do
# because we use the mock in tests
# we want to make sure its interface matches the real deck
Deck.should substitute_for MockDeck
end
end

Have you considered using mocha?
That would allow you to stub or mock the Deck to ensure that it has the expected cards for your test runs.

In your test use the method instance_variable_set, which is a ruby method on object.
So I am assuming your method is in the Game class, so you if you're setting up something like
#test_deck = something_that_sets_up_state_of_test_deck
#game = Game.new
#game.instance_variable_set(:deck, #test_deck
That will set your instance variable within Game, without the need for attr_accessible or getters and setters being explicitly built.

Related

Dynamically remove mixin ancestor from class

I am trying to develop a simple card game console application in ruby as a pet project. One of the interactions I would like to add to this card game is something along the lines of "During your turn, you can buy cards as if they cost X less". Now, I thought about modelling this behaviour with the help of mixin decorators. Assuming the following class:
class Card
attr_reader :cost
def initialize(cost)
self.cost = cost
end
def play(*_args)
raise NotImplementedError, "Override me!"
end
private
attr_writer :cost
end
I thought a simple solution would be something like this:
class CardThatDiscounts < Card
def play(*_args)
#mod = Module.new do
def cost
super - 1
end
end
Card.prepend(#mod)
end
end
This would give me the flexibility to target either the class itself, or specific instances of the cards. However, I am unsure of how to reverse this effect, at the end of the turn. I saw a few questions similar to this one, the answers of which said something along the lines of:
Remove the defined method manually.
This approach won't really work for me, since I am decorating existing methods instead of adding new ones. I could maybe rename the existing method when I prepend the module, and then return it back, but what happens in case I want to chain multiple such decorators?
Use gem mixology
Mixology is an old gem, and seems no longer maintained. Though, according to the documentation, it is exactly what I need. However, the implementation mostly deals with ruby internals, that I unfortunately do not understand. Is it not possible to modify a class ancestor chain from within Ruby, or do I need c/Java extension for this?
I considered other alternatives, such as SimpleDelegator or DelegateClass, but both of these require that I instantiate new objects, and then somehow replace references to existing Card objects with these new wrapped objects, and back again, at the end of turn. Which seemed a bit more involved than modifying the ancestry chain directly.
I suppose my question has 2 parts. Is it possible to remove a specific ancestor from a class's ancestor chain using pure ruby (since mixology gem already suggests it is possible to do with c/Java extensions)? If not, what would be a suitable workaround to get a similar behaviour?
What you are trying to achieve is a very bad pattern. You should almost never use prepend or include dynamically, but model your code around the concepts you (and people possibly reading your code) do understand.
What you probably want to do is to create (some kind of) a Delegator called CardAffectedByEnvironment - and then instead of doing Card.new(x), you will always do CardAffectedByEnvironment.new(Card.new(x), env), where env will keep all your state changes, or add a method real_cost that would calculate things based on your cost and environment and use this method.
Below is a code with CardAffectedByEnvironment that would maybe describe better how I would assume it would work:
class Environment
def initialize
#modifiers = []
end
attr_reader :modifiers
def next_round
#modifiers = []
end
def modifiers_for(method)
#modifiers.select { |i| i.first == method }
end
end
class CardAffectedByEnvironment
def initialize(card, env)
#card, #env = card, env
end
# Since Ruby 3.0 you can write `...` instead of `*args, **kwargs, &block`
def method_missing(method, *args, **kwargs, &block)
value = #card.public_send(method, *args, **kwargs, &block)
#env.modifiers_for(method).each do |_, modifier|
value = modifier.call(value)
end
value
end
end
class Joker
def cost
10
end
end
env = Environment.new
card = CardAffectedByEnvironment.new(Joker.new, env)
p card.cost # => 10
env.modifiers << [:cost, ->(i) { i - 1 }]
p card.cost # => 9
env.next_round
p card.cost # => 10

Using a method to generate multiple instances of another class

I am trying to let a method initiate multiple instances of another class.
class Players
def initialize(players)
#players = players
end
def generate_boards
#players.each do |player|
board = BingoBoardGenerator.new
player = BingoBoard.new(player, board.generate)
p player
end
end
end
players = ["Nick","Jiyoon","Mae","Lawson","Matthew"]
plays = Players.new(players)
plays.generate_boards
p player shows that five instances of BingoBoard were created properly, but I am not sure how to access them (or where they are). Any help on how to call these instances? Normally I would do:
nick = BingoBoard.new("Nick", board.generate)
nick.board
but when I instantiate them all together, I don't know how to set/access their instance name.
As indicated by user2864740, you can use :map instead of :each to return an array of BingoBoard instances. If you wanted to store those instances to use later, you can use memoization as shown below. The first time :bingo_board_instances is called, the boards will be generated and the instance variable #bingo_board_instances will be set so that future invocations of :bingo_board_instances will not result in the generation of new boards.
class Players
def initialize(players)
#players = players
end
def generate_boards
#players.map do |player|
board = BingoBoardGenerator.new
BingoBoard.new(player, board.generate)
end
end
def bingo_board_instances
#bingo_board_instances ||= generate_boards
end
end
While the above code works just fine, I think the more intuitive solution would be to have a Player class (instead of Players) and then pass in an array of Player instances when initializing a BingoBoardGenerator. With this approach, you can set an instance variable for each individual player and create a unique board that belongs to the player:
class BingoBoardGenerator
def initialize(args)
#dynamically set instance vars to handle n number of players
args.fetch(:players).each_with_index do |player,index|
instance_variable_set("#player_#{index+1}",player)
end
end
def generate_boards
instance_variables.map do |player|
player = instance_variable_get(instance_var)
#you will need to implement :generate_bingo_board on player...I would suggest using memoization so you can store the player's board for later retrieval
player.generate_bingo_board
end
end
end
#I have no idea what your implementation looks like...
b = BingoBoardGenerator.new(players: [Player.new("Jen"),Player.new("Phil"),Player.new("Mary"),Player.new("Bert")])
b.generate_boards
This would allow you to better encapsulate data that may belong to individual players, including the ability to ask each Player instance for its :board.

Ruby find method callers class

I have classes A and B, each with methods a and b respectively, like below.
class A
def a
#I want to get the object which calls this method
#caller = B
end
end
class B
def initialize
#to_call = A.new
end
def b
#to_call.a
end
end
B.new.b
How can I return B that calls a so that I can use methods of B inside A?
I have a class Board, which Game classes use to play. The board class has a method interact that gets user input either with gets.chomp or STDIN.getc to simulate guessing games or games which use arrow keys. The game class calls the interact method to begin playing the game, and sends it a block that handles the way the game is played. Each game has its own set of rules, therefore each game class has a method that displays its rule book to the user. Within interact, when the user enters "-rules", I want the board class to return the class that called its interact method and store it in a variable caller. With the caller variable defined, I want to use caller.rule_book to display the rules of the game class that called the board's interact method.
I don't know a way you you could do it, besides using the source's location (caller_locations), but I'd advise not to follow this path. Too much indirection often leads to code extremely hard to debug. It's much simpler and readable to simply pass the context (in this case, self, or the properties needed for the computation) as an argument:
class A
def initialize(context)
#context = context
end
def a
# Do something with #context
end
end
class B
def initialize
#to_call = A.new(self)
end
def b
#to_call.a
end
end
B.new.b

Lazy object in ruby

How can I create an opbjet that's totally lazy by itself? I have a block, and I want to pass around (as a dependency) the "current value" (at call time) of the block instead of the value at dependency injection time.
I can't actually pass around a lambda because all the services expect an actual object, so they won't send :call to them, just access them.
This (oversimplified) example might clarify the situation:
class Timer
def initialize(current_time)
#current_time = current_time
end
def print_current_time
print #current_time
end
end
class Injector
def current_time
# a lazy object that when accessed actually calls the lambda below
# every single time.
end
def current_time_lazy
-> { Time.now }
end
def instantiate(class_name)
# search for the class, look at the constructor and
# create an instance with the dependencies injected by
# name
# but to be simple
if class_name == "Timer"
Timer.new(current_time)
end
end
end
timer = Injector.new.instantiate("Timer")
timer.print_current_time # => some time
sleep 2
timer.print_current_time # => some *different* time
The actual situation implies passing around the current_user but depending on the situation the current user might change after those values are injected.
I would really appreciate any suggestion (even if for now I will carefully sort the dependency injection code so this doesn't happen, but I think it's pretty fragile)
This should help :
class Timer
def initialize(current_time)
#current_time = current_time
end
def print_current_time
puts #current_time
end
end
class LazyMaker < BasicObject
def self.instantiate(class_name, lambada)
if class_name == 'Timer'
::Timer.new(new(class_name, lambada))
end
end
def initialize(class_name, lambada)
#lambada = lambada
end
def method_missing(method, *args)
#lambada.call.send(method, *args)
end
end
timer = LazyMaker.instantiate('Timer', -> { Time.now })
timer.print_current_time # some time
sleep 2
timer.print_current_time # some other time
I'm trying to use delegation to implement it, so that I can call the block first, get a new object and redirect the method call to it. Why this way ? Because basically, accessing an object to do something means to call a method on it. For instance, in print #current_time, it sends #current_time.to_s.
But since almost all objects will have a few methods inherited from standard base classes in Ruby like Object, LazyMaker also has methods like to_s. So I thought of making just the LazyMaker inherit from BasicObject, which is a blank class. So almost all of the methods get delegated.
But yeah, there might be another way to do this.

Help a ruby noob understand class inheritance

I'm trying to learn ruby by building a basic Campfire bot to screw around with at work. I've gotten pretty far (it works!) and learned a lot (it works!), but now I'm trying to make it a bit more complex by separating the actions to be performed out into their own classes, so that they can be easier to write / fix when broken. If you're interested in seeing all the (probably crappy) code, it's all up on GitHub. But for the sake of this question, I'll narrow the scope a bit.
Ideally, I would like to be able to create plugins easily, name them the same as the class name, and drop them into an "actions" directory in the root of the project, where they will be instantiated at runtime. I want the plugins themselves to be as simple as possible to write, so I want them all to inherit some basic methods and properties from an action class.
Here is action.rb as it currently exists:
module CampfireBot
class Action
#handlers = {}
def initialize(room)
#room = room
end
class << self
attr_reader :handlers
attr_reader :room
def hear(pattern, &action)
Action.handlers[pattern] = action
end
end
end
end
Where #room is the room object, and #handlers is a hash of patterns and blocks. I kind of don't understand why I have to do that class << self call, but that's the only way I could get the child plugin classes to see that hear method.
I then attempt to create a simple plugin like so (named Foo.rb):
class Foo < CampfireBot::Action
hear /foo/i do
#room.speak "bar"
end
end
I then have my plugins instantiated inside bot.rb like so:
def load_handlers(room)
actions = Dir.entries("#{BOT_ROOT}/actions").delete_if {|action| /^\./.match(action)}
action_classes = []
# load the source
actions.each do |action|
load "#{BOT_ROOT}/actions/#{action}"
action_classes.push(action.chomp(".rb"))
end
# and instantiate
action_classes.each do |action_class|
Kernel.const_get(action_class).new(room)
end
#handlers = Action.handlers
end
The blocks are then called inside room.rb when the pattern is matched by the following:
handlers.each do |pattern, action|
if pattern.match(msg)
action.call($~)
end
end
If I do puts #room inside the initialization of Action, I see the room object printed out in the console. And if I do puts "foo" inside Foo.rb's hear method, I see foo printed out on the console (so, the pattern match is working). But, I can't read that #room object from the parent class (it comes out as a nil object). So obviously I'm missing something about how this is supposed to be working.
Furthermore, if I do something to make the plugin a bit cleaner (for larger functions) and rewrite it like so:
class Foo < CampfireBot::Action
hear /foo/i do
say_bar
end
def say_bar
#room.speak "bar"
end
end
I get NoMethodError: undefined method 'say_bar' for Foo:Class.
The definition of hear can be pulled out of the class << self block and changed to:
def self.hear(pattern, &action)
Action.handlers[pattern] = action
end
to yield the exact same result. That also immediately explains the problem. hear Is a class method. say_bar is an instance method. You can't call an instance method from a class method, because there simply isn't an instance of the class available.
To understand the class << self bit, you'll have to do your own reading and experiments: I won't try to improve on what has already been said. I'll only say that within the class << self .. end block, self refers to the eigenclass or metaclass of the CampfireBot::Action class. This is the instance of the Class class that holds the definition of the CampfireBot::Action class.

Resources