跳至內容

測試 Crystal 程式碼

Crystal 在 Spec 模組中提供功能齊全的規格程式庫。它提供了一個結構,用於編寫程式碼行為方式的可執行範例。

Rspec 的啟發,它包含一個領域特定語言 (DSL),可讓您以類似於普通英語的方式編寫範例。

一個基本的規格看起來像這樣

require "spec"

describe Array do
  describe "#size" do
    it "correctly reports the number of elements in the Array" do
      [1, 2, 3].size.should eq 3
    end
  end

  describe "#empty?" do
    it "is true when no elements are in the array" do
      ([] of Int32).empty?.should be_true
    end

    it "is false if there are elements in the array" do
      [1].empty?.should be_false
    end
  end
end

規格檔案的結構

若要使用規格模組和 DSL,您需要在您的規格檔案中加入 require "spec"。許多專案使用自訂的 規格輔助程式 來組織這些包含項目。

具體的測試案例在 it 區塊中定義。一個可選的(但強烈建議的)描述性字串說明其目的,而一個區塊包含執行測試的主要邏輯。

已經定義或概述但尚未預期可運作的測試案例可以使用 pending 而不是 it 來定義。它們不會被執行,但會在規格報告中顯示為待處理。

it 區塊包含一個範例,該範例應調用要測試的程式碼,並定義對它的期望。每個範例可以包含多個期望,但它應該只測試一個特定的行為。

當包含 spec 時,每個物件都有實例方法 #should#should_not。這些方法在被測試的值上調用,並以期望作為引數。如果符合期望,程式碼執行會繼續。否則,範例將失敗,並且此區塊中的其他程式碼將不會被執行。

在測試檔案中,規格由範例群組結構化,這些群組由 describecontext 區段定義。通常,最上層的 describe 定義規格要測試的外部單元(例如類別)。進一步的 describe 區段可以巢狀在外部單元內,以指定被測試的較小單元(例如個別方法)。

對於單元測試,建議遵循方法名稱的慣例:外部 describe 是類別的名稱,內部 describe 目標是方法。實例方法以 # 為前綴,類別方法以 . 為前綴。

若要建立某些情境 - 想像空陣列有元素的陣列 - 可以使用 context 方法將其傳達給讀者。它具有不同的名稱,但行為與 describe 完全相同。

describecontext 都會採用一個描述作為引數(通常應該是字串)和一個包含個別規格或巢狀分組的區塊。

期望

期望定義被測試的值(實際)是否符合特定的值或特定條件。

等價性、同一性和類型

有一些方法可以建立期望,這些期望測試等價性 (eq)、同一性 (be)、類型 (be_a) 和 nil (be_nil)。請注意,同一性期望使用 .same?,它會測試 #object_id 是否相同。只有當預期的值指向相同的物件而不是等價的物件時,這才是正確的。這僅適用於參考類型,而不適用於結構或數字等值類型。

actual.should eq(expected)   # passes if actual == expected
actual.should be(expected)   # passes if actual.same?(expected)
actual.should be_a(expected) # passes if actual.is_a?(expected)
actual.should be_nil         # passes if actual.nil?

真值

actual.should be_true   # passes if actual == true
actual.should be_false  # passes if actual == false
actual.should be_truthy # passes if actual is truthy (neither nil nor false nor Pointer.null)
actual.should be_falsey # passes if actual is falsey (nil, false or Pointer.null)

比較

actual.should be < expected  # passes if actual <  expected
actual.should be <= expected # passes if actual <= expected
actual.should be > expected  # passes if actual >  expected
actual.should be >= expected # passes if actual >= expected

其他匹配器

actual.should be_close(expected, delta) # passes if actual is within delta of expected:
#                                         (actual - expected).abs <= delta
actual.should contain(expected) # passes if actual.includes?(expected)
actual.should match(expected)   # passes if actual =~ expected

預期錯誤

這些匹配器會執行一個區塊,並且如果該區塊引發特定的例外,則會通過。

expect_raises(MyError) do
  # Passes if this block raises an exception of type MyError.
end

expect_raises(MyError, "error message") do
  # Passes if this block raises an exception of type MyError
  # and the error message contains "error message".
end

expect_raises(MyError, /error \w{7}/) do
  # Passes if this block raises an exception of type MyError
  # and the error message matches the regular expression.
end

expect_raises 會傳回已救援的例外,因此可用於進一步的期望,例如驗證例外的特定屬性。

ex = expect_raises(MyError) do
  # Passes if this block raises an exception of type MyError.
end
ex.my_error_value.should eq "foo"

聚焦於一組規格

describecontextit 區塊可以用 focus: true 來標記,如下所示

it "adds", focus: true do
  (2 + 2).should_not eq(5)
end

如果任何此類項目標記為 focus: true,則只會執行這些範例。

標記規格

標籤可用於分組規格,允許在向規格執行器提供 --tag 引數時僅執行規格的子集(請參閱使用編譯器)。

describecontextit 區塊可以標記,如下所示

it "is slow", tags: "slow" do
  sleep 60
  true.should be_true
end

it "is fast", tags: "fast" do
  true.should be_true
end

標記一個範例群組 (describecontext) 會延伸到所有包含的範例。

可以透過提供一個 Enumerable(例如 ArraySet)來指定多個標籤。

執行規格

Crystal 編譯器具有一個 spec 命令,其中包含限制要執行的範例和調整輸出的工具。專案的所有規格都透過命令 crystal spec 編譯和執行。

依照慣例,規格位於專案的 spec/ 目錄中。規格檔案必須以 _spec.cr 結尾,才能被編譯器命令識別為規格檔案。

您可以從資料夾樹狀結構、個別檔案或檔案中的特定行編譯和執行規格。如果指定的行是 describecontext 區段的開頭,則會執行該群組內的所有規格。

預設格式化器會為失敗的規格輸出檔案和行樣式命令,這使得可以輕鬆地重新執行此單獨的規格。

您可以使用切換 --no-color 來關閉色彩。

隨機化規格的順序

規格預設會按照定義的順序執行,但是可以透過將 --order random 傳遞給 crystal spec 來以隨機順序執行。

以隨機順序執行的規格會在完成時顯示一個種子值。這個種子值可以用來透過將種子值傳遞給 --order 來以相同的順序重新執行規格。

範例

# Run all specs in files matching spec/**/*_spec.cr
crystal spec

# Run  all specs in files matching spec/**/*_spec.cr without colors
crystal spec --no-color

# Run all specs in files matching spec/my/test/**/*_spec.cr
crystal spec spec/my/test/

# Run all specs in spec/my/test/file_spec.cr
crystal spec spec/my/test/file_spec.cr

# Run the spec or group defined in line 14 of spec/my/test/file_spec.cr
crystal spec spec/my/test/file_spec.cr:14

# Run all specs tagged with "fast"
crystal spec --tag 'fast'

# Run all specs not tagged with "slow"
crystal spec --tag '~slow'

還有其他選項可以依名稱執行規格、調整輸出格式、執行預執行等,請參閱使用編譯器

規格輔助程式

許多專案使用自訂的規格輔助程式檔案,通常命名為 spec/spec_helper.cr

此檔案用於引入 spec,以及其他所有 spec 檔案都需要的專案程式碼。這裡也是定義全域輔助方法的好地方,這些方法能讓撰寫 spec 更容易,並避免程式碼重複。

spec/spec_helper.cr
require "spec"
require "../src/my_project.cr"

def create_test_object(name)
  project = MyProject.new(option: false)
  object = project.create_object(name)
  object
end
spec/my_project_spec.cr
require "./spec_helper"

describe "MyProject::Object" do
  it "is created" do
    object = create_test_object(name)
    object.should_not be_nil
  end
end