Sự khác biệt giữa chủ đề của RSpec và let là gì? Khi nào chúng nên được sử dụng hay không?


Câu trả lời:


186

Tóm tắt: Chủ đề của RSpec là một biến đặc biệt đề cập đến đối tượng đang được kiểm tra. Kỳ vọng có thể được thiết lập ngầm trên đó, hỗ trợ các ví dụ một dòng. Nó rõ ràng đối với người đọc trong một số trường hợp thành ngữ, nhưng khó hiểu và nên tránh. Các letbiến của RSpec chỉ là các biến được khởi tạo (ghi nhớ) một cách lười biếng. Chúng không khó để làm theo chủ đề, nhưng vẫn có thể dẫn đến các bài kiểm tra rối vì vậy nên được sử dụng một cách thận trọng.

Chủ đề

Làm thế nào nó hoạt động

Chủ thể là đối tượng đang được kiểm tra. RSpec có một ý tưởng rõ ràng về chủ đề. Nó có thể được xác định hoặc không. Nếu đúng như vậy, RSpec có thể gọi các phương thức trên đó mà không cần tham chiếu đến nó một cách rõ ràng.

Theo mặc định, nếu đối số đầu tiên cho một nhóm ( describehoặc contextkhối) mẫu ngoài cùng là một lớp, RSpec sẽ tạo một thể hiện của lớp đó và gán nó cho chủ thể. Ví dụ: các thông số sau:

class A
end

describe A do
  it "is instantiated by RSpec" do
    expect(subject).to be_an(A)
  end
end

Bạn có thể tự xác định chủ đề bằng subject:

describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Bạn có thể đặt tên cho chủ thể khi bạn xác định nó:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(a).to be_an(A)
  end
end

Ngay cả khi bạn đặt tên chủ đề, bạn vẫn có thể đề cập đến nó một cách ẩn danh:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Bạn có thể xác định nhiều hơn một chủ đề được đặt tên. Chủ thể được đặt tên được xác định gần đây nhất là chủ thể ẩn danh subject.

Tuy nhiên, chủ đề được xác định,

  1. Nó được tạo ra một cách lười biếng. Có nghĩa là, sự khởi tạo ngầm định của lớp được mô tả hoặc việc thực thi khối được truyền tới subjectsẽ không xảy ra cho đến khi subjecthoặc chủ thể được đặt tên được tham chiếu trong một ví dụ. Nếu bạn muốn chủ đề hết hạn của mình được khởi tạo một cách háo hức (trước khi một ví dụ trong nhóm chạy), hãy nói subject!thay vì subject.

  2. Kỳ vọng có thể được đặt ra trên đó một cách ngầm định (không cần viết subjecthoặc tên của một chủ đề được đặt tên):

    describe A do
      it { is_expected.to be_an(A) }
    end
    

    Chủ đề tồn tại để hỗ trợ cú pháp một dòng này.

Khi nào sử dụng nó

subjectKhó hiểu một ẩn ý (suy ra từ nhóm ví dụ) vì

  • Nó được tạo ngay sau hậu trường.
  • Cho dù nó được sử dụng ngầm (bằng cách gọi is_expectedmà không có người nhận rõ ràng) hay rõ ràng (dưới dạng subject), nó không cung cấp cho người đọc thông tin về vai trò hoặc bản chất của đối tượng mà kỳ vọng đang được gọi.
  • Cú pháp ví dụ một lớp lót không có mô tả ví dụ (đối số chuỗi ittrong cú pháp ví dụ thông thường), vì vậy thông tin duy nhất mà người đọc có về mục đích của ví dụ là chính kỳ vọng.

Do đó, chỉ hữu ích khi sử dụng một chủ đề ngầm khi ngữ cảnh có thể được tất cả người đọc hiểu rõ và thực sự không cần mô tả ví dụ . Trường hợp chính tắc đang kiểm tra xác thực ActiveRecord với trình so khớp nêna:

describe Article do
  it { is_expected.to validate_presence_of(:title) }
end

Ẩn danh hết hạn subject(được định nghĩa subjectmà không có tên) tốt hơn một chút, vì người đọc có thể thấy cách nó được khởi tạo, nhưng

  • nó vẫn có thể đưa phần khởi tạo của chủ đề khác xa nơi nó được sử dụng (ví dụ: ở đầu nhóm ví dụ với nhiều ví dụ sử dụng nó), điều này vẫn khó theo dõi và
  • nó có các vấn đề khác mà chủ thể ngầm hiểu.

Một chủ thể được đặt tên cung cấp một cái tên tiết lộ ý định, nhưng lý do duy nhất để sử dụng một chủ đề được đặt tên thay vì một letbiến là nếu bạn muốn sử dụng chủ đề ẩn danh đôi khi và chúng tôi vừa giải thích lý do tại sao chủ đề ẩn danh khó hiểu.

Vì vậy, việc sử dụng hợp pháp một chủ thể ẩn danh rõ ràng subjecthoặc một chủ thể được đặt tên là rất hiếm .

let biến

Cách chúng hoạt động

let các biến giống như các đối tượng được đặt tên ngoại trừ hai điểm khác biệt:

  • chúng được định nghĩa bằng let/ let!thay vì subject/subject!
  • họ không đặt ẩn danh subjecthoặc cho phép các kỳ vọng được gọi ngầm trên đó.

Khi nào sử dụng chúng

Nó hoàn toàn hợp pháp để sử dụng letđể giảm sự trùng lặp giữa các ví dụ. Tuy nhiên, chỉ làm như vậy khi nó không làm mất đi độ rõ ràng của bài kiểm tra. Thời điểm an toàn nhất để sử dụng letlà khi letmục đích của biến hoàn toàn rõ ràng so với tên của nó (để người đọc không phải tìm định nghĩa, có thể là nhiều dòng, để hiểu từng ví dụ) và nó được sử dụng theo cách tương tự trong mọi ví dụ. Nếu một trong hai điều đó không đúng, hãy xem xét việc xác định đối tượng trong một biến cục bộ cũ thuần túy hoặc gọi một phương thức gốc ngay trong ví dụ.

let!là rủi ro, bởi vì nó không phải là lười biếng. Nếu ai đó thêm một ví dụ vào nhóm ví dụ có chứa let!, nhưng ví dụ đó không cần let!biến,

  • ví dụ đó sẽ khó hiểu, vì người đọc sẽ nhìn thấy let!biến và tự hỏi liệu nó có ảnh hưởng như thế nào đến ví dụ đó
  • ví dụ sẽ chậm hơn mức cần thiết, vì mất nhiều thời gian để tạo let!biến thể

Vì vậy, chỉ nên sử dụng let!trong các nhóm ví dụ nhỏ, đơn giản, nơi ít có khả năng những người viết ví dụ trong tương lai sẽ rơi vào cái bẫy đó.

Sự tôn sùng đơn kỳ vọng mỗi ví dụ

Có một sự lạm dụng phổ biến của các chủ đề hoặc letcác biến đáng để thảo luận riêng. Một số người thích sử dụng chúng như thế này:

describe 'Calculator' do
  describe '#calculate' do
    subject { Calculator.calculate }
    it { is_expected.to be >= 0 }
    it { is_expected.to be <= 9 }
  end
end

(Đây là một ví dụ đơn giản về phương thức trả về một số mà chúng ta cần hai kỳ vọng, nhưng kiểu này có thể có nhiều ví dụ / kỳ vọng hơn nếu phương thức trả về một giá trị phức tạp hơn cần nhiều kỳ vọng và / hoặc có nhiều tác dụng phụ tất cả đều cần kỳ vọng.)

Mọi người làm điều này vì họ đã nghe nói rằng người ta chỉ nên có một kỳ vọng cho mỗi ví dụ (điều này bị trộn lẫn với quy tắc hợp lệ rằng người ta chỉ nên kiểm tra một lệnh gọi phương thức cho mỗi ví dụ) hoặc vì họ yêu thích sự phức tạp của RSpec. Đừng làm điều đó, cho dù với một chủ đề ẩn danh hoặc có tên hoặc một letbiến! Phong cách này có một số vấn đề:

  • Chủ đề ẩn danh không phải là chủ đề của các ví dụ - phương thức là chủ đề. Viết bài kiểm tra theo cách này làm rối loạn ngôn ngữ, khiến bạn khó suy nghĩ hơn.
  • Như mọi khi với các ví dụ một dòng, không có chỗ nào để giải thích ý nghĩa của các kỳ vọng.
  • Đối tượng phải được xây dựng cho mỗi ví dụ, điều này diễn ra chậm.

Thay vào đó, hãy viết một ví dụ:

describe 'Calculator' do
  describe '#calculate' do
    it "returns a single-digit number" do
      result = Calculator.calculate
      expect(result).to be >= 0
      expect(result).to be <= 9
    end
  end
end

17
Chà! + 💯! "Sự tôn sùng duy nhất mỗi kỳ vọng" có giá trị bằng vàng, có thể nói như vậy. Tôi chưa bao giờ cảm thấy cần phải tuân theo quy tắc (sai) đó rất chặt chẽ, nhưng bây giờ tôi đã được trang bị tốt để chống lại những người cố gắng ép buộc phần còn lại của chúng tôi. Cảm ơn!
iconoclast

2
Ngoài ra, nếu bạn muốn khối đa sự mong đợi của bạn để chạy tất cả các dòng (thay vì không chạy nếu thất bại đầu tiên), bạn có thể sử dụng mong đợi :aggregate_failuresthẻ trong một dòng như it ​"marks a task complete"​, ​:aggregate_failures​ ​do(lấy từ cuốn sách Rails 5 Kiểm tra Đơn thuốc)
mê cung

1
Tôi cũng chống lại sự cường điệu và giả tạo, "kỳ vọng duy nhất cho mỗi ví dụ" chủ yếu dành cho những người thích nhóm nhiều kỳ vọng không liên quan vào một ví dụ duy nhất. Nói một cách ngữ nghĩa, ví dụ của bạn không phải là lý tưởng, bởi vì nó không xác thực cho một số có một chữ số, nó xác thực cho các số thực trong [0, 9] - điều này, gây ngạc nhiên, có thể được mã hóa theo một kỳ vọng duy nhất dễ đọc hơn nhiều expect(result).to be_between(0, 9).
Andre Figueedlyo

Nếu bạn chỉ muốn xác thực cho số có một chữ số (Int [0,9]) thì sẽ phù hợp hơn để làm expect(result.to_s).to match(/^[0-9]$/)- Tôi biết nó xấu nhưng nó thực sự kiểm tra những gì bạn đang nói, hoặc có lẽ, sử dụng between+ is_a? Integer, nhưng ở đây bạn đang thử nghiệm cả loại nữa. Và chỉ là let.. nó không nên là đối tượng của mối quan tâm, và thực sự có thể tốt hơn nếu đánh giá lại các giá trị giữa các ví dụ. Nếu không thì +1 cho bài đăng
Andre Figueosystemo

Tôi thích lời giải thích của bạn về những nguy hiểm let!và tôi đã không thuyết phục được đồng đội của mình. Tôi sẽ gửi câu trả lời này qua.
Damon Aw,

5

Subjectletchỉ là những công cụ giúp bạn sắp xếp và tăng tốc các bài kiểm tra của mình. Mọi người trong cộng đồng rspec đều sử dụng chúng nên tôi sẽ không lo lắng về việc sử dụng chúng có ổn hay không. Chúng có thể được sử dụng tương tự nhưng phục vụ các mục đích hơi khác nhau

Subjectcho phép bạn khai báo một đối tượng thử nghiệm, sau đó sử dụng lại nó cho bất kỳ số trường hợp thử nghiệm nào sau đó. Điều này làm giảm sự lặp lại mã (KHÔ mã của bạn)

Letlà một giải pháp thay thế cho before: eachcác khối, gán dữ liệu thử nghiệm cho các biến phiên bản. Letmang lại cho bạn một vài lợi thế. Đầu tiên, nó lưu trữ giá trị mà không gán giá trị đó cho một biến thể hiện. Thứ hai, nó được đánh giá một cách lười biếng, có nghĩa là nó không được đánh giá cho đến khi một thông số kỹ thuật gọi nó. Do đó letgiúp bạn tăng tốc độ kiểm tra của mình. Tôi cũng nghĩ letlà dễ đọc hơn


1

subjectlà những gì đang được kiểm tra, thường là một cá thể hoặc một lớp. letlà để gán các biến trong các thử nghiệm của bạn, được đánh giá một cách lười biếng so với sử dụng các biến phiên bản. Có một số ví dụ hay trong chủ đề này.

https://github.com/reachlocal/rspec-style-guide/issues/6

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.