2015-09-25 29 views
5

Tôi muốn thực hiện một số thử nghiệm tương tự về các loại khác nhau trong thư viện của mình.HTF không kiểm tra các đạo cụ được tạo bởi TH

Để đơn giản hóa mọi thứ, giả sử tôi có một số loại véc tơ đang triển khai lớp Num và tôi muốn tạo cùng một thuộc tính QuickCheck kiểm tra prop_absNorm x y = abs x + abs y >= abs (x+y) sẽ hoạt động trên tất cả các loại trong thư viện.

tôi tạo ra tài sản đó sử dụng TH:

$(writeTests 
    (\t -> 
     [d| prop_absNorm :: $(t) -> $(t) -> Bool 
      prop_absNorm x y = abs x + abs y >= abs (x+y) 
     |]) 
) 

chức năng của tôi để tạo ra các bài kiểm tra có chữ ký sau đây:

writeTests :: (TypeQ -> Q [Dec]) -> Q [Dec] 

Chức năng này sẽ tìm kiếm tất cả các trường của lớp vector của tôi VectorMath (n::Nat) t (và, tại đồng thời, các trường hợp của Num) thông qua reify ''VectorMath và tạo tất cả các chức năng chống sao cho phù hợp. -ddump-splices cho thấy một cái gì đó như thế này:

prop_absNormIntX4 :: Vector 4 Int -> Vector 4 Int -> Bool 
prop_absNormIntX4 x y = abs x + abs y >= abs (x+y) 
prop_absNormCIntX4 :: Vector 4 CInt -> Vector 4 CInt -> Bool 
prop_absNormCIntX4 x y = abs x + abs y >= abs (x+y) 
... 
prop_absNormFloatX4 :: Vector 4 Float -> Vector 4 Float -> Bool 
prop_absNormFloatX4 x y = abs x + abs y >= abs (x+y) 
prop_absNormFloatX3 :: Vector 3 Float -> Vector 3 Float -> Bool 
prop_absNormFloatX3 x y = abs x + abs y >= abs (x+y) 

Vấn đề là tất cả các thuộc tính bằng tay bằng văn bản được kiểm tra, nhưng những người tạo ra thì không.

Lưu ý 1: Tôi đã tạo và không tạo thuộc tính trong cùng một tệp (tức là biểu thức TH $(..) có cùng tệp với các đạo cụ khác).

Lưu ý 2: danh sách các loại để tạo chức năng prop thay đổi - Tôi muốn thêm các phiên bản khác của VectorMath sau đó, vì vậy chúng sẽ tự động được thêm vào danh sách kiểm tra.

Tôi tin rằng vấn đề là HTF (có lẽ sử dụng TH quá) phân tích cú pháp tệp gốc, không phải tệp có mã được tạo - nhưng tôi không thể hiểu tại sao điều này xảy ra.

Vì vậy, câu hỏi của tôi là: cách giải quyết vấn đề này? Nếu không thể sử dụng đạo cụ tạo ra TH, thì có thể thực hiện các kiểm tra QuickCheck trên các loại khác nhau (tức là nó thay thế chúng thành prop_absNorm :: Vector 4 a -> Vector 4 a -> Bool) không?

Một giải pháp thay thế khác có thể là sử dụng TH để thêm các mục kiểm tra theo cách thủ công vào htf_Main, nhưng tôi chưa tìm ra cách thực hiện điều này; và nó không giống như một giải pháp sạch đẹp.

Trả lời

1

Ok, tôi đã giải quyết được vấn đề này. Ý tưởng là sử dụng TH để tổng hợp các thử nghiệm và chèn chúng vào htfMain. Trên hết những gì tôi có trong câu hỏi, điều này bao gồm các bước sau:

  1. Chuyển đổi tất cả các thuộc tính kiểm chứng vào IO hành động chạy thử nghiệm QuickCheck;
  2. Tổng hợp tất cả các thử nghiệm thành TestSuite;
  3. Tổng hợp tất cả các dãy phòng thử nghiệm thành một danh sách và đặt nó vào htfMain.

Để sử dụng bước 1, tôi phải sử dụng chức năng bán nội bộ của HTF được gọi là qcAssertion :: (QCAssertion t) => t -> Assertion. Chức năng này có sẵn, nhưng không được khuyến khích sử dụng bên ngoài; nó cho phép chạy kiểm tra QuickCheck độc đáo, tích hợp chúng vào báo cáo.

Để tiếp tục với bước 2, tôi sử dụng hai chức năng từ HTF: makeTestSuitemakeQuickCheckTest. Tôi cũng sử dụng chức năng location từ TH để cung cấp tên tệp và dòng của vị trí nơi ghép nối với mẫu thử được chèn (đối với nhật ký thử nghiệm đẹp hơn).

Bước 3 là một điều khó khăn: vì điều này, chúng tôi cần tìm tất cả các bộ thử nghiệm đã tạo. Vấn đề là TH không cho phép duyệt qua tất cả các chức năng (kể cả được tạo) trong một mô-đun. Để khắc phục điều này, tôi đã thêm lớp loại sau đây:

class MultitypeTestSuite name where 
    multitypeTestSuite :: name -> TestSuite 

Vì vậy, chức năng của tôi writeTests tạo ra một kiểu dữ liệu mới data MTS[prop_name] và một thể hiện của MultitypeTestSuite cho rằng kiểu dữ liệu. Điều này cho phép tôi sau này sử dụng một chức năng hàn trong htfMain mà sẽ tạo ra một danh sách các dãy phòng thử nghiệm ra khỏi trường hợp của lớp đó sử dụng reify:

aggregateTests :: ExpQ 
aggregateTests = do 
    ClassI _ instances <- reify ''MultitypeTestSuite 
    liftM ListE . forM instances 
      $ \... -> [e| multitypeTestSuite $(...) |] 

Cuối cùng, bao gồm tất cả các bài kiểm tra được tạo ra cùng với những ngoại hình bằng tay bằng văn bản khá đơn giản:

main :: IO() 
main = htfMain $ htf_importedTests ++ $(aggregateTests) 

vì vậy, bằng cách điều chỉnh chức năng $(writeTests) tôi có thể hiện nay để tạo ra và tính thử nghiệm khác nhau về kiểu đối số - cho tất cả các loại có sẵn trong phạm vi cùng loại. Kết quả thử nghiệm và nhật ký được bao gồm giống như cách kiểm tra ban đầu.

Trên đó, sự cố được giải quyết hoàn toàn.

+0

Đây là giải pháp đầy đủ, nếu ai đó quan tâm https://github.com/achirkin/fastvec/tree/master/test/VectorTests – artem

3

Nếu bạn biết trước những gì tên của các bài kiểm tra tài sản được tạo ra đều, sau đó bạn có thể luôn tự xác định khai để HTF nhìn thấy chúng, ví dụ:

$(generate prop test for Int) 
$(generate prop test for CInt) 

prop_p1 = prop_absNormInt 
prop_p2 = prop_absNormCInt 

HTF sẽ thấy các bài kiểm tra như prop_p1prop_p2 . Bạn không cần phải đặt chữ ký kiểu trên các cuống này.

Một ý tưởng khác là tạo bộ tiền xử lý nguồn của riêng bạn để thêm những phần này cho bạn (và cung cấp cho họ tên tốt hơn). Bộ xử lý trước nguồn của bạn sẽ tự động gọi htfpp để hoàn tất quá trình xử lý trước.

Nếu bạn chỉ cho tôi cách TH của bạn được gọi, tôi có thể hướng dẫn bạn cách viết bộ xử lý trước.

Cập nhật:

Với nhận xét của bạn tôi sẽ xem xét cách làm như sau:

  1. Viết một chương trình để tạo ra các nguồn mô-đun thử nghiệm.
  2. Bao gồm chương trình đó và đầu ra nó tạo ra trong dự án cabal của bạn.
  3. Yêu cầu người dùng chạy chương trình nếu họ muốn cập nhật mô-đun thử nghiệm.

Vì vậy - các trường hợp thử nghiệm vẫn cố định cho đến khi chương trình được chạy để tạo lại mô-đun thử nghiệm.

Có mô-đun thử nghiệm tĩnh có lợi thế là bạn có thể biết chính xác những gì đang được thử nghiệm.

Có một chương trình để tạo lại mô-đun thử nghiệm cho phép bạn dễ dàng cập nhật khi có thể có các phiên bản Num mới.

+0

Cảm ơn bạn, tôi đã thêm nhiều thông tin hơn vào câu hỏi của mình. Tôi cố gắng tránh chỉ định danh sách các loại để kiểm tra rõ ràng – artem

+0

Trả lời câu trả lời với đề xuất của tôi. – ErikR

+0

Tôi thấy bạn quan điểm - rất hợp lý. Tuy nhiên, tôi không sợ các bài kiểm tra được tạo động, bởi vì trong các bài kiểm tra được tạo cuối cùng giống hệt nhau, chỉ khác nhau về kiểu đối số. Vì vậy, tôi thấy như là một thử nghiệm, chạy trên các loại khác nhau. Nhưng những gì tôi thực sự không hiểu là Tại sao HTF không nhận được các bài kiểm tra do TH tạo ra? Thay vào đó, tôi muốn tìm một giải pháp (thông qua TH hoặc HTF) để bao gồm các bài kiểm tra được tạo thủ công vào htf_Main. – artem

1

HTF không sử dụng TemplateHaskell để thu thập các thử nghiệm, điều này sẽ làm chậm đáng kể thời gian biên dịch. Thay vào đó, HTF sử dụng một bộ tiền xử lý tùy chỉnh được gọi là htfpp. htfpp chạy trước trình biên dịch (và do đó trước khi các kết nối TemplateHaskell được mở rộng). Điều này có nghĩa là bạn không thể sử dụng khám phá thử nghiệm tự động với htfpp khi tạo thử nghiệm của bạn với TemplateHaskell.

Đề xuất của tôi: khi bạn đang sử dụng TemplateHaskell, hãy sử dụng TemplateHaskell để thu thập các trường hợp thử nghiệm đã tạo của bạn. Chức năng này không được tích hợp vào HTF, nhưng không khó để thực hiện một chức năng như vậy. Dưới đây là nó:

-- file TH.hs 
{-# LANGUAGE TemplateHaskell #-} 
module TH (genTestSuiteFromQcProps) where 

import Language.Haskell.TH 

import Test.Framework 
import Test.Framework.Location 

genTestSuiteFromQcProps :: String -> [Name] -> Q Exp 
genTestSuiteFromQcProps suiteName names = 
    [| makeTestSuite $(stringE suiteName) $(listE genTests) |] 
    where 
     genTests :: [ExpQ] 
     genTests = 
      map genTest names 
     genTest :: Name -> Q Exp 
     genTest name = 
      [| makeQuickCheckTest $(stringE (show name)) unknownLocation 
       (qcAssertion $(varE name)) |] 

Chức năng genTestSuiteFromQcProps mất tên của bộ ứng dụng thử nghiệm để tạo ra và một danh sách tên, đề cập đến tính chất QC của bạn. genTestSuiteFromQcProps trả lại một biểu thức thuộc loại TestSuite. TestSuite là một trong các loại HTF sử dụng để tổ chức kiểm tra. (The htfpp als tiền xử lý sử dụng các loại TestSuite trong sản lượng của nó.)

Đây là cách bạn có rừng sử dụng genTestSuiteFromQcProps:

-- file Main.hs 
{-# OPTIONS_GHC -F -pgmF htfpp #-} 
{-# LANGUAGE TemplateHaskell #-} 
module Main where 

import TH 
import Test.Framework 

import {[email protected] HTF_TESTS @-} OtherTests 

prop_additionCommutative :: Int -> Int -> Bool 
prop_additionCommutative x y = (x + y) == (y + x) 

prop_reverseReverseIdentity :: [Int] -> Bool 
prop_reverseReverseIdentity l = l == reverse (reverse l) 

myTestSuite :: TestSuite 
myTestSuite = 
    $(genTestSuiteFromQcProps 
     "MyTestSuite" 
     ['prop_additionCommutative 
     ,'prop_reverseReverseIdentity]) 

main :: IO() 
main = htfMain (myTestSuite : htf_importedTests) 

Đối với trường hợp của bạn, bạn sẽ vượt qua genTestSuiteFromQcProps tên của các thuộc tính QC bạn tạo ra với TemplateHaskell.

Ví dụ này cũng cho thấy rằng bạn có thể trộn các trường hợp thử nghiệm được tạo bằng hàm TemplateHaskell với các trường hợp kiểm tra được thu thập bởi htfpp. Để có đầy đủ, đây là nội dung của OtherTests:

{-# OPTIONS_GHC -F -pgmF htfpp #-} 
module OtherTests (htf_thisModulesTests) where 

import Test.Framework 

test_someOtherTest :: IO() 
test_someOtherTest = 
    assertEqual 1 1 
+0

Cảm ơn! Có vẻ như chúng tôi đã đưa ra các giải pháp tương tự. Vấn đề duy nhất ở đây là sau đó làm thế nào để khám phá bằng cách sử dụng tất cả các chức năng được tạo ra trong các mối nối khác nhau (có thể sau này trong các tập tin khác nhau).Tôi không thể làm gì tốt hơn là đưa tất cả chúng vào trường hợp của lớp được xác định trước 'MultitypeTestSuite' và sau đó kiểm tra lớp này bằng cách sử dụng một mối nối khác trong' main'. – artem

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