RSpec के साथ समय की तुलना करने में परेशानी


119

मैं रूबी रेल्स 4 और आरएसईपी-रेल्स मणि 2.14 का उपयोग कर रहा हूं। मेरी ऑब्जेक्ट के लिए मैं updated_atनियंत्रक एक्शन चलाने के बाद ऑब्जेक्ट की विशेषता के साथ वर्तमान समय की तुलना करना चाहूंगा , लेकिन जब मैं कल्पना नहीं करता, तब तक मैं मुसीबत में हूं। यह है कि, निम्नलिखित कल्पना कोड है:

it "updates updated_at attribute" do
  Timecop.freeze

  patch :update
  @article.reload
  expect(@article.updated_at).to eq(Time.now)
end

जब मैं उपरोक्त युक्ति चलाता हूं तो मुझे निम्नलिखित त्रुटि मिलती है:

Failure/Error: expect(@article.updated_at).to eq(Time.now)

   expected: 2013-12-05 14:42:20 UTC
        got: Thu, 05 Dec 2013 08:42:20 CST -06:00

   (compared using ==)

मैं पास करने के लिए कल्पना कैसे कर सकता हूं?


नोट : मैंने भी निम्नलिखित की कोशिश की ( utcअतिरिक्त जोड़ दें ):

it "updates updated_at attribute" do
  Timecop.freeze

  patch :update
  @article.reload
  expect(@article.updated_at.utc).to eq(Time.now)
end

लेकिन कल्पना अभी भी पास नहीं हुई है (ध्यान दें "मिला" मूल्य अंतर):

Failure/Error: expect(@article.updated_at.utc).to eq(Time.now)

   expected: 2013-12-05 14:42:20 UTC
        got: 2013-12-05 14:42:20 UTC

   (compared using ==)

यह ऑब्जेक्ट आईडी की तुलना कर रहा है, इसलिए निरीक्षण से पाठ मेल खा रहा है, लेकिन आपके पास दो अलग-अलग समय ऑब्जेक्ट हैं। आप बस उपयोग कर सकते हैं ===, लेकिन यह दूसरी सीमाओं को पार करने से पीड़ित हो सकता है। संभवत: सबसे अच्छा यह है कि आप अपने स्वयं के मैचर को खोजें या लिखें, जिसमें आप युगांतर सेकंड में परिवर्तित होते हैं और एक छोटे से पूर्ण अंतर की अनुमति देते हैं।
नील स्लेटर

अगर मैंने आपको "दूसरी सीमाओं को पार करने" से संबंधित समझा, तो समस्या नहीं उठनी चाहिए क्योंकि मैं टाइमकोप रत्न का उपयोग कर रहा हूं जो समय को "जमा देता है"।
Backo

आह, मैं चूक गया, क्षमा करें। किस मामले में, ===इसके बजाय का उपयोग करें ==- वर्तमान में आप दो अलग-अलग समय ऑब्जेक्ट्स के object_id की तुलना कर रहे हैं। हालांकि Timecop डेटाबेस सर्वर समय फ्रीज नहीं करेगा। । । अगर आपके टाइमस्टैम्प आरडीबीएमएस द्वारा उत्पन्न किए जा रहे हैं तो यह काम नहीं करेगा (मुझे उम्मीद है कि आपके लिए यहां कोई समस्या नहीं है)
नील स्लेटर

जवाबों:


156

रूबी टाइम ऑब्जेक्ट डेटाबेस की तुलना में अधिक सटीक बनाए रखता है। जब मान डेटाबेस से वापस पढ़ा जाता है, तो इसे केवल माइक्रोसेकंड परिशुद्धता के लिए संरक्षित किया जाता है, जबकि इन-मेमोरी प्रतिनिधित्व नैनोकैन्ड के लिए सटीक है।

यदि आप मिलीसेकंड अंतर के बारे में परवाह नहीं करते हैं, तो आप अपनी अपेक्षा के दोनों तरफ एक to_s / to_i कर सकते हैं

expect(@article.updated_at.utc.to_s).to eq(Time.now.to_s)

या

expect(@article.updated_at.utc.to_i).to eq(Time.now.to_i)

समय अलग-अलग क्यों हैं, इस बारे में अधिक जानकारी के लिए इसका संदर्भ लें


जैसा कि आप प्रश्न से कोड में देख सकते हैं, मैं Timecopमणि का उपयोग करता हूं । क्या यह समय को "ठंड" द्वारा समस्या को हल करना चाहिए?
बैक डिओ

आप डेटाबेस में समय बचाते हैं और इसे पुनः प्राप्त करते हैं (@ article.updated_at) जो इसे नैनोसेकंड को ढीला कर देता है जहां Time.now ने नैनोसेकंड को बरकरार रखा है। यह मेरे जवाब की पहली कुछ पंक्तियों से स्पष्ट होना चाहिए
usha

3
स्वीकृत उत्तर नीचे दिए गए उत्तर की तुलना में लगभग एक वर्ष पुराना है - be_within माचिस इसे संभालने का एक बेहतर तरीका है: A) आपको मणि की आवश्यकता नहीं है; बी) यह किसी भी प्रकार के मूल्य (पूर्णांक, फ्लोट, तिथि, समय, आदि) के लिए काम करता है; ग) यह मूल रूप से RSpec का हिस्सा है
notaceo

3
यह एक "ओके" समाधान है, लेकिन निश्चित रूप be_withinसे सही एक है
फ्रांसेस्को बेलाडोना

नमस्ते, मैं नौकरी की देरी देरी की अपेक्षाओं के साथ डेटाइम तुलनाओं का उपयोग कर expect {FooJob.perform_now}.to have_enqueued_job(FooJob).at(some_time) रहा हूं, मुझे यकीन नहीं है कि .atमिलानकर्ता .to_iइस समस्या का सामना करने वाले किसी व्यक्ति के साथ पूर्णांक में परिवर्तित समय को स्वीकार करेगा ?
सिरिल डचोन-डोरिस

200

मुझे be_withinडिफ़ॉल्ट rspec मिलानकर्ता का उपयोग करना और अधिक सुंदर लगता है:

expect(@article.updated_at.utc).to be_within(1.second).of Time.now

2
.000001 एस थोड़ा तंग है। मैंने भी .001 की कोशिश की और यह कभी-कभी असफल रहा। यहां तक ​​कि .1 सेकंड मुझे लगता है कि अब समय निर्धारित किया जा रहा है। मेरे उद्देश्यों के लिए पर्याप्त है।
प्रातः

3
मुझे लगता है कि यह उत्तर बेहतर है क्योंकि यह परीक्षण के इरादे को व्यक्त करता है, न कि इसे कुछ असंबंधित तरीकों के पीछे रखने के बजाय to_s। इसके अलावा, to_iऔर to_sबार बार असफल हो सकता है अगर समय सेकंड का अंत निकट है।
बी सात

2
ऐसा लगता है कि be_within7 नवंबर, 2010 को RSpec 2.1.0 में मैचर जोड़ा गया था, और तब से कुछ समय बढ़ा। rspec.info/documentation/2.14/rspec-expectations/…
मार्क बेरी

1
मुझे मिलता है undefined method 'second' for 1:Fixnum। क्या मुझे कुछ चाहिए require?
डेविड मोल्स

3
@DavidMoles .secondएक रेल विस्तार है: api.rubyonrails.org/classes/Numeric.html
jwadsack

10

मैचर Oinका सुझाव be_withinहै कि हां सबसे अच्छा अभ्यास है

... और इसके कुछ और उपयोग हैं -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher

लेकिन इससे निपटने का एक और तरीका यह है कि इसमें middayऔर middnightविशेषताओं में निर्मित रेल का उपयोग किया जाए ।

it do
  # ...
  stubtime = Time.now.midday
  expect(Time).to receive(:now).and_return(stubtime)

  patch :update 
  expect(@article.reload.updated_at).to eq(stubtime)
  # ...
end

अब यह सिर्फ प्रदर्शन के लिए है!

मैं इसका उपयोग किसी नियंत्रक में नहीं करूंगा क्योंकि आप सभी Time.new कॉलों को रोक रहे हैं => सभी समय विशेषताओं का एक ही समय होगा => हो सकता है कि आप जिस अवधारणा को प्राप्त करने की कोशिश कर रहे हैं वह साबित न हो। मैं आमतौर पर इसे इसी तरह की रूबी वस्तुओं में उपयोग करता हूं:

class MyService
  attr_reader :time_evaluator, resource

  def initialize(resource:, time_evaluator: ->{Time.now})
    @time_evaluator = time_evaluator
    @resource = resource
  end

  def call
    # do some complex logic
    resource.published_at = time_evaluator.call
  end
end

require 'rspec'
require 'active_support/time'
require 'ostruct'

RSpec.describe MyService do
  let(:service) { described_class.new(resource: resource, time_evaluator: -> { Time.now.midday } ) }
  let(:resource) { OpenStruct.new }

  it do
    service.call
    expect(resource.published_at).to eq(Time.now.midday)    
  end
end

लेकिन ईमानदारी से मैं be_withinTime.now.midday की तुलना करते हुए भी माचिस के साथ छड़ी करने की सलाह देता हूं !

तो हाँ pls be_withinमिलान के साथ छड़ी ;)


अद्यतन 2017-02

टिप्पणी में प्रश्न:

क्या होगा अगर समय एक हैश में हो? किसी भी तरह से उम्मीद (हैश_1) .to eq (हैश_2) तब काम करती है जब कुछ हैश_1 मान प्री-डीबी-बार होते हैं और हैश_2 में संबंधित मान पोस्ट-डीबी-टाइम होते हैं? -

expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `

आप किसी भी RSpec मिलान करने वाले से matchमिलान कर सकते हैं (इसलिए जैसे आप भी कर सकते हैं शुद्ध RSpec के साथ API परीक्षण )

"पोस्ट-डीबी-टाइम्स" के रूप में मुझे लगता है कि आपको डीबी से बचाने के बाद उत्पन्न होने वाली स्ट्रिंग का मतलब है। मैं इस मामले को 2 अपेक्षाओं तक पहुँचाने का सुझाव दूंगा (एक हैश संरचना सुनिश्चित करना, दूसरा समय की जाँच करना) ताकि आप कुछ ऐसा कर सकें:

hash = {mytime: Time.now.to_s(:db)}
expect(hash).to match({mytime: be_kind_of(String))
expect(Time.parse(hash.fetch(:mytime))).to be_within(3.seconds).of(Time.now)

लेकिन अगर यह मामला आपके टेस्ट सूट में बहुत बार होता है, तो मैं सुझाव दूंगा कि आप अपने RSpec मैचर (उदा be_near_time_now_db_string) को db स्ट्रिंग टाइम को टाइम ऑब्जेक्ट में रूपांतरित करें और फिर इसका एक भाग के रूप में उपयोग करें match(hash):

 expect(hash).to match({mytime: be_near_time_now_db_string})  # you need to write your own matcher for this to work.

क्या होगा अगर समय एक हैश में हो? expect(hash_1).to eq(hash_2)काम करने का कोई तरीका जब कुछ हैश_1 मान प्री-डीबी-बार होते हैं और हैश_2 में संबंधित मान पोस्ट-डीबी-टाइम होते हैं?
माइकल जॉनसन

मैं एक मनमाना हैश सोच रहा था (मेरा अनुमान यह दावा कर रहा है कि एक ऑपरेशन रिकॉर्ड को बिल्कुल नहीं बदलता है)। लेकिन धन्यवाद!
माइकल जॉनसन

10

पुरानी पोस्ट, लेकिन मुझे आशा है कि यह किसी को भी मदद करता है जो समाधान के लिए यहां प्रवेश करता है। मुझे लगता है कि मैन्युअल रूप से तारीख बनाना आसान और अधिक विश्वसनीय है:

it "updates updated_at attribute" do
  freezed_time = Time.utc(2015, 1, 1, 12, 0, 0) #Put here any time you want
  Timecop.freeze(freezed_time) do
    patch :update
    @article.reload
    expect(@article.updated_at).to eq(freezed_time)
  end
end

यह सुनिश्चित करता है कि संग्रहित तिथि सही है, बिना to_xदशमलव के बारे में या चिंता किए।


सुनिश्चित करें कि आप कल्पना के अंत में समय
निकाल दें

इसके बजाय ब्लॉक का उपयोग करने के लिए इसे बदल दिया। इस तरह आप सामान्य समय पर वापस लौटने के बारे में नहीं भूल सकते।
जिल्बो

8

आप डेट / डेटटाइम / टाइम ऑब्जेक्ट को स्ट्रिंग में बदल सकते हैं क्योंकि यह डेटाबेस में संग्रहीत है to_s(:db)

expect(@article.updated_at.to_s(:db)).to eq '2015-01-01 00:00:00'
expect(@article.updated_at.to_s(:db)).to eq Time.current.to_s(:db)

7

इस समस्या को हल करने का सबसे आसान तरीका है कि मैं एक current_timeटेस्ट हेल्पर विधि तैयार करूं जैसे:

module SpecHelpers
  # Database time rounds to the nearest millisecond, so for comparison its
  # easiest to use this method instead
  def current_time
    Time.zone.now.change(usec: 0)
  end
end

RSpec.configure do |config|
  config.include SpecHelpers
end

अब समय हमेशा निकटतम मिलीसेकंड के लिए गोल होता है तुलना करने के लिए सीधे हैं:

it "updates updated_at attribute" do
  Timecop.freeze(current_time)

  patch :update
  @article.reload
  expect(@article.updated_at).to eq(current_time)
end

1
.change(usec: 0)बहुत मददगार है
कोएन।

2
हम इस .change(usec: 0)ट्रिक का इस्तेमाल बिना किसी हेल्पर के भी समस्या के समाधान के लिए कर सकते हैं । यदि पहली पंक्ति है Timecop.freeze(Time.current.change(usec: 0))तो हम बस .to eq(Time.now)अंत में तुलना कर सकते हैं ।
हैरी वुड

0

क्योंकि मैं हैश की तुलना कर रहा था, इसलिए इनमें से अधिकांश समाधान मेरे लिए काम नहीं करते थे, इसलिए मैंने पाया कि सबसे आसान समाधान यह था कि मैं जिस हैश की तुलना कर रहा था, उससे डेटा को हड़प सकता था। चूँकि अद्यतित_त समय मेरे लिए वास्तव में उपयोगी नहीं है, इसलिए यह ठीक काम करता है।

data = { updated_at: Date.new(2019, 1, 1,), some_other_keys: ...}

expect(data).to eq(
  {updated_at: data[:updated_at], some_other_keys: ...}
)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.