2015-02-18 17 views
7

trong lập trình hướng đối tượng tôi có đối tượng và trạng thái. vì vậy tôi có thể giả lập tất cả các phụ thuộc của một đối tượng và kiểm tra đối tượng. nhưng chức năng lập trình (đặc biệt là thuần túy) là về chức năng soạn thảothử nghiệm trong lập trình chức năng

thật dễ dàng để kiểm tra chức năng không phụ thuộc vào các chức năng khác. chúng ta chỉ cần truyền tham số và kiểm tra kết quả. nhưng những gì về chức năng mà có chức năng khác và trả về chức năng?

giả sử tôi có mã g = h1 ∘ h2 ∘ h3 ∘ h4. tôi có nên kiểm tra chức năng chỉ g? nhưng đó là thử nghiệm tích hợp/chức năng. không thể kiểm tra tất cả các chi nhánh chỉ với các bài kiểm tra tích hợp. những gì về thử nghiệm đơn vị? và nó trở nên phức tạp hơn khi một hàm lấy nhiều tham số hơn.

tôi có nên tạo các chức năng tùy chỉnh và sử dụng chúng làm mocks không? nó sẽ không đắt tiền và dễ bị lỗi?

và về monads thì sao? ví dụ làm thế nào để kiểm tra đầu ra console hoặc hoạt động đĩa trong haskell?

+4

Điều gì làm cho bạn nghĩ rằng các chức năng tùy chỉnh như mocks là bất kỳ đắt tiền hơn hoặc dễ bị lỗi hơn các đối tượng OO? – leftaroundabout

+0

tôi thì không. đó là lý do tại sao tôi hỏi – piotrek

+2

Tại sao bạn muốn thử một chức năng thuần túy? –

Trả lời

3

Trong ví dụ của bạn, bạn có thể kiểm tra riêng h1, h2, h3 và h4, không có vấn đề gì, bởi vì chúng không thực sự phụ thuộc lẫn nhau. Không có gì ngăn cản bạn thử nghiệm g. Nhưng g là một 'đơn vị'? Một định nghĩa rất tốt về một bài kiểm tra đơn vị được đưa ra bởi Michael Feathers trong cuốn sách thử nghiệm đơn vị nổi tiếng của ông, Working Workingively With Legacy Code. Ông nói rằng các bài kiểm tra đơn vị rất nhanh và đáng tin cậy để chạy trong giai đoạn cam kết của đường ống xây dựng của bạn và đủ nhanh để các nhà phát triển chạy. Vì vậy, g là một 'đơn vị' bằng thước đo này. Quan điểm tuyệt vời khác về thử nghiệm đơn vị là từ kiến ​​trúc lục giác, xem TDD ở đâu đã làm tất cả đi sai? Họ nói rằng bạn muốn kiểm tra API của ứng dụng thông qua 'cổng' mà nó sử dụng để giao tiếp với thế giới bên ngoài. G của bạn cũng là một đơn vị theo định nghĩa này. Nhưng chúng có ý nghĩa gì với 'cổng' và chúng ta có thể liên hệ điều này với Haskell không? Một cổng điển hình có thể là kết nối cơ sở dữ liệu mà ứng dụng sử dụng để lưu trữ mọi thứ trong cơ sở dữ liệu. Trong Lục giác, bạn sẽ muốn kiểm tra giao diện đó, có thể là do mô hình. Trong thuật ngữ Haskell, cốt lõi của ứng dụng là mã thuần túy và các cổng là IO. Vấn đề là, bạn muốn giới thiệu 'seams' của bạn (chẳng hạn như mocks) tại giao diện IO. Vì vậy, bạn có thể không muốn lo lắng về việc tách g.

Nhưng làm thế nào để bạn giới thiệu 'đường nối' để thử nghiệm trong Haskell? Sau khi tất cả, không có khuôn khổ tiêm phụ thuộc (và cũng không nên có). Vâng, câu trả lời cho điều này là, như mọi khi trong Haskell, để sử dụng các hàm và tham số hóa. Ví dụ, giả sử bạn có hàm foo được định nghĩa trong điều khoản của thanh chức năng. Bạn muốn thay đổi thanh vì vậy nó là một thử nghiệm tăng gấp đôi trong thử nghiệm của bạn và giá trị thường xuyên phần còn lại của thời gian. Chỉ cần tạo thanh tham số như sau:

Module Foo 
foo bar = ... bar ... 

Module Test 
foo = Foo.foo testBar 

Module Real 
    foo = Foo.foo realBar 

Bạn không cần phải thực hiện chính xác như vậy nhưng điểm là tham số giúp bạn vượt xa hơn bạn nghĩ.

Được rồi, nhưng thử nghiệm IO trong Haskell thì sao? Làm thế nào để chúng ta 'mock out' những hành động IO? Một cách là làm như bạn làm trong JavaScript: tạo cấu trúc dữ liệu đầy đủ các hành động IO (chúng gọi chúng là 'các đối tượng' ;-)) và truyền chúng xung quanh. Một cách khác là không sử dụng trực tiếp loại IO mà thay vào đó truy cập nó thông qua một trong hai loại đơn nguyên - loại thực và loại thử nghiệm là cả hai thể hiện cùng loại lớp xác định các hành động bạn muốn hoán đổi. Hoặc bạn có thể tạo một Monad miễn phí (sử dụng các gói miễn phí hoặc hoạt động) và viết hai thông dịch viên - một thử nghiệm và thực tế.

Tóm lại, kiểm tra mã thuần túy thật dễ dàng đến nỗi thực tế mọi thứ bạn thử sẽ hoạt động. Kiểm tra mã IO là khó hơn, đó là lý do tại sao chúng tôi cô lập nó càng nhiều càng tốt.

+1

liên quan đến thử nghiệm 'g'. mối quan tâm của tôi là: nếu mỗi 'h' có một số nhánh thì' g' có nhiều luồng thực thi có thể để kiểm tra tất cả chúng.đó là vấn đề với các bài kiểm tra tích hợp - với nhiều khả năng – piotrek

+2

Cũng trong ví dụ của bạn, bạn đã thử nghiệm h1, h2, h3 và h4, vì vậy thử nghiệm g thực sự chỉ là thử nghiệm bạn đã tạo chúng đúng. Bạn có thể làm điều đó chỉ với một vài giá trị mẫu. Bạn đã đề cập đến các chi nhánh. Trong Haskell, các kiểu có nhánh, không chỉ các hàm. Cấu trúc phân nhánh của một hàm phản ánh các trường hợp của dữ liệu mà nó hoạt động. Điều này làm cho nó dễ dàng hơn nhiều để biết những giá trị để kiểm tra. Và như toán học Orchid nói, Quickcheck và Smallcheck có thể tạo ra rất nhiều giá trị đó, cho bạn độ bao phủ tốt hơn nhiều so với bạn sẽ có. – GarethR

+0

Tôi đã viết một hướng dẫn về cách bạn có thể đạt được đề xuất thứ hai của bạn để thử nghiệm mã IO bằng cách sử dụng một ví dụ thế giới thực: https://blog.pusher.com/unit-testing-io-in-haskell/ –

3

Tôi cũng đã suy nghĩ về thử nghiệm trong mã chức năng.Tôi không có tất cả các câu trả lời, nhưng tôi sẽ viết một chút ở đây.

Các chương trình chức năng được đặt cùng nhau khác nhau và yêu cầu các phương pháp thử nghiệm khác nhau.

Nếu bạn có ngay cả những cái nhìn cursory nhất tại Haskell thử nghiệm, bạn chắc chắn sẽ đi qua QuickCheck và SmallCheck, hai thư viện kiểm tra Haskell rất nổi tiếng. Cả hai đều làm "thử nghiệm dựa trên thuộc tính". Trong một ngôn ngữ OO, bạn sẽ cố gắng viết các bài kiểm tra riêng lẻ để thiết lập nửa tá đối tượng giả, gọi một hoặc hai phương thức, và xác minh rằng các phương thức bên ngoài dự kiến ​​được gọi với dữ liệu đúng và/hoặc phương thức cuối cùng trả về câu trả lời đúng. Đó là một chút công việc. Bạn có thể chỉ làm điều này với một hoặc hai trường hợp thử nghiệm.

QuickCheck là thứ khác. Bạn có thể viết một thuộc tính có nội dung "nếu tôi sắp xếp danh sách này, đầu ra phải có cùng số phần tử như đầu vào". Đây là một lớp lót. Thư viện QuickCheck sau đó sẽ tự động tự động tạo hàng trăm danh sách được tạo ngẫu nhiên và kiểm tra xem điều kiện được chỉ định có giữ được cho từng danh sách đơn lẻ hay không. Và nếu không, nó sẽ nhổ ra đầu vào chính xác mà thử nghiệm không thành công.

(Cả hai QuickCheck và SmallCheck làm khoảng điều tương tự. QuickCheck tạo ngẫu nhiên kiểm tra, trong khi SmallCheck có hệ thống cố gắng tất cả các kết hợp lên đến một giới hạn kích thước quy định.)

Bạn nói rằng bạn đang lo lắng về sự kết hợp của các đường dẫn kiểm soát dòng chảy có thể kiểm tra, nhưng với các công cụ như thế này tạo ra các trường hợp thử nghiệm cho bạn một cách năng động, viết thủ công đủ các kiểm thử không phải là một vấn đề. Vấn đề chỉ sắp xuất hiện với đủ dữ liệu để kiểm tra tất cả các đường dẫn.

Haskell cũng có thể trợ giúp điều đó. Tôi đọc một bài báo về một thư viện [Tôi không biết nếu nó đã được phát hành] mà thực sự sử dụng đánh giá lười biếng của Haskell để phát hiện những gì các mã theo thử nghiệm đang làm với các dữ liệu đầu vào. Như trong, nó có thể phát hiện xem chức năng bạn đang thử nghiệm có nhìn vào nội dung của một danh sách hay chỉ kích thước của danh sách đó. Nó có thể phát hiện các trường nào của hồ sơ Khách hàng đang được chạm vào. Và kể từ đó trở đi. Bằng cách này, nó tự động tạo dữ liệu, nhưng không lãng phí giờ tạo ra các biến thể ngẫu nhiên khác nhau của các phần dữ liệu thậm chí không có liên quan đến mã cụ thể này. (Ví dụ: nếu bạn sắp xếp Khách hàng theo ID, thì điều đó không quan trọng trong trường Tên.)

Đối với các chức năng thử nghiệm lấy hoặc tạo chức năng ... vâng, tôi không có câu trả lời cho cái đó.

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