Đấu tranh với sự phụ thuộc theo chu kỳ trong các bài kiểm tra đơn vị


24

Tôi đang cố gắng thực hành TDD, bằng cách sử dụng nó để phát triển một đơn giản như Bit Vector. Tôi tình cờ sử dụng Swift, nhưng đây là một câu hỏi không biết ngôn ngữ.

My BitVectorlà một structcửa hàng lưu trữ một đĩa đơn UInt64và đưa ra một API cho phép bạn coi nó như một bộ sưu tập. Các chi tiết không quan trọng lắm, nhưng nó khá đơn giản. 57 bit cao là các bit lưu trữ và 6 bit thấp hơn là các bit "đếm", cho bạn biết có bao nhiêu bit lưu trữ thực sự lưu trữ một giá trị được chứa.

Cho đến nay, tôi có một số khả năng rất đơn giản:

  1. Bộ khởi tạo xây dựng các vectơ bit trống
  2. Một countloại tài sảnInt
  3. Một isEmptyloại tài sảnBool
  4. Một toán tử đẳng thức ( ==). NB: đây là toán tử đẳng thức giá trị gần giống với Object.equals()Java, không phải là toán tử đẳng thức tham chiếu như ==trong Java.

Tôi đang chạy vào một loạt các phụ thuộc theo chu kỳ:

  1. Bài kiểm tra đơn vị kiểm tra trình khởi tạo của tôi cần xác minh rằng phần mới được xây dựng BitVector. Nó có thể làm như vậy theo một trong 3 cách:

    1. Kiểm tra bv.count == 0
    2. Kiểm tra bv.isEmpty == true
    3. Kiểm tra xem bv == knownEmptyBitVector

    Phương pháp 1 dựa vào count, phương pháp 2 dựa vào isEmpty(chính nó dựa vào count, vì vậy không có điểm nào sử dụng nó), phương pháp 3 dựa vào ==. Trong mọi trường hợp, tôi không thể kiểm tra trình khởi tạo của mình một cách cô lập.

  2. Thử nghiệm cho countnhu cầu hoạt động trên một cái gì đó, chắc chắn sẽ kiểm tra (các) trình khởi tạo của tôi

  3. Việc thực hiện isEmptydựa vàocount

  4. Việc thực hiện ==dựa vào count.

Tôi đã có thể giải quyết một phần vấn đề này bằng cách giới thiệu API riêng xây dựng BitVectortừ một mẫu bit hiện có (dưới dạng UInt64). Điều này cho phép tôi khởi tạo các giá trị mà không cần kiểm tra bất kỳ trình khởi tạo nào khác, để tôi có thể "khởi động dây đeo" theo cách của mình.

Để các bài kiểm tra đơn vị của tôi thực sự là bài kiểm tra đơn vị, tôi thấy mình đang thực hiện một loạt các vụ hack, điều này làm phức tạp đáng kể sản phẩm và mã kiểm tra của tôi.

Làm thế nào chính xác để bạn có được xung quanh các loại vấn đề?


20
Bạn đang có một cái nhìn quá hẹp về thuật ngữ "đơn vị". BitVectorlà một kích thước đơn vị hoàn toàn tốt để thử nghiệm đơn vị và giải quyết ngay lập tức các vấn đề của bạn mà các thành viên công cộng BitVectorcần nhau để thực hiện các thử nghiệm có ý nghĩa.
Bart van Ingen Schenau

Bạn biết quá nhiều chi tiết thực hiện lên phía trước. Được phát triển của bạn thực sự Test- điều khiển ?
Herby

@herby Không, đó là lý do tại sao tôi đang thực hành. Mặc dù đó có vẻ như là một tiêu chuẩn thực sự không thể đạt được. Tôi không biết tôi đã từng lập trình bất cứ điều gì mà không có sự gần đúng về mặt tinh thần rõ ràng về những gì việc thực hiện sẽ đòi hỏi.
Alexander - Phục hồi

@Alexander Bạn nên cố gắng thư giãn điều đó, nếu không nó sẽ là thử nghiệm đầu tiên, nhưng không phải là thử nghiệm. Chỉ cần nói mơ hồ "Tôi sẽ làm một vectơ bit với một int 64 bit như một cửa hàng sao lưu" và đó là nó; từ thời điểm đó, TDD đỏ-xanh-tái cấu trúc lần lượt. Chi tiết triển khai, cũng như API, sẽ xuất hiện từ việc cố gắng làm cho các thử nghiệm chạy (cái trước) và từ việc viết các thử nghiệm đó ở vị trí đầu tiên (cái sau).
Herby

Câu trả lời:


66

Bạn đang lo lắng về chi tiết thực hiện quá nhiều.

Không thành vấn đề trong quá trình triển khai hiện tại của bạn , isEmptyphụ thuộc vào count(hoặc bất kỳ mối quan hệ nào khác mà bạn có thể có): tất cả những gì bạn nên quan tâm là giao diện công cộng. Ví dụ: bạn có thể có ba bài kiểm tra:

  • Đó là một đối tượng mới được khởi tạo có count == 0.
  • Đó là một đối tượng mới được khởi tạo có isEmpty == true
  • Đó là một đối tượng mới được khởi tạo bằng với đối tượng trống đã biết.

Đây đều là những bài kiểm tra hợp lệ và trở nên đặc biệt quan trọng nếu bạn từng quyết định cấu trúc lại các phần bên trong của lớp để isEmptycó cách triển khai khác không dựa vào count- miễn là bài kiểm tra của bạn vẫn vượt qua, bạn biết rằng bạn đã không thoái lui bất cứ điều gì

Những thứ tương tự áp dụng cho các điểm khác của bạn - hãy nhớ kiểm tra giao diện chung, chứ không phải thực hiện nội bộ của bạn. Bạn có thể thấy TDD hữu ích ở đây, vì sau đó bạn sẽ viết các bài kiểm tra bạn cần isEmptytrước khi bạn viết bất kỳ triển khai nào cho nó.


6
@Alexander Bạn có vẻ như một người đàn ông cần một định nghĩa rõ ràng về kiểm tra đơn vị. Người giỏi nhất mà tôi biết đến từ Michael Feathers
candied_orange

14
@Alexander bạn đang coi mỗi phương thức là một đoạn mã có thể kiểm tra độc lập. Đó là nguồn gốc của những khó khăn của bạn. Những khó khăn này sẽ biến mất nếu bạn kiểm tra toàn bộ đối tượng, mà không cố gắng chia nó thành các phần nhỏ hơn. Sự phụ thuộc giữa các đối tượng không thể so sánh với sự phụ thuộc giữa các phương thức.
amon

9
@Alexander "một đoạn mã" là một phép đo tùy ý. Chỉ bằng cách khởi tạo một biến bạn đang sử dụng nhiều "đoạn mã". Vấn đề là bạn đang kiểm tra một đơn vị hành vi gắn kết theo định nghĩa của bạn .
Ant P

9
"Từ những gì tôi đã đọc, tôi có ấn tượng rằng nếu bạn chỉ phá vỡ một đoạn mã, chỉ các thử nghiệm đơn vị liên quan trực tiếp đến mã đó sẽ thất bại." Đó dường như là một quy tắc rất khó tuân theo. (ví dụ: nếu bạn viết một lớp vectơ và bạn mắc lỗi về phương thức chỉ mục, bạn có thể sẽ có hàng tấn phá vỡ trên tất cả các mã sử dụng lớp vectơ đó)
jhominal

4
@Alexander Ngoài ra, hãy xem mẫu "Sắp xếp, Hành động, Khẳng định" để kiểm tra. Về cơ bản, bạn thiết lập đối tượng ở bất kỳ trạng thái nào cần có (Sắp xếp), gọi phương thức bạn thực sự đang thử nghiệm (Đạo luật) và sau đó xác minh rằng trạng thái của nó thay đổi theo mong đợi của bạn. (Khẳng định). Những thứ bạn thiết lập trong Sắp xếp sẽ là "điều kiện tiên quyết" cho bài kiểm tra.
GalacticCowboy

5

Làm thế nào chính xác để bạn có được xung quanh các loại vấn đề?

Bạn xem lại suy nghĩ của mình về "bài kiểm tra đơn vị" là gì.

Một đối tượng quản lý dữ liệu có thể thay đổi trong bộ nhớ về cơ bản là một máy trạng thái. Vì vậy, tối thiểu mọi trường hợp sử dụng có giá trị sẽ gọi một phương thức để đưa thông tin vào đối tượng và gọi một phương thức để đọc một bản sao thông tin ra khỏi đối tượng. Trong các trường hợp sử dụng thú vị, bạn cũng sẽ gọi các phương thức bổ sung thay đổi cấu trúc dữ liệu.

Trong thực tế, điều này thường trông giống như

// GIVEN
obj = new Object(...)

// THEN
assert object.read(...)

hoặc là

// GIVEN
obj = new Object(...)

// WHEN
object.change(...)

// THEN
assert object.read(...)

Thuật ngữ "kiểm tra đơn vị" - tốt, nó có một lịch sử lâu dài không được tốt lắm.

Tôi gọi chúng là các bài kiểm tra đơn vị, nhưng chúng không khớp với định nghĩa được chấp nhận của các bài kiểm tra đơn vị rất tốt - Kent Beck, Phát triển dựa trên thử nghiệm theo ví dụ

Kent đã viết phiên bản đầu tiên của SUnit vào năm 1994 , bản chuyển sang JUnit là vào năm 1998, bản thảo đầu tiên của cuốn sách TDD là đầu năm 2002. Sự nhầm lẫn này có rất nhiều thời gian để lan rộng.

Ý tưởng chính của các thử nghiệm này (được gọi chính xác hơn là "thử nghiệm lập trình viên" hoặc "thử nghiệm của nhà phát triển") là các thử nghiệm được cách ly với nhau. Các thử nghiệm không chia sẻ bất kỳ cấu trúc dữ liệu có thể thay đổi nào, vì vậy chúng có thể được chạy đồng thời. Không phải lo lắng rằng các bài kiểm tra phải được chạy theo một thứ tự cụ thể để đo lường chính xác giải pháp.

Trường hợp sử dụng chính cho các thử nghiệm này là chúng được lập trình viên chạy giữa các lần chỉnh sửa thành mã nguồn của riêng cô. Nếu bạn đang thực hiện giao thức tái cấu trúc màu xanh lục đỏ, một màu đỏ bất ngờ luôn chỉ ra lỗi trong lần chỉnh sửa cuối cùng của bạn; bạn hoàn nguyên thay đổi đó, xác minh rằng các bài kiểm tra là XANH và thử lại. Không có nhiều lợi thế trong việc cố gắng đầu tư vào một thiết kế trong đó mỗi lỗi có thể xảy ra chỉ bằng một thử nghiệm.

Tất nhiên, là một sự hợp nhất giới thiệu một lỗi, sau đó tìm ra lỗi đó không còn tầm thường nữa. Có nhiều bước khác nhau bạn có thể thực hiện để đảm bảo rằng các lỗi dễ dàng được bản địa hóa. Xem


1

Nói chung (ngay cả khi không sử dụng TDD), bạn nên cố gắng viết các bài kiểm tra càng nhiều càng tốt trong khi giả vờ bạn không biết nó được thực hiện như thế nào.

Nếu bạn thực sự đang làm TDD thì đó là trường hợp. Các bài kiểm tra của bạn là một đặc điểm kỹ thuật thực thi của chương trình.

Làm thế nào biểu đồ cuộc gọi trông bên dưới các bài kiểm tra là không liên quan, miễn là bản thân các bài kiểm tra là hợp lý và được duy trì tốt.

Tôi nghĩ vấn đề của bạn là sự hiểu biết của bạn về TDD.

Vấn đề của bạn theo ý kiến ​​của tôi là bạn đang "trộn" TDD personas của mình. "Kiểm tra", "mã" và "tái cấu trúc" của bạn hoạt động hoàn toàn độc lập với nhau, lý tưởng nhất. Cụ thể, mã hóa và tái cấu trúc của bạn không có nghĩa vụ đối với các thử nghiệm ngoài việc làm cho / giữ cho chúng chạy màu xanh lá cây.

Chắc chắn, về nguyên tắc, sẽ là tốt nhất nếu tất cả các xét nghiệm là trực giao và độc lập với nhau. Nhưng đó không phải là mối quan tâm của hai TDD personas khác của bạn và nó chắc chắn không phải là một yêu cầu nghiêm ngặt hoặc thậm chí nhất thiết phải thực tế đối với các bài kiểm tra của bạn. Về cơ bản: Đừng loại bỏ cảm giác thông thường của bạn về chất lượng mã để cố gắng thực hiện một yêu cầu mà không ai yêu cầu bạn.

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.