Viết mã tối thiểu để vượt qua bài kiểm tra đơn vị - mà không gian lận!


36

Khi thực hiện TDD và viết một bài kiểm tra đơn vị, làm thế nào một người chống lại sự thôi thúc "gian lận" khi viết lần lặp đầu tiên của mã "thực thi" mà bạn đang kiểm tra?

Ví dụ:
Chúng ta cần tính Nhân tố của một số. Tôi bắt đầu với một bài kiểm tra đơn vị (sử dụng MSTest) đại loại như:

[TestClass]
public class CalculateFactorialTests
{
    [TestMethod]
    public void CalculateFactorial_5_input_returns_120()
    {
        // Arrange
        var myMath = new MyMath();
        // Act
        long output = myMath.CalculateFactorial(5);
        // Assert
        Assert.AreEqual(120, output);
    }
}

Tôi chạy mã này và nó thất bại vì CalculateFactorialphương thức thậm chí không tồn tại. Vì vậy, bây giờ tôi viết lần lặp đầu tiên của mã để thực hiện phương thức đang thử nghiệm, viết tối thiểu cần thiết để vượt qua thử nghiệm.

Vấn đề là, tôi liên tục bị cám dỗ để viết như sau:

public class MyMath
{
    public long CalculateFactorial(long input)
    {
        return 120;
    }
}

Về mặt kỹ thuật, điều này đúng ở chỗ nó thực sự là mã tối thiểu cần thiết để thực hiện bài kiểm tra cụ thể đó ( chuyển sang màu xanh lá cây), mặc dù rõ ràng đó là một "trò gian lận" vì nó thực sự thậm chí không cố gắng thực hiện chức năng tính toán giai thừa. Tất nhiên, bây giờ phần tái cấu trúc trở thành một bài tập trong "viết đúng chức năng" chứ không phải là tái cấu trúc thực sự của việc thực hiện. Rõ ràng, việc thêm các thử nghiệm bổ sung với các tham số khác nhau sẽ thất bại và buộc tái cấu trúc, nhưng bạn phải bắt đầu với thử nghiệm đó.

Vì vậy, câu hỏi của tôi là, làm thế nào để bạn có được sự cân bằng giữa "viết mã tối thiểu để vượt qua bài kiểm tra" trong khi vẫn giữ cho nó hoạt động và theo tinh thần của những gì bạn đang thực sự đạt được?


4
Đó là một điều của con người: bạn phải chống lại sự thôi thúc gian lận. Không còn gì về nó. Bạn có thể thêm nhiều bài kiểm tra và viết nhiều mã kiểm tra hơn mã để kiểm tra, nhưng nếu bạn không có sự xa xỉ đó thì bạn chỉ cần chống lại. Có NHIỀU nơi mã hóa mà chúng ta phải chống lại sự thôi thúc muốn hack hoặc gian lận, bởi vì chúng ta biết rằng, trong khi nó có thể hoạt động ngày hôm nay, nó sẽ không hoạt động sau này.
Dan Rosenstark

7
Chắc chắn, trong TDD, làm theo cách khác là gian lận - tức là trả lại 120 là cách thích hợp. Tôi thấy khá khó khăn để khiến mình làm điều đó, và không chạy đua về phía trước và bắt đầu viết tính toán giai thừa.
Paul Butcher

2
Tôi sẽ coi đây là một trò gian lận, chỉ vì nó có thể vượt qua bài kiểm tra nhưng nó không thêm bất kỳ chức năng thực sự nào hoặc giúp bạn tiến gần hơn đến một giải pháp cuối cùng cho vấn đề trong tay.
GrumpyMonkey

3
Nếu nó chỉ ra rằng mã mã máy khách chỉ bao giờ vượt qua trong 5, trả về 120 không chỉ là một trò lừa đảo, mà thực sự là một giải pháp hợp pháp.
Kramii phục hồi Monica

Tôi đồng ý với @PaulButcher - trên thực tế, rất nhiều ví dụ thử nghiệm đơn vị trong các văn bản và bài viết sẽ áp dụng phương pháp này.
HorusKol

Câu trả lời:


45

Nó hoàn toàn hợp pháp. Đỏ, Xanh, Tái cấu trúc.

Bài kiểm tra đầu tiên trôi qua.

Thêm thử nghiệm thứ hai, với một đầu vào mới.

Bây giờ nhanh chóng chuyển sang màu xanh lá cây, bạn có thể thêm một if-other, hoạt động tốt. Nó trôi qua, nhưng bạn chưa làm xong.

Phần thứ ba của Red, Green, Refactor là quan trọng nhất. Tái cấu trúc để loại bỏ trùng lặp . Bạn S have có sự trùng lặp trong mã của bạn bây giờ. Hai câu lệnh trả về số nguyên. Và cách duy nhất để loại bỏ sự trùng lặp đó là mã chính xác chức năng.

Tôi không nói rằng đừng viết chính xác ngay lần đầu tiên. Tôi chỉ nói rằng nó không lừa dối nếu bạn không.


12
Điều này chỉ đặt ra câu hỏi, tại sao không chỉ viết đúng chức năng ở vị trí đầu tiên?
Robert Harvey

8
@Robert, số giai thừa rất đơn giản. Ưu điểm thực sự của TDD là khi bạn viết các thư viện không tầm thường và viết bài kiểm tra trước tiên buộc bạn phải thiết kế API trước khi triển khai, theo kinh nghiệm của tôi - dẫn đến mã tốt hơn.

1
@Robert, chính bạn là người quan tâm đến việc giải quyết vấn đề thay vì vượt qua bài kiểm tra. Tôi đang nói với bạn rằng đối với các vấn đề không tầm thường, đơn giản là nó hoạt động tốt hơn để trì hoãn thiết kế cứng cho đến khi bạn có các thử nghiệm tại chỗ.

1
@ Thorbjørn Ravn Andersen, không, tôi không nói rằng bạn chỉ có thể có một lần trở lại. Có nhiều lý do hợp lệ cho nhiều (nghĩa là các tuyên bố bảo vệ). Vấn đề là, cả hai báo cáo trả lại là "bằng nhau". Họ đã làm cùng một "điều". Họ chỉ tình cờ có giá trị khác nhau. TDD không phải là về độ cứng và tuân thủ một kích thước cụ thể của tỷ lệ kiểm tra / mã. Đó là về việc tạo một mức độ thoải mái trong cơ sở mã của bạn. Nếu bạn có thể viết một bài kiểm tra thất bại, thì một chức năng sẽ hoạt động cho các bài kiểm tra tương lai của chức năng đó, thật tuyệt. Làm điều đó, sau đó viết các bài kiểm tra trường hợp cạnh của bạn đảm bảo chức năng của bạn vẫn hoạt động.
CaffGeek

3
điểm không viết đầy đủ (mặc dù đơn giản) ngay lập tức là sau đó bạn không có gì đảm bảo rằng các bài kiểm tra của bạn thậm chí CÓ THỂ thất bại. điểm nhìn thấy một thử nghiệm thất bại trước khi vượt qua là bạn sau đó có bằng chứng thực tế rằng sự thay đổi của bạn đối với mã là điều làm hài lòng khẳng định bạn đã thực hiện trên nó. đây là lý do duy nhất tại sao TDD rất tuyệt vời để xây dựng bộ kiểm tra hồi quy và hoàn toàn xóa sạch sàn bằng "kiểm tra sau" -approach theo nghĩa đó.
sara

25

Rõ ràng một sự hiểu biết về mục tiêu cuối cùng, và thành tựu của một thuật toán đáp ứng mục tiêu đó, là bắt buộc.

TDD không phải là một viên đạn ma thuật cho thiết kế; bạn vẫn phải biết cách giải quyết vấn đề bằng cách sử dụng mã và bạn vẫn phải biết cách thực hiện điều đó ở mức cao hơn một vài dòng mã để thực hiện kiểm tra.

Tôi thích ý tưởng của TDD vì nó khuyến khích thiết kế tốt; nó làm cho bạn suy nghĩ về cách bạn có thể viết mã của mình sao cho có thể kiểm tra được và nói chung triết lý đó sẽ đẩy mã theo hướng thiết kế tổng thể tốt hơn. Nhưng bạn vẫn phải biết cách kiến ​​trúc sư một giải pháp.

Tôi không ủng hộ các triết lý TDD theo chủ nghĩa giản lược cho rằng bạn có thể phát triển ứng dụng bằng cách viết số lượng mã nhỏ nhất để vượt qua bài kiểm tra. Không cần suy nghĩ về kiến ​​trúc, điều này sẽ không hoạt động, và ví dụ của bạn chứng minh điều đó.

Chú Bob Martin nói điều này:

Nếu bạn không thực hiện Phát triển theo hướng thử nghiệm, sẽ rất khó để tự gọi mình là chuyên gia. Jim Coplin gọi tôi trên thảm cho cái này. Anh ấy không thích điều đó. Trên thực tế, vị trí của anh ta hiện tại là Test Driven Development đang phá hủy các kiến ​​trúc bởi vì mọi người đang viết các bài kiểm tra để từ bỏ bất kỳ loại suy nghĩ nào khác và xé tan các kiến ​​trúc của họ trong cơn điên cuồng để vượt qua các bài kiểm tra và anh ta có một điểm thú vị, đó là một cách thú vị để lạm dụng nghi lễ và đánh mất ý định đằng sau kỷ luật.

nếu bạn không suy nghĩ về kiến ​​trúc, nếu những gì bạn đang làm thay vào đó là bỏ qua kiến ​​trúc và ném các bài kiểm tra lại với nhau và khiến chúng vượt qua, bạn sẽ phá hủy thứ sẽ cho phép tòa nhà đứng vững vì đó là sự tập trung vào cấu trúc của hệ thống và các quyết định thiết kế vững chắc giúp hệ thống duy trì tính toàn vẹn cấu trúc của nó.

Bạn không thể chỉ đơn giản là ném cả đống bài kiểm tra lại với nhau và khiến chúng vượt qua hết thập kỷ này đến thập kỷ khác và cho rằng hệ thống của bạn sẽ tồn tại. Chúng tôi không muốn tiến hóa thành địa ngục. Vì vậy, một nhà phát triển hướng thử nghiệm tốt luôn có ý thức đưa ra quyết định kiến ​​trúc, luôn nghĩ về bức tranh lớn.


Không thực sự là một câu trả lời cho câu hỏi, nhưng 1+
Không ai vào

2
@rmx: Ừm, câu hỏi là: Làm thế nào để bạn có được sự cân bằng giữa "viết mã tối thiểu để vượt qua bài kiểm tra" trong khi vẫn giữ cho nó hoạt động và theo tinh thần của những gì bạn đang thực sự đạt được? Có phải chúng ta đang đọc cùng một câu hỏi?
Robert Harvey

Giải pháp lý tưởng là một thuật toán và không liên quan gì đến kiến ​​trúc. Làm TDD sẽ không làm cho bạn phát minh ra các thuật toán. Tại một số điểm bạn cần thực hiện các bước về mặt thuật toán / giải pháp.
Joppe

Tôi đồng ý với @rmx. Điều này thực sự không trả lời câu hỏi cụ thể của tôi, nhưng nó sẽ làm nảy sinh thực phẩm vì suy nghĩ TDD nói chung phù hợp với bức tranh lớn của quá trình phát triển phần mềm tổng thể như thế nào. Vì vậy, vì lý do đó, +1.
CraigTP

Tôi nghĩ rằng bạn có thể thay thế "thuật toán" - và các thuật ngữ khác - cho "kiến trúc" và đối số vẫn còn; Đó là tất cả về việc không thể nhìn thấy gỗ cho cây. Trừ khi bạn sẽ viết một bài kiểm tra riêng cho mỗi đầu vào số nguyên, TDD sẽ không thể phân biệt giữa triển khai giai thừa thích hợp và một số mã hóa cứng hoạt động cho tất cả các trường hợp được kiểm tra nhưng không phải cho các trường hợp khác. Vấn đề với TDD là sự dễ dàng trong đó "tất cả các bài kiểm tra vượt qua" và "mã là tốt" được kết hợp lại. Tại một số điểm, một biện pháp nặng nề của ý thức chung cần phải được áp dụng.
Julia Hayward

16

Một câu hỏi rất hay ... và tôi phải không đồng ý với hầu hết mọi người trừ @Robert.

Viết

return 120;

đối với một chức năng giai thừa để thực hiện một bài kiểm tra là một sự lãng phí thời gian . Đó không phải là "gian lận", cũng không phải theo cấu trúc tái cấu trúc đỏ-xanh theo nghĩa đen. Đó là sai .

Đây là lý do tại sao:

  • Tính toán yếu tố là tính năng, không phải "trả về một hằng số". "Trả lại 120" không phải là một phép tính.
  • lập luận 'tái cấu trúc' là sai lầm; nếu bạn có hai trường hợp thử nghiệm cho 5 và 6, mã này vẫn là sai, bởi vì bạn không tính toán một thừa ở tất cả :

    if (input == 5) { return 120; } //input=5 case
    else { return 720; }   //input=6 case
    
  • nếu chúng ta tuân theo đối số 'refactor' theo nghĩa đen , thì khi chúng ta có 5 trường hợp thử nghiệm, chúng ta sẽ gọi YAGNI và thực hiện hàm bằng bảng tra cứu:

    if (factorialDictionary.Contains(input)) {
        return factorialDictionary[input]; 
    }
    throw new Exception("Input failure");
    

Không ai trong số này thực sự đang tính toán bất cứ điều gì, bạn đang có . Và đó không phải là nhiệm vụ!


1
@rmx: không, không bỏ lỡ nó; "Tái cấu trúc để loại bỏ trùng lặp" có thể được thỏa mãn với bảng tra cứu. BTW theo nguyên tắc kiểm tra đơn vị yêu cầu mã hóa không đặc trưng cho BDD, đó là nguyên tắc chung của Agile / XP. Nếu yêu cầu là "Trả lời câu hỏi 'giai thừa của 5' là gì thì 'trả về 120;' sẽ hợp pháp ;-)
Steven A. Lowe

2
@ Chọn tất cả trong số đó là công việc không cần thiết - chỉ cần viết chức năng lần đầu tiên ;-)
Steven A. Lowe

2
@Steven A.Lowe, theo logic đó, tại sao lại viết bất kỳ bài kiểm tra nào?! "Chỉ cần viết ứng dụng lần đầu tiên!" Điểm của TDD, là nhỏ, an toàn, thay đổi gia tăng.
CaffGeek

1
@Chad: người rơm.
Steven A. Lowe

2
điểm không viết đầy đủ (mặc dù đơn giản) ngay lập tức là sau đó bạn không có gì đảm bảo rằng các bài kiểm tra của bạn thậm chí CÓ THỂ thất bại. điểm nhìn thấy một thử nghiệm thất bại trước khi vượt qua là bạn sau đó có bằng chứng thực tế rằng sự thay đổi của bạn đối với mã là điều làm hài lòng khẳng định bạn đã thực hiện trên nó. đây là lý do duy nhất tại sao TDD rất tuyệt vời để xây dựng bộ kiểm tra hồi quy và hoàn toàn xóa sạch sàn bằng "kiểm tra sau" -approach theo nghĩa đó. bạn không bao giờ vô tình viết một bài kiểm tra mà không thể thất bại. Ngoài ra, hãy xem chú mèo nguyên tố kata.
sara

10

Khi bạn chỉ viết một bài kiểm tra đơn vị, việc triển khai một dòng ( return 120;) là hợp pháp. Viết một vòng lặp tính toán giá trị 120 - đó sẽ là gian lận!

Các thử nghiệm ban đầu đơn giản như vậy là một cách tốt để bắt các trường hợp cạnh và ngăn ngừa lỗi một lần. Năm thực sự không phải là giá trị đầu vào mà tôi bắt đầu.

Một nguyên tắc nhỏ có thể hữu ích ở đây là: không, một, nhiều, rất nhiều . Không và một là trường hợp cạnh quan trọng cho giai thừa. Chúng có thể được thực hiện với một lớp lót. Trường hợp kiểm tra "nhiều" (ví dụ 5!) Sau đó sẽ buộc bạn phải viết một vòng lặp. Trường hợp thử nghiệm "rất nhiều" (1000!?) Có thể buộc bạn phải thực hiện một thuật toán thay thế để xử lý số lượng rất lớn.


2
Trường hợp "-1" sẽ rất thú vị. Bởi vì nó không được xác định rõ ràng, vì vậy cả anh chàng viết bài kiểm tra và anh chàng viết mã phải đồng ý trước tiên những gì sẽ xảy ra.
gnasher729

2
+1 cho thực sự chỉ ra rằng đó factorial(5)là một thử nghiệm đầu tiên tồi tệ. chúng tôi bắt đầu từ các trường hợp đơn giản nhất có thể và trong mỗi lần lặp, chúng tôi làm cho các bài kiểm tra cụ thể hơn một chút, thúc giục mã trở nên chung chung hơn một chút. đây là điều mà chú bob gọi là tiền đề ưu tiên chuyển đổi ( blog.8thlight.com/uncle-bob/2013/05/27/ mẹo )
sara

5

Miễn là bạn chỉ có một bài kiểm tra duy nhất, thì mã tối thiểu cần thiết để vượt qua bài kiểm tra là thực sự return 120;và bạn có thể dễ dàng giữ bài kiểm tra đó miễn là bạn không có thêm bài kiểm tra nào nữa.

Điều này cho phép bạn hoãn thiết kế thêm cho đến khi bạn thực sự viết các bài kiểm tra thực hiện các giá trị trả về KHÁC của phương thức này.

Xin nhớ rằng bài kiểm tra là phiên bản có thể chạy được của thông số kỹ thuật của bạn và nếu tất cả thông số kỹ thuật đó nói là f (6) = 120 thì điều đó hoàn toàn phù hợp với hóa đơn.


Nghiêm túc? Theo logic này, bạn sẽ phải viết lại mã mỗi khi có ai đó đưa ra một đầu vào mới.
Robert Harvey

6
@Robert, tại MỘT SỐ điểm thêm một trường hợp mới sẽ không dẫn đến mã đơn giản nhất có thể nữa, tại thời điểm đó bạn viết một triển khai mới. Khi bạn đã có các bài kiểm tra, bạn sẽ biết chính xác thời điểm thực hiện mới của bạn giống như bài kiểm tra cũ.

1
@ Thorbjørn Ravn Andersen, chính xác, phần quan trọng nhất của Red-Green-Refactor, là tái cấu trúc.
CaffGeek

+1: Đây cũng là ý tưởng chung từ kiến ​​thức của tôi, nhưng cần phải nói một điều gì đó về việc hoàn thành hợp đồng ngụ ý (ví dụ: tên phương thức giai thừa ). Nếu bạn chỉ bao giờ spec (tức là kiểm tra) f (6) = 120 thì bạn chỉ cần 'trả về 120'. Khi bạn bắt đầu thêm các bài kiểm tra để đảm bảo rằng f (x) == x * x-1 ... * xx-1: UpperBound> = x> = 0 thì bạn sẽ đến một hàm thỏa mãn phương trình giai thừa.
Steven Evers

1
@SnOrfus, nơi dành cho "hợp đồng ngụ ý" là trong các trường hợp thử nghiệm. Nếu bạn ký hợp đồng là cho giai thừa, bạn KIỂM TRA nếu các yếu tố đã biết là và nếu không phải là yếu tố không biết. Nhiều người trong số họ. Không mất nhiều thời gian để chuyển đổi danh sách mười yếu tố đầu tiên sang thử nghiệm vòng lặp cho mỗi số cho đến giai đoạn thứ mười.

4

Nếu bạn có thể "gian lận" theo cách như vậy, điều đó cho thấy các bài kiểm tra đơn vị của bạn là thiếu sót.

Thay vì kiểm tra phương thức giai thừa với một giá trị duy nhất, hãy kiểm tra nó là một phạm vi các giá trị. Kiểm tra dựa trên dữ liệu có thể giúp đỡ ở đây.

Xem các bài kiểm tra đơn vị của bạn như một biểu hiện của các yêu cầu - chúng phải xác định chung hành vi của phương pháp mà chúng kiểm tra. (Điều này được gọi là phát triển theo hành vi - tương lai của nó ;-))

Vì vậy, hãy tự hỏi - nếu ai đó thay đổi việc thực hiện thành điều gì đó không chính xác, liệu các bài kiểm tra của bạn vẫn vượt qua hay họ sẽ nói "chờ một phút!"?

Hãy nhớ rằng, nếu bài kiểm tra duy nhất của bạn là câu hỏi trong câu hỏi của bạn, thì về mặt kỹ thuật, việc thực hiện tương ứng là chính xác. Vấn đề sau đó được xem là yêu cầu được xác định kém.


Như nanda đã chỉ ra, bạn luôn có thể thêm một loạt các casecâu lệnh vô tận vào a switchvà bạn không thể viết một bài kiểm tra cho mọi đầu vào và đầu ra có thể cho ví dụ của OP.
Robert Harvey

Bạn có thể kiểm tra kỹ thuật các giá trị từ Int64.MinValueđến Int64.MaxValue. Sẽ mất nhiều thời gian để chạy nhưng nó sẽ xác định rõ ràng yêu cầu không có chỗ cho lỗi. Với công nghệ hiện tại, điều này là không khả thi (tôi nghi ngờ rằng nó có thể trở nên phổ biến hơn trong tương lai) và tôi đồng ý, bạn có thể gian lận nhưng tôi nghĩ rằng câu hỏi của OP không phải là một câu hỏi thực tế (không ai thực sự sẽ gian lận theo cách như vậy trong thực tế), nhưng một lý thuyết.
Không ai vào

@rmx: Nếu bạn có thể làm điều đó, các bài kiểm tra sẽ là thuật toán và bạn sẽ không còn cần phải viết thuật toán nữa.
Robert Harvey

Đúng rồi. Luận án đại học của tôi thực sự liên quan đến việc tạo ra việc thực hiện tự động bằng cách sử dụng các bài kiểm tra đơn vị làm hướng dẫn với thuật toán di truyền như là một trợ giúp cho TDD - và chỉ có thể với các bài kiểm tra vững chắc. Sự khác biệt là ràng buộc các yêu cầu của bạn với mã của bạn thường khó đọc và nắm bắt hơn nhiều so với một phương pháp duy nhất thể hiện các bài kiểm tra đơn vị. Sau đó xuất hiện câu hỏi: nếu việc triển khai của bạn là biểu hiện của các bài kiểm tra đơn vị của bạn và bài kiểm tra đơn vị của bạn là biểu hiện của các yêu cầu của bạn, tại sao bạn không bỏ qua việc kiểm tra hoàn toàn? Tôi không có câu trả lời.
Không ai vào

Ngoài ra, có phải chúng ta, cũng như con người, có khả năng mắc lỗi trong các bài kiểm tra đơn vị như chúng ta đang ở trong mã thực hiện không? Vậy tại sao đơn vị kiểm tra cả?
Không ai vào

3

Chỉ cần viết thêm bài kiểm tra. Cuối cùng, nó sẽ ngắn hơn để viết

public long CalculateFactorial(long input)
{
    return input <= 1 ? 1 : CalculateFactorial(input-1)*input;
}

hơn

public long CalculateFactorial(long input)
{
    switch (input) {
       case 0: return 1;
       case 1: return 1;
       case 2: return 2;
       case 3: return 6;
       case 4: return 24;
       case 5: return 120;
    }
}

:-)


3
Ai không chỉ viết đúng thuật toán ngay từ đầu?
Robert Harvey

3
@Robert, đó là các thuật toán chính xác để tính giai thừa của một số từ 0 đến 5. Bên cạnh đó, những gì hiện "đúng" nghĩa là gì? Đây là một ví dụ rất đơn giản, nhưng khi nó trở nên phức tạp hơn, sẽ có nhiều sự thay đổi về ý nghĩa của "chính xác". Là một chương trình yêu cầu quyền truy cập root "chính xác" đủ? Là sử dụng XML "chính xác", thay vì sử dụng CSV? Bạn không thể trả lời điều này. Bất kỳ thuật toán nào cũng đúng miễn là nó thỏa mãn một số yêu cầu nghiệp vụ, được coi là các thử nghiệm trong TDD.
P Shved

3
Cần lưu ý rằng vì loại đầu ra dài, chỉ có một số lượng nhỏ các giá trị đầu vào (20 hoặc hơn) mà hàm có thể xử lý chính xác, do đó, một câu lệnh chuyển đổi lớn không nhất thiết phải là triển khai tồi tệ nhất - nếu tốc độ cao hơn quan trọng hơn kích thước mã, câu lệnh chuyển đổi có thể là cách để đi, tùy thuộc vào các ưu tiên của bạn.
dùng281377

3

Viết các bài kiểm tra "gian lận" là OK, với các giá trị "OK" đủ nhỏ. Nhưng thu hồi - kiểm tra đơn vị chỉ hoàn thành khi tất cả các bài kiểm tra vượt qua và không có bài kiểm tra mới nào có thể được viết sẽ thất bại . Nếu bạn thực sự muốn có một phương pháp CalculateFactorial có chứa một loạt các nếu báo cáo (hoặc thậm chí tốt hơn, một lớn chuyển / trường hợp tuyên bố :-) bạn có thể làm điều đó, và kể từ khi bạn giao dịch đang có một số cố định chính xác cần mã để thực hiện điều này là hữu hạn (mặc dù có thể khá lớn và xấu, và có lẽ bị giới hạn bởi trình biên dịch hoặc giới hạn hệ thống trên kích thước tối đa của mã thủ tục). Tại thời điểm này nếu bạn thực sự nhấn mạnh rằng tất cả các phát triển phải được thúc đẩy bởi một thử nghiệm đơn vị bạn có thể viết một bài kiểm tra đó đòi hỏi mã để tính toán kết quả trong một khoảng thời gian ngắn hơn mà có thể được thực hiện bằng cách làm theo tất cả các chi nhánh của nếu tuyên bố.

Về cơ bản, TDD có thể giúp bạn viết mã thực hiện các yêu cầu một cách chính xác , nhưng nó không thể buộc bạn viết mã tốt . Tùy ban.

Chia sẻ và tận hưởng.


+1 cho "kiểm tra đơn vị chỉ hoàn thành khi tất cả các bài kiểm tra vượt qua và không có bài kiểm tra mới nào có thể bị lỗi" Nhiều người đang nói rằng việc trả lại hằng số là hợp pháp, nhưng không tuân theo "trong thời gian ngắn" hoặc " nếu các yêu cầu tổng thể chỉ cần những trường hợp cụ thể đó "
Thymine

1

Tôi đồng ý 100% với đề xuất của Robert Harveys ở đây, không chỉ là làm cho các bài kiểm tra vượt qua, bạn cũng cần phải ghi nhớ mục tiêu chung.

Là một giải pháp cho điểm đau của bạn là "nó chỉ được xác minh để hoạt động với một bộ đầu vào nhất định" Tôi đề xuất sử dụng các thử nghiệm dựa trên dữ liệu, chẳng hạn như lý thuyết xunit. Sức mạnh đằng sau khái niệm này là nó cho phép bạn dễ dàng tạo Thông số kỹ thuật của đầu vào thành đầu ra.

Đối với Factorials, một bài kiểm tra sẽ như thế này:

    [Theory]
    [InlineData(0, 1)]
    [InlineData( 1, 1 )]
    [InlineData( 2, 2 )]
    [InlineData( 3, 6 )]
    [InlineData( 4, 24 )]
    public void Test_Factorial(int input, int expected)
    {
        int result = Factorial( input );
        Assert.Equal( result, expected);
    }

Bạn thậm chí có thể thực hiện cung cấp dữ liệu thử nghiệm (trả về IEnumerable<Tuple<xxx>>) và mã hóa một bất biến toán học, chẳng hạn như chia nhiều lần cho n sẽ mang lại n-1).

Tôi thấy tp này là một cách thử nghiệm rất mạnh mẽ.


1

Nếu bạn vẫn có thể gian lận thì các bài kiểm tra là không đủ. Viết thêm bài kiểm tra! Ví dụ của bạn, tôi sẽ cố gắng thêm các bài kiểm tra với đầu vào 1, -1, -1000, 0, 10, 200.

Tuy nhiên, nếu bạn thực sự cam kết gian lận, bạn có thể viết một if-then vô tận. Trong trường hợp này, không có gì có thể giúp ngoại trừ xem lại mã. Bạn sẽ sớm bị bắt trong bài kiểm tra chấp nhận ( được viết bởi người khác! )

Vấn đề với các bài kiểm tra đơn vị đôi khi các lập trình viên xem chúng là công việc không cần thiết. Cách chính xác để xem chúng là công cụ để bạn thực hiện kết quả công việc của mình chính xác. Vì vậy, nếu bạn tạo một if-then, bạn biết một cách vô thức rằng có những trường hợp khác để xem xét. Điều này có nghĩa là bạn phải viết một bài kiểm tra khác. Và cứ như vậy cho đến khi bạn nhận ra gian lận không hoạt động và tốt hơn là chỉ viết mã đúng cách. Nếu bạn vẫn cảm thấy rằng bạn chưa hoàn thành, bạn sẽ không hoàn thành.


1
Vì vậy, có vẻ như bạn đang nói rằng chỉ viết mã vừa đủ để bài kiểm tra vượt qua (như những người ủng hộ TDD) là không đủ. Bạn cũng phải ghi nhớ các nguyên tắc thiết kế phần mềm âm thanh. Tôi đồng ý với bạn BTW.
Robert Harvey

0

Tôi muốn đề nghị rằng sự lựa chọn kiểm tra của bạn không phải là bài kiểm tra tốt nhất.

Tôi sẽ bắt đầu với:

giai thừa (1) là thử nghiệm đầu tiên,

giai thừa (0) là lần thứ hai

giai thừa (-ve) là thứ ba

và sau đó tiếp tục với các trường hợp không tầm thường

và kết thúc với một trường hợp tràn.


Là gì -ve??
Robert Harvey

một giá trị âm.
Chris Cudmore
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.