2010-03-15 36 views
13

Tôi đã quản lý để có được xUnit làm việc trên lắp ráp mẫu nhỏ của tôi. Bây giờ tôi muốn xem nếu tôi có thể grok FsCheck quá. Vấn đề của tôi là tôi stumped khi nói đến xác định tính chất thử nghiệm cho các chức năng của tôi.Khó khăn suy nghĩ về tài sản cho FsCheck

Có lẽ tôi đã không có một bộ mẫu tốt các chức năng, nhưng những gì sẽ là tài sản thử nghiệm tốt cho các chức năng này, ví dụ?

//transforms [1;2;3;4] into [(1,2);(3,4)] 
pairs : 'a list -> ('a * 'a) list  //' 

//splits list into list of lists when predicate returns 
// true for adjacent elements 
splitOn : ('a -> 'a -> bool) -> 'a list -> 'a list list 

//returns true if snd is bigger 
sndBigger : ('a * 'a) -> bool (requires comparison) 

Trả lời

7

Đối với một số mã (ví dụ sndBigger), việc thực hiện rất đơn giản mà bất kỳ tài sản sẽ có ít nhất phức tạp như mã gốc, vì vậy thử nghiệm qua FsCheck có thể không có ý nghĩa. Tuy nhiên, đối với hai chức năng khác đây là một số điều mà bạn có thể kiểm tra:

  • pairs
    • Có gì mong đợi khi chiều dài ban đầu là không chia hết cho hai? Bạn có thể kiểm tra việc ném một ngoại lệ nếu đó là hành vi đúng.
    • List.map fst (pairs x) = evenEntries xList.map snd (pairs x) = oddEntries x cho các chức năng đơn giản evenEntriesoddEntries mà bạn có thể viết.
  • splitOn
    • Nếu tôi hiểu mô tả của bạn như thế nào chức năng có nghĩa vụ phải làm việc, sau đó bạn có thể kiểm tra các điều kiện như "Đối với mỗi danh sách trong kết quả của splitOn f l, không có hai mục liên tiếp đáp ứng f" và "Lấy danh sách (l1,l2) từ splitOn f l theo chiều kim đồng hồ, f (last l1) (first l2) giữ". Thật không may, logic ở đây có lẽ sẽ có thể so sánh về độ phức tạp với bản thân việc thực hiện.
+0

Cảm ơn các mẹo. Tôi đã kiểm tra ngoại lệ với xUnit, vì vậy không có giá trị gia tăng ở đó (theo như tôi có thể nói?).Tất cả dường như rất suy nghĩ cho tôi vào lúc này, và như bạn nói, trong những trường hợp cụ thể này, rủi ro kiểm tra phức tạp hơn bản gốc. – Benjol

+3

Séc không nên phức tạp hơn - điều đó thực sự đánh bại mục đích thử nghiệm. nhưng chúng có thể phức tạp như nhau, theo kinh nghiệm của tôi. Theo thời gian, việc triển khai của bạn có thể trở nên phức tạp hơn (ví dụ: tối ưu hóa), trong khi các thuộc tính/thông số thường ít nhiều giống nhau. Vì vậy, trong khi nó có thể không có ý nghĩa bây giờ, bạn có thể sẽ được hạnh phúc với các thuộc tính sau này. –

10

Tôi sẽ bắt đầu với sndBigger - đó là một chức năng rất đơn giản, nhưng bạn có thể viết một số tài sản mà nên giữ về nó. Ví dụ, những gì sẽ xảy ra khi bạn đảo ngược các giá trị trong các tuple:

// Reversing values of the tuple negates the result 
let swap (a, b) = (b, a) 
let prop_sndBiggerSwap x = 
    sndBigger x = not (sndBigger (swap x)) 

// If two elements of the tuple are same, it should give 'false' 
let prop_sndBiggerEq a = 
    sndBigger (a, a) = false 

EDIT: này quy tắc prop_sndBiggerSwap không phải lúc nào giữ (xem bình luận bởi kvb). Tuy nhiên những điều sau đây nên chính xác:

// Reversing values of the tuple negates the result 
let prop_sndBiggerSwap a b = 
    if a <> b then 
    let x = (a, b) 
    sndBigger x = not (sndBigger (swap x)) 

Về pairs chức năng, kvb đã được đăng một số ý tưởng tốt. Ngoài ra, bạn có thể kiểm tra việc chuyển danh sách được chuyển trở lại thành danh sách các phần tử trả về danh sách gốc (bạn cần xử lý trường hợp khi danh sách đầu vào là lẻ - tùy thuộc vào chức năng pairs sẽ làm trong trường hợp này):

let prop_pairsEq (x:_ list) = 
    if (x.Length%2 = 0) then 
    x |> pairs |> List.collect (fun (a, b) -> [a; b]) = x 
    else true 

Đối splitOn, chúng ta có thể kiểm tra điều tương tự - nếu bạn nối tất cả các danh sách trở lại, nó sẽ cho danh sách ban đầu (điều này không xác minh hành vi chia tách, nhưng nó là một điều tốt để bắt đầu - nó ít nhất đảm bảo rằng không có yếu tố nào sẽ bị mất).

let prop_splitOnEq f x = 
    x |> splitOn f |> List.concat = x 

Tôi không chắc chắn nếu FsCheck có thể xử lý mặc dù điều này (!) Vì tài sản mất một chức năng như một cuộc tranh cãi (vì vậy nó sẽ cần phải tạo ra "chức năng ngẫu nhiên"). Nếu điều này không hiệu quả, bạn sẽ cần phải cung cấp một vài thuộc tính cụ thể hơn với một số hàm viết tay f. Tiếp theo, thực hiện việc kiểm tra rằng f lợi nhuận đúng đối với tất cả các cặp liền kề trong danh sách tách (như kvb gợi ý) không phải là thực sự là khó khăn:

let prop_splitOnAdjacentTrue f x = 
    x |> splitOn f 
    |> List.forall (fun l -> 
     l |> Seq.pairwise 
      |> Seq.forall (fun (a, b) -> f a b)) 

Có lẽ điều cuối cùng duy nhất mà bạn có thể kiểm tra là f trả về false khi bạn cho nó phần tử cuối cùng từ một danh sách và phần tử đầu tiên từ danh sách tiếp theo. Sau đây là không hoàn toàn đầy đủ, nhưng nó cho thấy con đường để đi:

let prop_splitOnOtherFalse f x = 
    x |> splitOn f 
    |> Seq.pairwise 
    |> Seq.forall (fun (a, b) -> lastElement a = firstElement b) 

Mẫu cuối cùng cũng cho thấy rằng bạn nên kiểm tra xem splitOn chức năng có thể trả về một danh sách rỗng như một phần của danh sách trả về kết quả (vì trong trường hợp đó, bạn không thể tìm thấy phần tử đầu tiên/cuối cùng).

+0

Cảm ơn, tôi sẽ phải thử một số ý tưởng này. – Benjol

+0

Câu trả lời tuyệt vời, toàn diện. Tuy nhiên, 'prop_sndBiggerSwap' của bạn không phải lúc nào cũng giữ, vì' sndBigger (z, z) = sndBigger (hoán đổi (z, z)) 'cho tất cả' z'. – kvb

+0

@kvb: Cảm ơn - tất nhiên là bạn đã đúng! Tôi đã chỉnh sửa bài đăng để hiển thị phiên bản quy tắc đã sửa. –

14

Đã có rất nhiều câu trả lời cụ thể, vì vậy tôi sẽ cố gắng đưa ra một số câu trả lời chung có thể cung cấp cho bạn một số ý tưởng.

  1. Thuộc tính quy nạp cho các hàm đệ quy. Đối với các hàm đơn giản, số tiền này có thể tái triển khai đệ quy. Tuy nhiên, hãy đơn giản: trong khi triển khai thực tế thường xuyên hơn không phát triển (ví dụ: nó trở thành đệ quy đuôi, bạn thêm ghi nhớ, ...) giữ cho thuộc tính đơn giản. Các ==> tài sản combinator thường có ích ở đây. Chức năng cặp của bạn có thể là một ví dụ điển hình.
  2. Thuộc tính giữ nhiều chức năng trong mô-đun hoặc loại. Đây thường là trường hợp khi kiểm tra các kiểu dữ liệu trừu tượng. Ví dụ: thêm một phần tử vào một mảng có nghĩa là mảng chứa phần tử đó. Điều này kiểm tra tính nhất quán của Array.add và Array.contains.
  3. Chuyến đi khứ hồi: điều này phù hợp cho chuyển đổi (ví dụ: phân tích cú pháp, tuần tự hóa) - tạo đại diện tùy ý, tuần tự hóa, deserialize nó, kiểm tra xem nó bằng nguyên bản hay không. Bạn có thể làm điều này với splitOn và concat.
  4. Thuộc tính chung làm kiểm tra độ chính xác. Hãy tìm các thuộc tính thường được biết đến có thể giữ - những thứ như giao thoa, tính liên kết, tính ngẫu nhiên (áp dụng một cái gì đó hai lần không thay đổi kết quả), phản xạ, vv Ý tưởng ở đây là tập thể dục nhiều hơn một chút - xem nó có thực sự lạ không .

Là một lời khuyên chung, hãy cố gắng không tạo ra một thỏa thuận quá lớn. Đối với sndBigger, một thuộc tính tốt sẽ là:

để `` trả về true nếu và chỉ khi snd lớn hơn`` (a: int) (b: int) = sndBigger (a, b) = b> a

Và đó có thể chính xác là việc triển khai. Đừng lo lắng về nó - đôi khi một đơn giản, thử nghiệm đơn vị cũ thời chỉ là những gì bạn cần. Không có tội lỗi cần thiết! :)

Có thể this link (bởi nhóm Pex) cũng đưa ra một số ý tưởng.

+0

Cảm ơn lời khuyên. – Benjol

+0

Liên kết đã cho đã chết. Liên kết đã sửa có thể là http://research.microsoft.com/en-us/projects/pex/patterns.pdf – PetPaulsen

+0

Cảm ơn - đã cập nhật liên kết. –

Các vấn đề liên quan