TDD và bảo hiểm kiểm tra đầy đủ trong trường hợp cần kiểm tra theo cấp số nhân


17

Tôi đang làm việc trên một công cụ so sánh danh sách để hỗ trợ sắp xếp danh sách kết quả tìm kiếm không theo thứ tự theo yêu cầu rất cụ thể từ khách hàng của chúng tôi. Các yêu cầu yêu cầu một thuật toán phù hợp được xếp hạng với các quy tắc sau theo thứ tự quan trọng:

  1. Kết hợp chính xác về tên
  2. Tất cả các từ truy vấn tìm kiếm trong tên hoặc từ đồng nghĩa của kết quả
  3. Một số từ truy vấn tìm kiếm theo tên hoặc từ đồng nghĩa của kết quả (% giảm dần)
  4. Tất cả các từ của truy vấn tìm kiếm trong mô tả
  5. Một số từ của truy vấn tìm kiếm trong phần mô tả (% giảm dần)
  6. Ngày sửa đổi cuối cùng giảm dần

Sự lựa chọn thiết kế tự nhiên cho bộ so sánh này dường như là một thứ hạng được tính dựa trên sức mạnh của 2. Tổng các quy tắc ít quan trọng hơn không bao giờ có thể nhiều hơn một trận đấu tích cực theo quy tắc quan trọng cao hơn. Điều này đạt được bằng số điểm sau:

  1. 32
  2. 16
  3. 8 (Điểm hòa vốn thứ cấp dựa trên% giảm dần)
  4. 4
  5. 2 (Điểm hòa vốn thứ cấp dựa trên% giảm dần)
  6. 1

Theo tinh thần TDD, tôi quyết định bắt đầu với bài kiểm tra đơn vị của mình trước. Để có một trường hợp thử nghiệm cho mỗi kịch bản duy nhất sẽ có tối thiểu 63 trường hợp thử nghiệm duy nhất không xem xét các trường hợp thử nghiệm bổ sung cho logic ngắt kết nối thứ cấp theo quy tắc 3 và 5. Điều này có vẻ hống hách.

Các thử nghiệm thực tế sẽ thực sự ít hơn mặc dù. Dựa trên các quy tắc thực tế, các quy tắc nhất định đảm bảo rằng các quy tắc thấp hơn sẽ luôn đúng (Ví dụ: 'Tất cả các từ Truy vấn Tìm kiếm xuất hiện trong mô tả' thì quy tắc 'Một số từ Truy vấn Tìm kiếm xuất hiện trong mô tả' sẽ luôn đúng). Vẫn là mức độ nỗ lực trong việc viết ra từng trường hợp thử nghiệm này có đáng không? Đây có phải là mức độ kiểm tra thường được yêu cầu khi nói về phạm vi kiểm tra 100% trong TDD? Nếu không thì chiến lược thử nghiệm thay thế có thể chấp nhận là gì?


1
Kịch bản này và các kịch bản tương tự là lý do tại sao tôi đã phát triển một "TMatrixTestCase" và trình liệt kê để bạn có thể viết mã kiểm tra một lần và cung cấp cho nó hai hoặc nhiều mảng chứa đầu vào và kết quả mong đợi.
Marjan Venema

Câu trả lời:


16

Câu hỏi của bạn ngụ ý rằng TDD có liên quan đến việc "viết tất cả các trường hợp kiểm tra trước". IMHO không "theo tinh thần TDD", thực ra nó chống lại nó. Hãy nhớ rằng TDD là viết tắt của " phát triển theo hướng thử nghiệm ", vì vậy bạn chỉ cần những trường hợp thử nghiệm thực sự "thúc đẩy" việc triển khai của bạn chứ không phải nhiều hơn thế. Và miễn là việc triển khai của bạn không được thiết kế theo cách số lượng khối mã tăng theo cấp số nhân với mỗi yêu cầu mới, bạn cũng sẽ không cần số lượng trường hợp thử nghiệm theo cấp số nhân. Trong ví dụ của bạn, chu trình TDD có thể sẽ trông như thế này:

  • bắt đầu với yêu cầu đầu tiên từ danh sách của bạn: các từ có "Kết hợp chính xác trên tên" phải có điểm cao hơn mọi thứ khác
  • bây giờ bạn viết một trường hợp thử nghiệm đầu tiên cho điều này (ví dụ: một từ khớp với một truy vấn nhất định) và thực hiện số lượng mã làm việc tối thiểu làm cho thử nghiệm đó vượt qua
  • thêm trường hợp thử nghiệm thứ hai cho yêu cầu đầu tiên (ví dụ: một từ không khớp với truy vấn) và trước khi thêm trường hợp thử nghiệm mới , hãy thay đổi mã hiện tại của bạn cho đến khi thử nghiệm thứ 2 đi qua
  • tùy thuộc vào chi tiết triển khai của bạn, vui lòng thêm nhiều trường hợp kiểm tra, ví dụ: truy vấn trống, từ trống, v.v (hãy nhớ: TDD là cách tiếp cận hộp trắng , bạn có thể sử dụng thực tế là bạn biết triển khai của mình khi bạn thiết kế trường hợp thử nghiệm của bạn).

Sau đó, bắt đầu với yêu cầu thứ 2:

  • "Tất cả các từ truy vấn tìm kiếm trong tên hoặc từ đồng nghĩa của kết quả" phải có điểm thấp hơn "Kết hợp chính xác về tên", nhưng điểm cao hơn mọi thứ khác.
  • bây giờ xây dựng các trường hợp thử nghiệm cho yêu cầu mới này, giống như ở trên, từng trường hợp khác và triển khai phần tiếp theo của mã của bạn sau mỗi thử nghiệm mới. Đừng quên cấu trúc lại ở giữa, mã của bạn cũng như các trường hợp thử nghiệm của bạn.

Ở đây có một nhược điểm : khi bạn thêm các trường hợp kiểm tra cho số yêu cầu / số danh mục "n", bạn sẽ chỉ phải thêm các bài kiểm tra để đảm bảo rằng điểm của danh mục "n-1" cao hơn điểm cho danh mục "n" . Bạn sẽ không phải thêm bất kỳ trường hợp kiểm tra nào cho mọi kết hợp khác của các loại 1, ..., n-1, vì các bài kiểm tra bạn đã viết trước đó sẽ đảm bảo rằng điểm số của các danh mục đó sẽ vẫn theo đúng thứ tự.

Vì vậy, điều này sẽ cung cấp cho bạn một số trường hợp thử nghiệm phát triển xấp xỉ tuyến tính với số lượng yêu cầu, không theo cấp số nhân.


Tôi thực sự thích câu trả lời này. Nó đưa ra một chiến lược thử nghiệm đơn vị rõ ràng và súc tích để tiếp cận vấn đề này giúp TDD luôn nhớ đến. Bạn phá vỡ nó khá độc đáo.
maple_shaft

@maple_shaft: cảm ơn, và tôi thực sự thích câu hỏi của bạn. Tôi muốn nói thêm rằng tôi đoán ngay cả với cách tiếp cận của bạn là thiết kế tất cả các trường hợp thử nghiệm trước tiên, kỹ thuật cổ điển xây dựng các lớp tương đương cho các thử nghiệm có thể đủ để giảm sự tăng trưởng theo cấp số nhân (nhưng tôi đã không làm việc cho đến nay).
Doc Brown

13

Xem xét việc viết một lớp trải qua một danh sách các điều kiện được xác định trước và nhân số điểm hiện tại lên 2 cho mỗi lần kiểm tra thành công.

Điều này có thể được kiểm tra rất dễ dàng, chỉ sử dụng một vài thử nghiệm giả.

Sau đó, bạn có thể viết một lớp cho từng điều kiện và chỉ có 2 bài kiểm tra cho mỗi trường hợp.

Tôi không thực sự hiểu trường hợp sử dụng của bạn, nhưng hy vọng ví dụ này sẽ giúp ích.

public class ScoreBuilder
{
    private ISingleScorableCondition[] _conditions;
    public ScoreBuilder (ISingleScorableCondition[] conditions)
    {
        _conditions = conditions;
    }

    public int GetScore(string toBeScored)
    {
        foreach (var condition in _conditions)
        {
            if (_conditions.Test(toBeScored))
            {
                // score this somehow
            }
        }
    }
}

public class ExactMatchOnNameCondition : ISingleScorableCondition
{
    private IDataSource _dataSource;
    public ExactMatchOnNameCondition(IDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public bool Test(string toBeTested)
    {
        return _dataSource.Contains(toBeTested);
    }
}

// etc

Bạn sẽ nhận thấy rằng các bài kiểm tra điều kiện 2 ^ của bạn nhanh chóng giảm xuống còn hơn 4 điều kiện (2 *). 20 ít hơn so với 64. Và nếu bạn thêm một số khác sau đó, bạn không phải thay đổi BẤT K of các lớp hiện có (nguyên tắc đóng mở), vì vậy bạn không phải viết 64 bài kiểm tra mới, bạn chỉ cần có để thêm một lớp khác với 2 bài kiểm tra mới và đưa nó vào lớp ScoreBuilder của bạn.


Cách tiếp cận thú vị. Toàn bộ thời gian tâm trí tôi không bao giờ xem xét một phương pháp OOP vì tôi bị mắc kẹt trong tâm trí của một thành phần so sánh duy nhất. Tôi thực sự không tìm kiếm lời khuyên về thuật toán nhưng điều này rất hữu ích.
maple_shaft

4
@maple_shaft: Không, nhưng bạn đang tìm kiếm lời khuyên TDD và các loại thuật toán này là hoàn hảo để loại bỏ câu hỏi liệu nó có đáng để nỗ lực hay không, bằng cách giảm đáng kể nỗ lực. Giảm độ phức tạp là chìa khóa cho TDD.
pdr

+1, câu trả lời tuyệt vời. Mặc dù tôi tin rằng ngay cả khi không có giải pháp tinh vi như vậy, số lượng các trường hợp thử nghiệm không phải tăng theo cấp số nhân (xem câu trả lời của tôi dưới đây).
Doc Brown

Tôi không chấp nhận câu trả lời của bạn vì tôi cảm thấy một câu trả lời khác giải quyết tốt hơn câu hỏi thực tế, nhưng tôi thích cách tiếp cận thiết kế của bạn rất nhiều đến nỗi tôi đang thực hiện nó theo cách bạn đề xuất. Điều này không làm giảm sự phức tạp và làm cho nó mở rộng hơn trong thời gian dài.
maple_shaft

4

Vẫn là mức độ nỗ lực trong việc viết ra từng trường hợp thử nghiệm này có đáng không?

Bạn sẽ cần xác định "giá trị nó". Vấn đề với loại kịch bản này là các bài kiểm tra sẽ có lợi tức giảm dần về tính hữu dụng. Chắc chắn bài kiểm tra đầu tiên bạn viết sẽ hoàn toàn xứng đáng. Nó có thể tìm thấy các lỗi rõ ràng trong mức độ ưu tiên và thậm chí những thứ như phân tích lỗi khi cố gắng chia nhỏ các từ.

Thử nghiệm thứ hai sẽ có giá trị vì nó bao gồm một đường dẫn khác thông qua mã, có thể kiểm tra mối quan hệ ưu tiên khác.

Bài kiểm tra thứ 63 có thể sẽ không có giá trị vì đó là điều mà bạn tự tin 99,99% được bao phủ bởi logic của mã của bạn hoặc bài kiểm tra khác.

Đây có phải là mức độ kiểm tra thường được yêu cầu khi nói về phạm vi kiểm tra 100% trong TDD?

Hiểu biết của tôi là bảo hiểm 100% có nghĩa là tất cả các đường dẫn mã được thực hiện. Điều này không có nghĩa là bạn thực hiện tất cả các kết hợp quy tắc của mình, nhưng tất cả các đường dẫn khác nhau mà mã của bạn có thể đi xuống (như bạn chỉ ra, một số kết hợp không thể tồn tại trong mã). Nhưng vì bạn đang làm TDD, nên chưa có "mã" nào để kiểm tra đường dẫn. Thư của quá trình sẽ nói làm cho tất cả 63+.

Cá nhân, tôi thấy bảo hiểm 100% là một giấc mơ ống. Ngoài ra, nó không thực tế. Kiểm tra đơn vị tồn tại để phục vụ bạn, không phải ngược lại. Khi bạn thực hiện nhiều thử nghiệm hơn, bạn sẽ nhận được lợi nhuận giảm dần về lợi ích (khả năng thử nghiệm đó ngăn ngừa lỗi + sự tự tin rằng mã là chính xác). Tùy thuộc vào những gì mã của bạn xác định nơi quy mô trượt mà bạn dừng thực hiện kiểm tra. Nếu mã của bạn đang chạy lò phản ứng hạt nhân, thì có lẽ tất cả 63 bài kiểm tra đều đáng giá. Nếu mã của bạn đang tổ chức kho lưu trữ nhạc của bạn, thì có lẽ bạn có thể thoát khỏi với rất ít.


"phạm vi bảo hiểm" thường đề cập đến phạm vi bảo hiểm mã (mỗi dòng mã được thực thi) hoặc phạm vi bảo hiểm chi nhánh (mỗi chi nhánh được thực hiện ít nhất một lần theo bất kỳ hướng nào có thể). Đối với cả hai loại bảo hiểm, không cần 64 trường hợp thử nghiệm khác nhau. Ít nhất, không phải với một triển khai nghiêm túc không chứa các phần mã riêng lẻ cho mỗi 64 trường hợp. Vì vậy, bảo hiểm 100% là hoàn toàn có thể.
Doc Brown

@DocBrown - chắc chắn, trong trường hợp này - những thứ khác khó hơn / không thể kiểm tra; xem xét ra khỏi các đường dẫn ngoại lệ bộ nhớ. Không phải tất cả 64 được yêu cầu trong 'bằng thư' TDD để thực thi hành vi được kiểm tra không biết gì về việc thực hiện?
Telastyn

tốt, nhận xét của tôi có liên quan đến câu hỏi và câu trả lời của bạn cho cảm tưởng rằng có thể khó có được phạm vi bảo hiểm 100% trong trường hợp của OP . Tôi nghi ngờ điều đó. Và tôi đồng ý với bạn rằng người ta có thể xây dựng các trường hợp mà phạm vi bảo hiểm 100% khó đạt được hơn, nhưng điều đó không được yêu cầu.
Doc Brown

4

Tôi cho rằng đây là một trường hợp hoàn hảo cho TDD.

Bạn có một bộ tiêu chí đã biết để kiểm tra, với sự phân tích logic của những trường hợp đó. Giả sử bạn sẽ kiểm tra đơn vị chúng ngay bây giờ hoặc sau đó, có vẻ hợp lý khi lấy kết quả đã biết và xây dựng xung quanh nó, trên thực tế, đảm bảo bạn sẽ bao quát từng quy tắc một cách độc lập.

Ngoài ra, bạn có thể tìm hiểu khi bạn đi nếu thêm quy tắc tìm kiếm mới phá vỡ quy tắc hiện có. Nếu bạn làm tất cả những thứ này khi kết thúc mã hóa, có lẽ bạn sẽ gặp rủi ro lớn hơn khi phải thay đổi cái này để sửa cái này, cái này phá cái khác, cái kia phá vỡ cái khác ... Và, bạn học khi bạn thực hiện các quy tắc cho dù thiết kế của bạn có hợp lệ không hoặc cần tinh chỉnh.


1

Tôi không phải là một fan hâm mộ của việc diễn giải nghiêm ngặt phạm vi kiểm tra 100% như viết các thông số kỹ thuật đối với mọi phương thức đơn lẻ hoặc kiểm tra mọi hoán vị của mã. Làm điều này một cách điên cuồng có xu hướng dẫn đến một thiết kế hướng đến kiểm tra các lớp của bạn không gói gọn logic logic kinh doanh và mang lại các bài kiểm tra / thông số kỹ thuật nói chung là vô nghĩa về mặt mô tả logic nghiệp vụ được hỗ trợ. Thay vào đó, tôi tập trung vào cấu trúc các thử nghiệm giống như các quy tắc kinh doanh và cố gắng thực hiện mọi nhánh mã có điều kiện với các thử nghiệm với kỳ vọng rõ ràng rằng các thử nghiệm dễ hiểu bởi các thử nghiệm như các trường hợp sử dụng thường và thực sự mô tả quy tắc kinh doanh đã được thực hiện.

Với ý tưởng này, tôi sẽ kiểm tra toàn diện 6 yếu tố xếp hạng mà bạn liệt kê tách biệt với nhau theo sau với 2 hoặc 3 thử nghiệm kiểu tích hợp để đảm bảo bạn sẽ đưa kết quả của mình lên các giá trị xếp hạng tổng thể dự kiến. Ví dụ: trường hợp # 1, Kết hợp chính xác về tên, tôi sẽ có ít nhất hai bài kiểm tra đơn vị để kiểm tra khi nào chính xác và khi nào không và hai kịch bản trả về điểm số dự kiến. Nếu trường hợp của nó nhạy cảm, thì đây cũng là trường hợp để kiểm tra "Kết hợp chính xác" so với "khớp chính xác" và có thể các biến thể đầu vào khác như dấu câu, dấu cách thêm, v.v. cũng trả về điểm số dự kiến.

Khi tôi đã xử lý tất cả các yếu tố riêng lẻ đóng góp vào điểm xếp hạng, về cơ bản, tôi cho rằng các yếu tố này hoạt động chính xác ở cấp độ tích hợp và tập trung vào việc đảm bảo các yếu tố kết hợp của chúng đóng góp chính xác vào điểm xếp hạng dự kiến ​​cuối cùng.

Giả sử các trường hợp # 2 / # 3 và # 4 / # 5 được khái quát hóa cho cùng các phương thức cơ bản, nhưng chuyển các trường khác nhau vào, bạn chỉ phải viết một bộ kiểm tra đơn vị cho các phương thức cơ bản và viết các bài kiểm tra đơn vị bổ sung đơn giản để kiểm tra cụ thể các trường (tiêu đề, tên, mô tả, v.v.) và ghi điểm tại bao thanh toán được chỉ định, vì vậy điều này càng làm giảm sự dư thừa của nỗ lực kiểm tra tổng thể của bạn.

Với cách tiếp cận này, cách tiếp cận được mô tả ở trên có thể sẽ mang lại 3 hoặc 4 bài kiểm tra đơn vị cho trường hợp # 1, có lẽ 10 thông số về một số / tất cả các từ đồng nghĩa chiếm - cộng với 4 thông số về cách tính điểm chính xác của các trường hợp # 2 - # 5 và 2 đến 3 thông số kỹ thuật về xếp hạng ngày cuối cùng được sắp xếp, sau đó 3 đến 4 bài kiểm tra mức độ tích hợp đo lường tất cả 6 trường hợp được kết hợp theo các cách có thể (quên đi các trường hợp cạnh khó hiểu bây giờ trừ khi bạn thấy rõ vấn đề trong mã của mình cần được thực hiện để đảm bảo điều kiện đó được xử lý) hoặc đảm bảo không bị vi phạm / phá vỡ bởi các sửa đổi sau này. Điều đó mang lại khoảng 25 thông số kỹ thuật để thực hiện 100% mã được viết (mặc dù bạn không gọi trực tiếp 100% các phương thức được viết).


1

Tôi chưa bao giờ là một fan hâm mộ của phạm vi kiểm tra 100%. Theo kinh nghiệm của tôi, nếu một cái gì đó đủ đơn giản để kiểm tra chỉ với một hoặc hai trường hợp thử nghiệm, thì nó đủ đơn giản để hiếm khi thất bại. Khi nó không thành công, thường là do thay đổi kiến ​​trúc sẽ yêu cầu thay đổi thử nghiệm.

Điều đó đang được nói, đối với các yêu cầu như của bạn, tôi luôn kiểm tra đơn vị kỹ lưỡng, ngay cả đối với các dự án cá nhân không có ai thực hiện, vì đó là những trường hợp khi kiểm tra đơn vị giúp bạn tiết kiệm thời gian và làm nặng thêm. Càng nhiều bài kiểm tra đơn vị cần thiết để kiểm tra một cái gì đó, các bài kiểm tra đơn vị thời gian sẽ tiết kiệm hơn.

Đó là bởi vì bạn chỉ có thể giữ rất nhiều thứ trong đầu cùng một lúc. Nếu bạn đang cố gắng viết mã hoạt động cho 63 kết hợp khác nhau, thường rất khó để sửa một kết hợp mà không phá vỡ một kết hợp khác. Bạn kết thúc bằng tay kiểm tra các kết hợp khác nhiều lần. Kiểm tra thủ công chậm hơn nhiều, điều này khiến bạn không muốn chạy lại mọi kết hợp có thể mỗi khi bạn thực hiện thay đổi. Điều đó khiến bạn có nhiều khả năng bỏ lỡ điều gì đó và có nhiều khả năng lãng phí thời gian theo đuổi những con đường không hoạt động trong mọi trường hợp.

Ngoài thời gian tiết kiệm được so với kiểm tra thủ công, có sự căng thẳng về tinh thần ít hơn nhiều, giúp bạn dễ dàng tập trung vào vấn đề trong tay mà không lo lắng về việc vô tình đưa ra hồi quy. Điều đó cho phép bạn làm việc nhanh hơn và lâu hơn mà không bị kiệt sức. Theo tôi, chỉ riêng lợi ích sức khỏe tâm thần cũng đáng giá cho việc kiểm tra đơn vị mã phức tạp, ngay cả khi nó không giúp bạn tiết kiệm bất cứ lúc nào.

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.