Làm thế nào một ngôn ngữ mới sẽ trông như thế nào nếu nó được thiết kế từ đầu để dễ dàng đến TDD?


9

Với một số ngôn ngữ phổ biến nhất (Java, C #, Java, v.v.) đôi khi có vẻ như bạn đang làm việc mâu thuẫn với ngôn ngữ khi bạn muốn hoàn toàn TDD mã của mình.

Ví dụ, trong Java và C #, bạn sẽ muốn chế nhạo bất kỳ sự phụ thuộc nào của các lớp của bạn và hầu hết các khung mô phỏng sẽ khuyên bạn giả định các giao diện không phải là các lớp. Điều này thường có nghĩa là bạn có nhiều giao diện với một triển khai duy nhất (hiệu ứng này thậm chí còn đáng chú ý hơn vì TDD sẽ buộc bạn phải viết một số lượng lớn hơn các lớp nhỏ hơn). Các giải pháp cho phép bạn mô phỏng các lớp cụ thể một cách chính xác làm những việc như thay đổi trình biên dịch hoặc ghi đè các trình nạp lớp, v.v., điều này khá khó chịu.

Vậy một ngôn ngữ sẽ trông như thế nào nếu nó được thiết kế từ đầu để trở nên tuyệt vời với TDD? Có thể một số cách ngôn ngữ mức độ mô tả các phụ thuộc (thay vì chuyển giao diện cho một nhà xây dựng) và có thể tách giao diện của một lớp mà không làm như vậy rõ ràng?


Làm thế nào về một ngôn ngữ không cần TDD? blog.8thlight.com/uncle-bob/2011/10/20/Simple-Hickey.html
Công việc

2
Không có ngôn ngữ cần TDD. TDD là một cách thực hành hữu ích và một trong những điểm của Hickey là chỉ vì bạn kiểm tra không có nghĩa là bạn có thể ngừng suy nghĩ .
Frank Shearar

Kiểm tra hướng phát triển là về việc làm cho API bên trong và bên ngoài của bạn đúng, và làm như vậy trước. Do đó trong Java, tất cả về các giao diện - các lớp thực tế là sản phẩm phụ.

Câu trả lời:


6

Nhiều năm trước tôi đã cùng nhau tạo ra một nguyên mẫu giải quyết một câu hỏi tương tự; đây là một ảnh chụp màn hình:

Kiểm tra nút không

Ý tưởng là các xác nhận là nội tuyến với chính mã và tất cả các thử nghiệm chạy cơ bản tại mỗi tổ hợp phím. Vì vậy, ngay sau khi bạn thực hiện bài kiểm tra, bạn sẽ thấy phương thức chuyển sang màu xanh.


2
Haha, thật tuyệt vời Tôi thực sự khá thích ý tưởng đặt các bài kiểm tra cùng với mã. Nó khá tẻ nhạt (mặc dù có những lý do rất chính đáng) trong .NET có các hội đồng riêng biệt với các không gian tên song song cho các bài kiểm tra đơn vị. Nó cũng giúp tái cấu trúc dễ dàng hơn vì di chuyển mã, tự động di chuyển các bài kiểm tra: P
Geoff

Nhưng bạn có muốn để lại các bài kiểm tra trong đó không? Bạn sẽ để chúng được kích hoạt cho mã sản xuất? Có lẽ họ có thể là # ifdef'd out for C, nếu không, chúng tôi đang xem xét các lần truy cập kích thước mã / thời gian chạy.
Mawg nói rằng phục hồi Monica

Nó hoàn toàn là một nguyên mẫu. Nếu nó là sự thật, thì chúng ta sẽ phải xem xét những thứ như hiệu suất và kích thước, nhưng sẽ sớm phải lo lắng về điều đó, và nếu chúng ta đã đến thời điểm đó, sẽ không khó để chọn ra những gì để bỏ qua hoặc, nếu muốn, để loại bỏ các xác nhận ra khỏi mã được biên dịch. Cảm ơn sự nhiệt tình của bạn.
Carl Manaster

5

Nó sẽ được tự động hơn là gõ tĩnh. Gõ vịt sau đó sẽ làm công việc tương tự như các giao diện thực hiện trong các ngôn ngữ gõ tĩnh. Ngoài ra, các lớp của nó sẽ có thể sửa đổi trong thời gian chạy để khung kiểm tra có thể dễ dàng khai thác hoặc giả định các phương thức trên các lớp hiện có. Ruby là một ngôn ngữ như vậy; rspec là khung thử nghiệm hàng đầu của nó cho TDD.

Làm thế nào kiểm tra năng động gõ hỗ trợ

Với kiểu gõ động, bạn có thể tạo các đối tượng giả bằng cách đơn giản tạo một lớp có cùng giao diện (chữ ký phương thức) đối tượng cộng tác viên bạn cần để giả. Ví dụ: giả sử bạn có một số lớp đã gửi tin nhắn:

class MessageSender
  def send
    # Do something with a side effect
  end
end

Hãy nói rằng chúng tôi có MessageSenderUser sử dụng một phiên bản của MessageSender:

class MessageSenderUser

  def initialize(message_sender)
    @message_sender = message_sender
  end

  def do_stuff
    ...
    @message_sender.send
    ...
    @message_sender.send
    ...
  end

end

Lưu ý việc sử dụng ở đây của tiêm phụ thuộc , một yếu tố chính của thử nghiệm đơn vị. Chúng tôi sẽ trở lại với điều đó.

Bạn muốn kiểm tra rằng MessageSenderUser#do_stuffcác cuộc gọi gửi hai lần. Giống như bạn làm trong một ngôn ngữ được nhập tĩnh, bạn có thể tạo một MessageSender giả, đếm số lần sendđược gọi. Nhưng không giống như một ngôn ngữ gõ tĩnh, bạn không cần lớp giao diện. Bạn chỉ cần tiếp tục và tạo ra nó:

class MockMessageSender

  attr_accessor :send_count

  def initialize
    @send_count = 0
  end

  def send
    @send_count += 1
  end

end

Và sử dụng nó trong thử nghiệm của bạn:

mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)

Chính nó, "gõ vịt" của một ngôn ngữ được gõ động không thêm nhiều thứ để kiểm tra so với một ngôn ngữ gõ tĩnh. Nhưng nếu các lớp không đóng, nhưng có thể được sửa đổi trong thời gian chạy thì sao? Đó là một người thay đổi cuộc chơi. Chúng ta hãy xem làm thế nào.

Điều gì sẽ xảy ra nếu bạn không phải sử dụng phép tiêm phụ thuộc để kiểm tra lớp?

Giả sử rằng MessageSenderUser sẽ chỉ sử dụng MessageSender để gửi tin nhắn và bạn không cần phải cho phép thay thế MessageSender bằng một số lớp khác. Trong một chương trình duy nhất, điều này thường là trường hợp. Chúng ta hãy viết lại MessageSenderUser để nó đơn giản tạo và sử dụng MessageSender, không có nội dung phụ thuộc.

class MessageSenderUser

  def initialize
    @message_sender = MessageSender.new
  end

  def do_stuff
    ...
    @message_sender.send
    ...
    @message_sender.send
    ...
  end

end

MessageSenderUser giờ đây đơn giản hơn để sử dụng: Không ai tạo ra nó cần tạo MessageSender để sử dụng. Nó không giống như một sự cải tiến lớn trong ví dụ đơn giản này, nhưng bây giờ hãy tưởng tượng rằng MessageSenderUser được tạo ra ở nhiều nơi hoặc nó có ba phụ thuộc. Bây giờ hệ thống có rất nhiều trường hợp vượt qua chỉ để làm cho các bài kiểm tra đơn vị hài lòng, không phải vì nó nhất thiết phải cải thiện thiết kế.

Các lớp mở cho phép bạn kiểm tra mà không cần tiêm phụ thuộc

Một khung kiểm tra trong một ngôn ngữ với kiểu gõ động và các lớp mở có thể làm cho TDD khá đẹp. Đây là đoạn mã từ kiểm tra rspec cho MessageSenderUser:

mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff

Đó là toàn bộ bài kiểm tra. Nếu MessageSenderUser#do_stuffkhông gọi MessageSender#sendchính xác hai lần, thử nghiệm này thất bại. Lớp MessageSender thực sự không bao giờ được gọi: Chúng tôi đã nói với bài kiểm tra rằng bất cứ khi nào ai đó cố gắng tạo MessageSender, họ sẽ nhận được MessageSender giả của chúng tôi. Không cần tiêm phụ thuộc.

Thật tuyệt khi làm rất nhiều trong một thử nghiệm đơn giản như vậy. Sẽ không bao giờ phải sử dụng tiêm phụ thuộc trừ khi nó thực sự có ý nghĩa đối với thiết kế của bạn.

Nhưng điều này có liên quan gì đến các lớp mở? Lưu ý cuộc gọi đến MessageSender.should_receive. Chúng tôi đã không xác định #should_receive khi chúng tôi viết MessageSender, vậy ai đã làm? Câu trả lời là khung kiểm tra, thực hiện một số sửa đổi cẩn thận của các lớp hệ thống, có thể làm cho nó xuất hiện vì thông qua #should_receive được xác định trên mọi đối tượng. Nếu bạn nghĩ rằng sửa đổi các lớp hệ thống như thế đòi hỏi một chút thận trọng, bạn đã đúng. Nhưng đó là điều hoàn hảo cho những gì thư viện kiểm tra đang làm ở đây và các lớp mở làm cho nó có thể.


Câu trả lời chính xác! Các bạn đang bắt đầu nói chuyện với tôi về các ngôn ngữ động :) Tôi nghĩ rằng gõ vịt là chìa khóa ở đây, mẹo với .new cũng có thể bằng một ngôn ngữ gõ tĩnh (mặc dù nó sẽ kém thanh lịch hơn nhiều).
Geoff

3

Vậy một ngôn ngữ sẽ trông như thế nào nếu nó được thiết kế từ đầu để trở nên tuyệt vời với TDD?

'hoạt động tốt với TDD' chắc chắn không đủ để mô tả một ngôn ngữ, vì vậy nó có thể "trông" như mọi thứ. Lisp, Prolog, C ++, Ruby, Python ... hãy lựa chọn.

Hơn nữa, không rõ ràng rằng hỗ trợ TDD là thứ được xử lý tốt nhất bởi chính ngôn ngữ. Chắc chắn, bạn có thể tạo một ngôn ngữ trong đó mọi chức năng hoặc phương pháp đều có một bài kiểm tra liên quan và bạn có thể xây dựng để hỗ trợ cho việc khám phá và thực hiện các bài kiểm tra đó. Nhưng các khung kiểm thử đơn vị đã xử lý tốt phần khám phá và thực thi và thật khó để biết cách thêm yêu cầu kiểm tra cho mọi chức năng một cách sạch sẽ. Làm xét nghiệm cũng cần xét nghiệm? Hoặc có hai loại chức năng - loại bình thường cần kiểm tra và chức năng kiểm tra không cần chúng? Điều đó không có vẻ rất thanh lịch.

Có lẽ tốt hơn là hỗ trợ TDD với các công cụ và khung. Xây dựng nó vào IDE. Tạo một quá trình phát triển khuyến khích nó.

Ngoài ra, nếu bạn đang thiết kế một ngôn ngữ, thật tốt khi nghĩ về lâu dài. Hãy nhớ rằng TDD chỉ là một phương pháp và không phải cách làm việc ưa thích của mọi người. Có thể khó tưởng tượng, nhưng có thể những cách thậm chí tốt hơn đang đến. Là một nhà thiết kế ngôn ngữ, bạn có muốn mọi người phải từ bỏ ngôn ngữ của mình khi điều đó xảy ra không?

Tất cả những gì bạn thực sự có thể nói để trả lời câu hỏi là một ngôn ngữ như vậy sẽ có lợi cho việc kiểm tra. Tôi biết điều đó không giúp được gì nhiều, nhưng tôi nghĩ vấn đề nằm ở câu hỏi.


Đồng ý, đó là một câu hỏi rất khó để diễn đạt tốt. Tôi nghĩ rằng điều tôi muốn nói là các công cụ kiểm tra hiện tại cho các ngôn ngữ như Java / C # có cảm giác như ngôn ngữ đang xâm nhập một chút và một số tính năng ngôn ngữ bổ sung / thay thế sẽ làm cho toàn bộ trải nghiệm trở nên thanh lịch hơn (tức là không có giao diện cho 90 % các lớp học của tôi, chỉ những lớp có ý nghĩa từ quan điểm thiết kế cấp cao hơn).
Geoff

0

Vâng, các ngôn ngữ gõ động không yêu cầu giao diện rõ ràng. Xem Ruby hoặc PHP, v.v.

Mặt khác, các ngôn ngữ được nhập tĩnh như Java và C # hoặc C ++ thực thi các loại và buộc bạn phải viết các giao diện đó.

Điều tôi không hiểu là vấn đề của bạn với họ là gì. Các giao diện là một yếu tố chính của thiết kế và chúng được sử dụng trên tất cả các mẫu thiết kế và tôn trọng các nguyên tắc RẮN. Tôi, ví dụ, thường xuyên sử dụng các giao diện trong PHP vì chúng làm cho thiết kế rõ ràng và chúng cũng thực thi thiết kế. Mặt khác, trong Ruby, bạn không có cách nào để thực thi một loại, đó là một ngôn ngữ gõ vịt. Tuy nhiên, bạn vẫn phải tưởng tượng giao diện ở đó và bạn phải trừu tượng hóa thiết kế trong tâm trí của bạn để thực hiện chính xác nó.

Vì vậy, mặc dù câu hỏi của bạn nghe có vẻ thú vị, nhưng nó ngụ ý rằng bạn có vấn đề với việc hiểu hoặc áp dụng các kỹ thuật tiêm phụ thuộc.

Và để trả lời trực tiếp câu hỏi của bạn, Ruby và PHP có cơ sở hạ tầng chế nhạo tuyệt vời, cả hai được xây dựng trong các khung thử nghiệm đơn vị của chúng và được phân phối riêng (xem Mockery cho PHP). Trong một số trường hợp, các khung này thậm chí cho phép bạn thực hiện những gì bạn đang đề xuất, những thứ như chế nhạo các cuộc gọi tĩnh hoặc khởi tạo đối tượng mà không cần tiêm một cách phụ thuộc một cách rõ ràng.


1
Tôi đồng ý rằng các giao diện là tuyệt vời và là một yếu tố thiết kế quan trọng. Tuy nhiên, trong mã của tôi, tôi thấy rằng 90% các lớp có một giao diện và chỉ có hai triển khai của giao diện đó là lớp và giả của lớp đó. Mặc dù về mặt kỹ thuật chính xác là điểm giao diện, tôi không thể không cảm thấy rằng nó không phù hợp.
Geoff

Tôi không quen thuộc lắm với việc chế nhạo trong Java và C #, nhưng theo như tôi biết, một đối tượng bị chế giễu bắt chước đối tượng thực sự. Tôi thường xuyên thực hiện tiêm phụ thuộc bằng cách sử dụng một tham số của loại đối tượng và gửi một giả cho phương thức / lớp thay thế. Một cái gì đó giống như hàm someName (AnotherClass $ object = null) {$ this-> AnotherObject = $ object? : AnotherClass mới; } Đây là một mẹo thường được sử dụng để tiêm phụ thuộc mà không xuất phát từ giao diện.
Patkos Csaba

1
Đây chắc chắn là nơi các ngôn ngữ động có lợi thế hơn các ngôn ngữ loại Java / C # đối với câu hỏi của tôi. Một mô hình điển hình của một lớp cụ thể sẽ thực sự tạo ra một lớp con của lớp, điều đó có nghĩa là hàm tạo của lớp cụ thể sẽ được gọi, đây là điều bạn chắc chắn muốn tránh (có trường hợp ngoại lệ, nhưng chúng có vấn đề riêng). Một mock động chỉ tận dụng việc gõ vịt để không có mối quan hệ nào giữa lớp cụ thể và giả của nó. Tôi đã từng viết mã bằng Python rất nhiều, nhưng đó là trước ngày TDD của tôi, có lẽ đã đến lúc phải xem xét lại!
Geoff
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.