Làm thế nào để sửa một lỗi trong bài kiểm tra, sau khi viết


21

Quá trình hành động tốt nhất trong TDD là gì, sau khi thực hiện logic một cách chính xác, thử nghiệm vẫn thất bại (vì có một lỗi trong thử nghiệm)?

Ví dụ: giả sử bạn muốn phát triển chức năng sau:

int add(int a, int b) {
    return a + b;
}

Giả sử chúng tôi phát triển nó theo các bước sau:

  1. Kiểm tra viết (chưa có chức năng):

    // test1
    Assert.assertEquals(5, add(2, 3));
    

    Kết quả trong lỗi biên dịch.

  2. Viết một thực hiện chức năng giả:

    int add(int a, int b) {
        return 5;
    }
    

    Kết quả: test1vượt qua.

  3. Thêm một trường hợp thử nghiệm khác:

    // test2 -- notice the wrong expected value (should be 11)!
    Assert.assertEquals(12, add(5, 6));
    

    Kết quả: test2thất bại, test1vẫn vượt qua.

  4. Viết thực hiện:

    int add(int a, int b) {
        return a + b;
    }
    

    Kết quả: test1vẫn vượt qua, test2vẫn thất bại (kể từ đó 11 != 12).

Trong trường hợp cụ thể này: sẽ tốt hơn nếu:

  1. chính xác test2, và thấy rằng bây giờ nó đi qua, hoặc
  2. xóa phần thực hiện mới (tức là quay lại bước # 2 ở trên), sửa test2và để nó thất bại, sau đó giới thiệu lại cách thực hiện đúng (bước # 4. ở trên).

Hoặc có một số cách khác, thông minh hơn?

Mặc dù tôi hiểu rằng vấn đề ví dụ khá tầm thường, tôi quan tâm đến việc phải làm gì trong trường hợp chung, có thể phức tạp hơn việc cộng hai số.

EDIT (Đáp lại câu trả lời của @Thomas Junk):

Trọng tâm của câu hỏi này là những gì TDD gợi ý trong trường hợp như vậy, không phải là "thực tiễn tốt nhất phổ quát" để đạt được mã hay bài kiểm tra tốt (có thể khác với cách TDD).


3
Tái cấu trúc chống lại thanh màu đỏ là một khái niệm có liên quan.
RubberDuck

5
Rõ ràng, bạn cần phải làm TDD trên TDD của bạn.
Blrfl

17
Nếu bất cứ ai từng hỏi tôi tại sao tôi hoài nghi về TDD, tôi sẽ chỉ cho họ câu hỏi này. Đây là Kafkaesque.
Traubenfuchs

@Blrfl đó là những gì Xibit nói với chúng tôi »Tôi đã đặt TDD vào TDD để bạn có thể TDD trong khi TDDing«: D
Thomas Junk

3
@Traubenfuchs Tôi thừa nhận câu hỏi có vẻ ngớ ngẩn ngay từ cái nhìn đầu tiên và tôi không phải là người ủng hộ "làm TDD mọi lúc", nhưng tôi tin rằng có một lợi ích mạnh mẽ khi thấy một bài kiểm tra thất bại, sau đó viết mã khiến bài kiểm tra vượt qua (đó là những gì câu hỏi này thực sự là về, sau tất cả).
Vincent Savard

Câu trả lời:


31

Điều hoàn toàn quan trọng là bạn thấy bài kiểm tra cả vượt qua và thất bại.

Cho dù bạn xóa mã để làm cho bài kiểm tra thất bại, sau đó viết lại mã hoặc lẻn vào bảng tạm chỉ để dán lại sau đó không thành vấn đề. TDD không bao giờ nói bạn phải gõ lại bất cứ điều gì. Nó muốn biết bài kiểm tra chỉ vượt qua khi nó nên vượt qua và chỉ thất bại khi nó nên thất bại.

Xem bài kiểm tra cả vượt qua và thất bại là cách bạn kiểm tra bài kiểm tra. Không bao giờ tin tưởng một bài kiểm tra mà bạn chưa từng thấy làm cả hai.


Tái cấu trúc chống lại Red Bar cho chúng ta các bước chính thức để tái cấu trúc một bài kiểm tra làm việc:

  • Chạy thử
    • Lưu ý thanh màu xanh
    • Phá mã đang được thử nghiệm
  • Chạy thử
    • Lưu ý thanh màu đỏ
    • Tái cấu trúc bài kiểm tra
  • Chạy thử
    • Lưu ý thanh màu đỏ
    • Hủy bỏ mã đang được thử nghiệm
  • Chạy thử
    • Lưu ý thanh màu xanh

Tuy nhiên, chúng tôi không tái cấu trúc một bài kiểm tra làm việc. Chúng tôi phải chuyển đổi một bài kiểm tra lỗi. Một mối quan tâm là mã được giới thiệu trong khi chỉ có thử nghiệm này bao trùm nó. Mã này nên được khôi phục và giới thiệu lại sau khi thử nghiệm được cố định.

Nếu đó không phải là trường hợp và phạm vi bảo hiểm mã không phải là mối quan tâm do các thử nghiệm khác bao gồm mã, bạn có thể chuyển đổi thử nghiệm và giới thiệu thử nghiệm dưới dạng thử nghiệm xanh.

Ở đây, mã cũng đang được khôi phục nhưng chỉ đủ để làm cho bài kiểm tra thất bại. Nếu điều đó không đủ để bao gồm tất cả các mã được giới thiệu trong khi chỉ được bao phủ bởi thử nghiệm lỗi, chúng tôi cần một mã lớn hơn cuộn lại và nhiều thử nghiệm hơn.

Giới thiệu một bài kiểm tra màu xanh lá cây

  • Chạy thử
    • Lưu ý thanh màu xanh
    • Phá mã đang được thử nghiệm
  • Chạy thử
    • Lưu ý thanh màu đỏ
    • Hủy bỏ mã đang được thử nghiệm
  • Chạy thử
    • Lưu ý thanh màu xanh

Việc phá mã có thể là nhận xét mã hoặc di chuyển mã đi nơi khác chỉ để dán lại mã sau. Điều này cho chúng ta thấy phạm vi mã bao gồm các bài kiểm tra.

Trong hai lần chạy cuối cùng này, bạn quay lại chu kỳ xanh đỏ bình thường. Bạn chỉ cần dán thay vì gõ để bỏ mã và thực hiện kiểm tra. Vì vậy, hãy chắc chắn rằng bạn chỉ dán đủ để vượt qua bài kiểm tra.

Mô hình tổng thể ở đây là để xem màu sắc của thử nghiệm thay đổi theo cách chúng ta mong đợi. Lưu ý rằng điều này tạo ra một tình huống trong đó bạn có một thử nghiệm xanh không đáng tin cậy trong thời gian ngắn. Hãy cẩn thận về việc bị gián đoạn và quên mất bạn đang ở đâu trong các bước này.

Tôi cảm ơn RubberDuck vì đã liên kết Embrace the Red Bar .


2
Tôi thích câu trả lời này nhất: Điều quan trọng là xem thử nghiệm thất bại với mã không chính xác, vì vậy tôi sẽ xóa / nhận xét mã, sửa các thử nghiệm và xem chúng thất bại, đặt lại mã (có thể đưa ra lỗi cố ý để đặt thử nghiệm kiểm tra) và sửa mã để làm cho nó hoạt động. Rất cần XP để xóa và viết lại hoàn toàn, nhưng đôi khi bạn phải thực dụng. ;)
GolezTrol

@GolezTrol Tôi nghĩ rằng câu trả lời của tôi cũng nói điều tương tự, vì vậy tôi đánh giá cao bất kỳ phản hồi nào của bạn về việc điều đó không rõ ràng.
jonrsharpe

@jonrsharpe Câu trả lời của bạn cũng tốt, và tôi đã nâng cấp nó trước cả khi đọc câu này. Nhưng nơi bạn rất nghiêm khắc trong việc hoàn nguyên mã, CandiedOrange gợi ý một cách tiếp cận thực tế hơn, hấp dẫn tôi hơn.
GolezTrol

@GolezTrol Tôi không nói cách hoàn nguyên mã; bình luận, cắt và dán, bỏ nó, sử dụng lịch sử IDE của bạn; nó không thực sự quan trọng Điều quan trọng là tại sao bạn làm điều đó: vì vậy bạn có thể kiểm tra xem mình có đang thất bại đúng không. Tôi đã chỉnh sửa, hy vọng sẽ làm rõ.
jonrsharpe

10

Mục tiêu tổng thể , bạn muốn đạt được là gì?

  • Làm bài kiểm tra đẹp?

  • Làm cho việc thực hiện đúng ?

  • Làm TTD một cách tôn giáo phải không?

  • Không có cái nào ở trên?

Có lẽ bạn lật đổ mối quan hệ của bạn để kiểm tra và kiểm tra.

Các thử nghiệm không đảm bảo về tính đúng đắn của việc thực hiện. Có tất cả các bài kiểm tra vượt qua không nói gì về việc phần mềm của bạn có làm những gì cần làm hay không; nó làm cho không có tuyên bố thiết yếu về phần mềm của bạn.

Lấy ví dụ của bạn:

Việc thực hiện "chính xác" của bổ sung sẽ là mã tương đương a+b. Và miễn là mã của bạn làm điều đó, bạn sẽ nói thuật toán là chính xác trong những gì nó làm và được thực hiện chính xác .

int add(int a, int b) {
    return a + b;
}

Ngay từ cái nhìn đầu tiên , cả hai chúng tôi sẽ đồng ý rằng đây việc thực hiện bổ sung.

Nhưng những gì chúng tôi đang làm thực sự không nói, rằng mã này là việc thực hiện additionchỉ hành xử ở một mức độ nhất định như một: nghĩ về tràn số nguyên .

Tràn số nguyên không xảy ra trong mã, nhưng không phải trong khái niệm addition. Vì vậy: mã của bạn hoạt động ở một mức độ nhất định như khái niệm addition, nhưng không phải addition.

Quan điểm khá triết học này có một số hậu quả.

Và một là, bạn có thể nói, các bài kiểm tra không có gì khác hơn là các giả định về hành vi dự kiến ​​của mã của bạn. Khi kiểm tra mã của bạn, bạn có thể (có thể) không bao giờ chắc chắn, việc triển khai của bạn là đúng , điều tốt nhất bạn có thể nói là, kỳ vọng của bạn về kết quả mà mã của bạn mang lại hoặc không được đáp ứng; có thể, mã của bạn sai, có thể là, thử nghiệm của bạn sai hoặc là nó, rằng cả hai đều sai.

Các thử nghiệm hữu ích giúp bạn khắc phục những kỳ vọng của bạn về những gì mã nên làm: miễn là tôi không thay đổi kỳ vọng của mình và miễn là mã được sửa đổi mang lại cho tôi kết quả mà tôi mong đợi, tôi có thể chắc chắn, rằng các giả định mà tôi đã đưa ra kết quả có vẻ hiệu quả

Điều đó không có ích, khi bạn đưa ra các giả định sai; nhưng này ít nhất nó ngăn ngừa tâm thần phân liệt: mong đợi kết quả khác nhau khi không nên có.


tl; dr

Quá trình hành động tốt nhất trong TDD là gì, sau khi thực hiện logic một cách chính xác, thử nghiệm vẫn thất bại (vì có một lỗi trong thử nghiệm)?

Các thử nghiệm của bạn là các giả định về hành vi của mã. Nếu bạn có lý do chính đáng để nghĩ rằng việc triển khai của mình là đúng, hãy sửa bài kiểm tra và xem liệu giả định đó có đúng không.


1
Tôi nghĩ rằng câu hỏi về các mục tiêu tổng thể là khá quan trọng, cảm ơn vì đã đưa nó lên. Đối với tôi, ưu tiên cao nhất là như sau: 1. thực hiện đúng 2. các thử nghiệm "đẹp" (hoặc, tôi muốn nói là "các thử nghiệm" hữu ích "/" được thiết kế tốt "). Tôi thấy TDD là một công cụ khả thi để đạt được hai mục tiêu đó. Vì vậy, trong khi tôi không nhất thiết phải theo TDD một cách tôn giáo, trong bối cảnh của câu hỏi này, tôi chủ yếu quan tâm đến viễn cảnh TDD. Tôi sẽ chỉnh sửa câu hỏi để làm rõ điều này.
Attilio

Vì vậy, bạn sẽ viết một bài kiểm tra kiểm tra lỗi tràn và vượt qua khi nó xảy ra hay bạn sẽ làm cho nó thất bại khi nó xảy ra bởi vì thuật toán là bổ sung và tràn tạo ra câu trả lời sai?
Jerry Jeremiah

1
@JerryJeremiah Quan điểm của tôi là: Những gì xét nghiệm của bạn nên bao gồm phụ thuộc vào trường hợp sử dụng của bạn. Đối với trường hợp sử dụng khi bạn thêm một loạt các chữ số đơn, thuật toán là đủ tốt . Nếu bạn biết, rất có khả năng bạn cộng "số lớn", datatyperõ ràng đó là lựa chọn sai. Một bài kiểm tra sẽ tiết lộ rằng: kỳ vọng của bạn sẽ là »hoạt động với số lượng lớn« và trong một số trường hợp không được đáp ứng. Sau đó, câu hỏi sẽ là làm thế nào để đối phó với những trường hợp. Có phải họ là trường hợp góc? Khi có, làm thế nào để đối phó với họ? Có lẽ một số mệnh đề quard giúp ngăn chặn một mớ hỗn độn lớn hơn. Câu trả lời là bối cảnh bị ràng buộc.
Thomas Junk

7

Bạn cần biết rằng bài kiểm tra sẽ thất bại nếu việc triển khai sai, điều này không giống với việc vượt qua nếu việc thực hiện là đúng. Do đó, bạn nên đặt mã trở lại trạng thái mà bạn dự đoán sẽ thất bại trước khi sửa bài kiểm tra và đảm bảo rằng nó không thành công vì lý do bạn mong đợi (ví dụ 5 != 12), thay vì một điều gì khác mà bạn không dự đoán.


Làm thế nào chúng ta có thể kiểm tra rằng bài kiểm tra thất bại vì lý do chúng ta mong đợi?
Basilevs

2
@Basilevs bạn: 1. đưa ra một giả thuyết về lý do thất bại nên là gì; 2. chạy thử nghiệm; và 3. đọc thông báo thất bại kết quả và so sánh. Đôi khi, điều này cũng gợi ý các cách bạn có thể viết lại bài kiểm tra để cung cấp cho bạn một lỗi có ý nghĩa hơn (ví dụ: assertTrue(5 == add(2, 3))cho kết quả đầu ra ít hữu ích hơn assertEqual(5, add(2, 3))mặc dù cả hai đều kiểm tra cùng một thứ).
jonrsharpe

Hiện vẫn chưa rõ làm thế nào để áp dụng nguyên tắc này ở đây. Tôi có một giả thuyết - kiểm tra trả về một giá trị không đổi, làm thế nào để chạy lại kiểm tra một lần nữa sẽ đảm bảo tôi đúng? Rõ ràng để kiểm tra điều đó, tôi cần kiểm tra KHÁC. Tôi đề nghị thêm ví dụ rõ ràng để trả lời.
Basilevs

1
@Basilevs gì? Giả thuyết của bạn ở đây ở bước 3 sẽ là "thử nghiệm thất bại vì 5 không bằng 12" . Chạy thử nghiệm sẽ cho bạn biết liệu thử nghiệm thất bại vì lý do đó, trong trường hợp bạn tiến hành hoặc vì một lý do khác, trong trường hợp đó bạn tìm hiểu tại sao. Có lẽ đây là một vấn đề ngôn ngữ, nhưng tôi không rõ bạn đang gợi ý điều gì.
jonrsharpe

5

Trong trường hợp cụ thể này, nếu bạn thay đổi 12 thành 11 và thử nghiệm bây giờ đã qua, tôi nghĩ bạn đã hoàn thành tốt việc kiểm tra thử nghiệm cũng như thực hiện, do đó không cần phải trải qua nhiều vòng nữa.

Tuy nhiên, vấn đề tương tự có thể xảy ra trong các tình huống phức tạp hơn, chẳng hạn như khi bạn gặp lỗi trong mã thiết lập. Trong trường hợp đó, sau khi sửa bài kiểm tra của bạn, có lẽ bạn nên thử thay đổi cách thực hiện của mình theo cách đó để làm cho bài kiểm tra cụ thể đó thất bại, và sau đó hoàn nguyên đột biến. Nếu hoàn nguyên việc thực hiện là cách dễ nhất để làm điều đó, thì tốt thôi. Trong ví dụ của bạn, bạn có thể biến đổi a + bthành a + ahoặc a * b.

Ngoài ra, nếu bạn có thể thay đổi khẳng định một chút và thấy thử nghiệm thất bại, điều đó có thể khá hiệu quả trong việc kiểm tra thử nghiệm.


0

Tôi muốn nói, đây là một trường hợp cho hệ thống kiểm soát phiên bản yêu thích của bạn:

  1. Giai đoạn sửa lỗi kiểm tra, giữ cho mã của bạn thay đổi trong thư mục làm việc của bạn.
    Cam kết với một thông điệp tương ứng Fixed test ... to expect correct output.

    Với git, điều này có thể yêu cầu sử dụng git add -pnếu kiểm tra và triển khai trong cùng một tệp, nếu không, rõ ràng bạn có thể chỉ phân tách hai tệp riêng biệt.

  2. Cam kết mã thực hiện.

  3. Quay ngược thời gian để kiểm tra cam kết được thực hiện ở bước 1, đảm bảo rằng thử nghiệm thực sự thất bại .

Bạn thấy đấy, theo cách đó bạn không dựa vào năng lực chỉnh sửa của mình để di chuyển mã triển khai ra khỏi đường đi trong khi bạn kiểm tra bài kiểm tra thất bại của mình. Bạn sử dụng VCS của mình để lưu công việc của bạn và để đảm bảo rằng lịch sử được ghi lại của VCS bao gồm cả thử nghiệm thất bại và thử nghiệm vượt qua.

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.