Hetzelfde willekeurige nummer robijn

Nou, ik ben een Ruby-newbie en ik probeer te leren met RubyKoans, maar ik ben vastgelopen met deze test

def test_dice_values_should_change_between_rolls
 48     dice = DiceSet.new
 49     dice.roll(5)
 50     first_time = dice.values
 51    
 52     dice.roll(5)
 53     second_time = dice.values
 54     
 55     assert_not_equal first_time, second_time,
 56       "Two rolls should not be equal"
 57   end

en dit is de klasse DiceSet

5  class DiceSet
  6    attr_accessor :values
  7 ··
  8    def initialize
  9      @values = []
 10    end
 11 
 12    def roll(times)
 13      @values.clear
 14      times.times do |x|
 15        @values << ( 1 + rand(6))
 16      end
 17     end
 18 ····
 19    end

het ding is hier dat telkens wanneer ik de code uitvoer, het altijd precies dezelfde reeks getallen genereert, dit is de uitvoer.

Two rolls should not be equal.  <[3, 2, 4, 1, 3]> expected to be != to  <[3, 2, 4, 1, 3]>.

in de test noem ik DiceSet.roll twee keer en voor die twee keer krijg ik exact dezelfde reeks 'willekeurige' getallen als ze supossed om gelijk te zijn toch? Ik dacht dat ik misschien een ander exemplaar van DiceSet zou maken om de test te halen, maar ik vermoed dat dat niet het doel van de test is

1
Ik ben met je, Gustavo. Ik wil graag geheugen besparen en dezelfde array opnieuw gebruiken. Helaas zijn de ontwerpers van koans vergeten dat gelijkheid eerst de referentie controleert en vervolgens de instantievariabelen controleert. Een mogelijke oplossing voor dit referentieprobleem is het wijzigen van first_time = dice.values ​​ in first_time = dice.values.clone . Maar dit neemt niet weg dat er een kleine kans is dat deze test toch zal mislukken.
toegevoegd de auteur Patrick James McDougle, de bron
Om dit te laten werken, moet je first_time = Array.new (dice.values) en second_time = Array.new (dice.values)
toegevoegd de auteur Kassym Dorsel, de bron

4 antwoord

Het probleem is dat DiceSet # -waarden een verwijzing naar een array retourneert en die array hetzelfde blijft gedurende de gehele levensduur van uw DiceSet-object. In DiceSet # roll wist u die array en voegt u nieuwe nummers toe. Aangezien beide aanroepen van DiceSet # -waarden dezelfde referentie retourneren, zal het resultaat van de eerste rol verloren gaan en vergelijkt uw test de array met zichzelf.

Ik ben niet bekend met de RubyKoans en welke eisen ze hebben, dat wil zeggen dat als je DiceSet de waarden enz. Zou moeten opslaan. Als dit het geval is, dan is de meest eenvoudige oplossing om ofwel twee DiceSets te gebruiken of Object # dup om een ​​kopie van het geretourneerde object voor de test op te slaan.

Houd er echter rekening mee dat uw test fragiel is, zelfs met correct functionerende code, aangezien er altijd de kans is dat twee opeenvolgende rollen exact dezelfde aantallen zullen retourneren. In dit specifieke geval is het relatief klein, maar nog steeds zeer bestaand.

6
toegevoegd
De kans op een foutieve fout bij deze test is 1/(6 ^ 5) = 1/7776. Als u een paar keer een mislukte test herhaalt, wordt de test met een zeer hoog vertrouwensniveau opgelost.
toegevoegd de auteur Frank Szczerba, de bron
Ik ben het ermee eens dat willekeurig mislukkende testen slecht zijn. Mijn punt was dat je de test kunt aanpassen zodat deze lus totdat het 2 verschillende resultaten krijgt, met een limiet aan het aantal lussen. Als u die luslimiet instelt op 10, dan is de kans op een foute uitval gelijk aan de 1e-39. De kans dat de hardware faalt is groter dan dat.
toegevoegd de auteur Frank Szczerba, de bron
Excuseer mij? De getallen worden opgeslagen in @ values ​​ en hij gebruikt de attribuutlezer # values ​​ om die array te krijgen. first_time en second_time wijzen naar de array exact dezelfde .
toegevoegd de auteur Dominik Honnef, de bron
@FrankSzczerba Maar dan zijn er tests die alleen willekeurig falen en daadwerkelijk daadwerkelijke fouten in de code onthullen. Dus vertrouwen op "oh, het verschijnt maar één op de 8000 keer" is slecht. Bovendien heeft het het potentieel van valse resultaten bij CI.
toegevoegd de auteur Dominik Honnef, de bron
Ah, mijn fout. Ja, dat zou werken.
toegevoegd de auteur Dominik Honnef, de bron
De waarden worden wel elke keer gewist, maar in de test na elke rol worden de waarden opgeslagen in een andere variabele. Er is geen probleem.
toegevoegd de auteur Kassym Dorsel, de bron
Mijn slechte ... ik heb over het hoofd gezien.
toegevoegd de auteur Kassym Dorsel, de bron

Het volgende zou voor deze test moeten werken:

class DiceSet
  attr_accessor :values

  def roll (times)
    @values = []
    times.times do |x|
      @values << ( 1 + rand(6) )
    end
  end
end

Dus we creëren een nieuwe array voor elke rol.

1
toegevoegd

Elke keer dat een nieuwe array wordt gebruikt, wordt het referentieprobleem opgelost dat dominikh heeft aangegeven, maar zoals hij correct zei, kan je niet garanderen dat 2 opeenvolgende rollen een andere reeks nos hebben. In mijn implementatie herinner ik me de laatste worp en loop tot ik een andere set krijg:

class DiceSet
  attr_reader :values, :lastroll
  def initialize
      @values = []
      @lastroll = []
  end
  def roll(n)
      while @values == @lastroll
          @values = Array.new(n) { |i| i = rand(6) + 1 }
      end
      @lastroll = @values
  end
end
1
toegevoegd
Dat maakt je resultaat iets minder willekeurig. De test is verbroken (in het onwaarschijnlijke geval dat twee resultatensets hetzelfde zijn), moet u de code niet breken om daar rekening mee te houden, u moet in plaats daarvan de test repareren.
toegevoegd de auteur Frank Szczerba, de bron

Het is beter om attr_reader te gebruiken in plaats van attr_accessor (zoals Ilya Tsuryev suggereerde). Omdat je niet wilt dat die code de dobbelstenen bedriegt. En het is beter leesbaar om rand (1..6) te gebruiken.

class DiceSet
  attr_reader :values

  def roll(set_size)
    @values = []
    set_size.times { @values.push rand(1..6) }
  end
end
0
toegevoegd