Trong TDD, nếu tôi viết một trường hợp thử nghiệm vượt qua mà không sửa đổi mã sản xuất, điều đó có nghĩa là gì?


17

Đây là những quy tắc của Robert C. Martin cho TDD :

  • Bạn không được phép viết bất kỳ mã sản xuất nào trừ khi nó không vượt qua bài kiểm tra đơn vị.
  • Bạn không được phép viết thêm bất kỳ bài kiểm tra đơn vị nào là đủ để thất bại; và thất bại biên dịch là thất bại.
  • Bạn không được phép viết thêm bất kỳ mã sản xuất nào đủ để vượt qua bài kiểm tra đơn vị thất bại.

Khi tôi viết một bài kiểm tra có vẻ đáng giá nhưng vượt qua mà không thay đổi mã sản xuất:

  1. Điều đó có nghĩa là tôi đã làm điều gì sai?
  2. Tôi có nên tránh viết các bài kiểm tra như vậy trong tương lai nếu nó có thể được giúp đỡ?
  3. Tôi nên để thử nghiệm đó ở đó hoặc loại bỏ nó?

Lưu ý: Tôi đã cố gắng đặt câu hỏi này tại đây: Tôi có thể bắt đầu với bài kiểm tra đơn vị vượt qua không? Nhưng tôi không thể nói rõ câu hỏi cho đến bây giờ.


"Trò chơi Bowling Kata" được liên kết đến trong bài viết mà bạn trích dẫn thực sự có một bài kiểm tra ngay lập tức là bước cuối cùng.
jscs

Câu trả lời:


21

Nó nói rằng bạn không thể viết mã sản xuất trừ khi để vượt qua bài kiểm tra đơn vị không thành công, không phải là bạn không thể viết bài kiểm tra vượt qua được. Mục đích của quy tắc là nói "Nếu bạn cần chỉnh sửa mã sản xuất, hãy đảm bảo rằng bạn viết hoặc thay đổi thử nghiệm cho nó trước".

Đôi khi chúng tôi viết bài kiểm tra để chứng minh một lý thuyết. Bài kiểm tra vượt qua và điều đó bác bỏ lý thuyết của chúng tôi. Chúng tôi không xóa bài kiểm tra. Tuy nhiên, chúng tôi có thể (biết rằng chúng tôi có sự hỗ trợ của kiểm soát nguồn) phá vỡ mã sản xuất, để đảm bảo rằng chúng tôi hiểu lý do tại sao nó được thông qua khi chúng tôi không mong đợi nó.

Nếu nó trở thành một thử nghiệm hợp lệ và chính xác và nó không trùng lặp với một thử nghiệm hiện có, hãy để nó ở đó.


Cải thiện phạm vi kiểm tra của mã hiện tại là một lý do hoàn toàn hợp lệ khác để viết một bài kiểm tra (hy vọng) vượt qua.
Jack

13

Nó có nghĩa là:

  1. Bạn đã viết mã sản xuất đáp ứng tính năng bạn muốn mà không cần viết thử nghiệm trước (vi phạm "TDD tôn giáo") hoặc
  2. Tính năng mà bạn cần đã được mã sản xuất đáp ứng và bạn chỉ đang viết một bài kiểm tra đơn vị khác để trình bày tính năng đó.

Tình huống thứ hai là phổ biến hơn bạn nghĩ. Là một ví dụ hoàn toàn cụ thể và tầm thường (nhưng vẫn mang tính minh họa), giả sử bạn đã viết bài kiểm tra đơn vị sau đây (mã giả, vì tôi lười biếng):

public void TestAddMethod()
{
    Assert.IsTrue(Add(2,3) == 5);
}

Bởi vì tất cả những gì bạn thực sự cần là kết quả của 2 và 3 cộng lại.

Phương pháp thực hiện của bạn sẽ là:

public int add(int x, int y)
{
    return x + y;
}

Nhưng hãy nói rằng bây giờ tôi cần thêm 4 và 6 lại với nhau:

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

Tôi không cần phải viết lại phương pháp của mình, vì nó đã bao gồm trường hợp thứ hai.

Bây giờ hãy nói rằng tôi phát hiện ra rằng hàm Add của tôi thực sự cần trả về một số có trần, giả sử 100. Tôi có thể viết một phương thức mới kiểm tra điều này:

public void TestAddMethod3()
{
    Assert.IsTrue(Add(100,100) == 100);
}

Và thử nghiệm này bây giờ sẽ thất bại. Bây giờ tôi phải viết lại chức năng của mình

public int add(int x, int y)
{
    var a = x + y;
    return a > 100 ? 100 : a;
}

để làm cho nó vượt qua.

Tâm lý chung cho rằng nếu

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

vượt qua, bạn không cố tình làm cho phương thức của mình thất bại chỉ để bạn có thể có một bài kiểm tra thất bại để bạn có thể viết mã mới để thực hiện bài kiểm tra đó.


5
Nếu bạn đã làm theo đầy đủ các ví dụ của Martin (và anh ấy không nhất thiết phải đề nghị bạn làm như vậy), để add(2,3)vượt qua, bạn sẽ quay trở lại 5. Mã hóa cứng. Sau đó, bạn sẽ viết bài kiểm tra để add(4,6)buộc bạn phải viết mã sản xuất khiến nó vượt qua trong khi không phá vỡ add(2,3)cùng một lúc. Bạn sẽ kết thúc với return x + y, nhưng bạn sẽ không bắt đầu với nó. Về lý thuyết. Đương nhiên, Martin (hoặc có thể là người khác, tôi không nhớ lại) thích cung cấp các ví dụ như vậy cho giáo dục, nhưng không mong đợi bạn thực sự viết mã tầm thường như vậy.
Anthony Pegram

1
@tieTYT, nói chung, nếu tôi nhớ lại từ (các) cuốn sách của Martin một cách chính xác, trường hợp thử nghiệm thứ hai thường sẽ đủ để bạn viết giải pháp chung cho một phương pháp đơn giản (và trong thực tế, bạn thực sự sẽ làm cho nó hoạt động được lần đầu tiên). Không cần một phần ba.
Anthony Pegram

2
@tieTYT, sau đó bạn sẽ tiếp tục viết bài kiểm tra cho đến khi bạn làm. :)
Anthony Pegram

4
Có khả năng thứ ba, và nó đi ngược lại ví dụ của bạn: bạn đã viết một bài kiểm tra trùng lặp. Nếu bạn theo TDD một cách "tôn giáo", thì một bài kiểm tra mới vượt qua luôn luôn là một lá cờ đỏ. Theo DRY , bạn không bao giờ nên viết hai bài kiểm tra mà về cơ bản là giống nhau.
congusbongus

1
"Nếu bạn đã làm theo đầy đủ các ví dụ của Martin (và anh ấy không nhất thiết phải đề nghị bạn làm như vậy), để thực hiện thêm (2,3), bạn sẽ trả lại theo nghĩa đen 5. Mã hóa cứng." - đây là một chút của TDD nghiêm ngặt luôn luôn làm tôi khó chịu, ý tưởng rằng bạn viết mã mà bạn biết là sai trong kỳ vọng về một bài kiểm tra trong tương lai sẽ xuất hiện và chứng minh điều đó. Điều gì sẽ xảy ra nếu bài kiểm tra trong tương lai không bao giờ được viết, vì một số lý do và các đồng nghiệp cho rằng "all-tests-green" ngụ ý "all-code-true"?
Julia Hayward

2

Bài kiểm tra của bạn vượt qua nhưng bạn không sai. Tôi nghĩ rằng, nó đã xảy ra bởi vì mã sản xuất không phải là TDD ngay từ đầu.

Chúng ta hãy giả sử TDD (?). Không có mã sản xuất nhưng một vài trường hợp thử nghiệm (điều đó tất nhiên luôn luôn thất bại). Chúng tôi thêm mã sản xuất để vượt qua. Sau đó dừng lại ở đây để thêm trường hợp thử nghiệm thất bại. Một lần nữa thêm mã sản xuất để vượt qua.

Nói cách khác, bài kiểm tra của bạn có thể là một loại kiểm tra chức năng không phải là bài kiểm tra đơn vị TDD đơn giản. Đó luôn là tài sản quý giá cho chất lượng sản phẩm.

Cá nhân tôi không thích những quy tắc toàn trị, vô nhân đạo như vậy; (


2

Trên thực tế, vấn đề tương tự đã xảy ra trên một võ đường đêm qua.

Tôi đã làm một nghiên cứu nhanh về nó. Đây là những gì tôi nghĩ ra:

Về cơ bản, nó không bị cấm rõ ràng bởi các quy tắc TDD. Có thể một số thử nghiệm bổ sung là cần thiết để chứng minh rằng một chức năng hoạt động chính xác cho đầu vào tổng quát. Trong trường hợp này, thực hành TDD bị bỏ qua một chút. Lưu ý rằng việc rời khỏi thực hành TDD trong thời gian ngắn không nhất thiết phải phá vỡ các quy tắc TDD miễn là không có mã sản xuất được thêm vào trong thời gian đó.

Các xét nghiệm bổ sung có thể được viết miễn là chúng không dư thừa. Một thực hành tốt sẽ là làm kiểm tra phân vùng lớp tương đương. Điều đó có nghĩa là các trường hợp cạnh và ít nhất một trường hợp bên trong cho mỗi lớp tương đương được kiểm tra.

Tuy nhiên, một vấn đề có thể xảy ra với phương pháp này là nếu các bài kiểm tra vượt qua từ đầu, không thể chắc chắn rằng không có kết quả dương tính giả. Có nghĩa là có thể có các thử nghiệm vượt qua vì các thử nghiệm không được triển khai chính xác và không phải do mã sản xuất hoạt động chính xác. Để ngăn chặn điều này, mã sản xuất nên được thay đổi một chút để phá vỡ thử nghiệm. Nếu điều này làm cho thử nghiệm thất bại, rất có thể thử nghiệm được thực hiện chính xác và mã sản xuất có thể được thay đổi trở lại để làm cho thử nghiệm vượt qua một lần nữa.

Nếu bạn chỉ muốn thực hành TDD nghiêm ngặt, bạn có thể không viết bất kỳ bài kiểm tra bổ sung nào vượt qua từ đầu. Mặt khác, trong môi trường phát triển doanh nghiệp, người ta thực sự nên rời khỏi thực tiễn TDD nếu các thử nghiệm bổ sung có vẻ hữu ích.


0

Một thử nghiệm vượt qua mà không sửa đổi mã sản xuất vốn không phải là xấu và thường là cần thiết để mô tả một yêu cầu bổ sung hoặc trường hợp ranh giới. Miễn là bài kiểm tra của bạn "có vẻ đáng giá", như bạn nói là của bạn, hãy giữ nó.

Nơi bạn gặp rắc rối là khi bạn viết một bài kiểm tra đã qua để thay thế cho việc thực sự hiểu không gian vấn đề.

Chúng ta có thể tưởng tượng ở hai thái cực: một lập trình viên viết một số lượng lớn các bài kiểm tra "chỉ trong trường hợp" một người bắt lỗi; và một lập trình viên thứ hai, người cẩn thận phân tích không gian vấn đề trước khi viết một số lượng thử nghiệm tối thiểu. Giả sử cả hai đang cố gắng thực hiện một hàm giá trị tuyệt đối.

Lập trình viên đầu tiên viết:

assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...

Lập trình viên thứ hai viết:

assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1

Việc triển khai chương trình đầu tiên có thể dẫn đến:

def abs(n):
    if n < 0:
        return -n
    elif n > 0:
        return n

Việc triển khai chương trình thứ hai có thể dẫn đến:

def abs(n):
    if n < 0:
        return -n
    else:
        return n

Tất cả các bài kiểm tra đều vượt qua, nhưng lập trình viên đầu tiên không chỉ viết một số bài kiểm tra dự phòng (không cần thiết làm chậm chu kỳ phát triển của họ), mà còn thất bại trong việc kiểm tra trường hợp ranh giới ( abs(0)).

Nếu bạn thấy mình viết các bài kiểm tra vượt qua mà không sửa đổi mã sản xuất, hãy tự hỏi liệu các bài kiểm tra của bạn có thực sự tăng thêm giá trị hay bạn cần dành nhiều thời gian hơn để hiểu không gian vấn đề.


Vâng, lập trình viên thứ hai rõ ràng là bất cẩn với các bài kiểm tra, bởi vì đồng nghiệp của anh ta đã xác định lại abs(n) = n*nvà thông qua.
Eiko

@Eiko Bạn hoàn toàn đúng. Viết quá ít bài kiểm tra có thể cắn bạn cũng tệ như vậy. Các lập trình viên thứ hai đã quá keo kiệt bằng cách ít nhất là thử nghiệm abs(-2). Như với tất cả mọi thứ, điều độ là chìa khóa.
suy nghĩ
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.