2013-11-21 24 views
14

Có cách nào để viết một cái gì đó như một "bài kiểm tra đơn vị" mà chắc chắn rằng một số mã không không phải là biên dịch?Làm thế nào để viết một bài kiểm tra đơn vị scala để đảm bảo sự thất bại?

Tại sao tôi lại muốn một thứ như vậy? Hai lý do.

1) Kiểm tra loại an toàn của API của tôi. Tôi muốn một cách để đảm bảo rằng nếu ai đó vượt qua trong một giá trị xấu, bạn nhận được một lỗi trình biên dịch, không chỉ là một lỗi thời gian chạy. Rõ ràng, tôi chỉ có thể chạy trình biên dịch và kiểm tra lỗi, nhưng có nó được chính thức hóa trong một bài kiểm tra đơn vị là tốt để tránh một hồi quy & cũng cho tài liệu.

Ví dụ: xem xét thử nghiệm này. Có một số nhận xét ra mã mà tôi đã sử dụng để kiểm tra loại an toàn: https://github.com/squito/boxwood/blob/master/core/src/test/scala/com/quantifind/boxwood/EnumUnionTest.scala#L42 (dòng 42 & 48 - trên đường dây 34 Tôi gọi một API khác nhau trong đó có một ngoại lệ thời gian chạy, mà tôi có thể kiểm tra)

Nó thực sự đã cho tôi một thời gian để có được quyền an toàn loại, vì vậy đó là những kiểm tra quan trọng. Bây giờ nếu tôi đi và sửa đổi việc thực hiện bên dưới, tôi không thể chạy bộ thử nghiệm của mình - tôi cũng phải nhớ bỏ ghi chú những dòng đó và kiểm tra lỗi trình biên dịch.

2) Kiểm tra xử lý lỗi của macro. Nếu một macro có một số đầu vào xấu, nó sẽ dẫn đến lỗi trình biên dịch. Cùng một vấn đề ở đây, cùng mong muốn có nó trong một bộ thử nghiệm dễ chạy.

Tôi sử dụng ScalaTest, nhưng tôi rất vui khi có giải pháp với bất kỳ khung kiểm thử đơn vị nào.

+0

Các giải pháp rõ ràng cho rằng là để viết một bài kiểm tra đơn vị đó chạy trình biên dịch trên tài nguyên đó được quy định trong đó đơn vị kiểm tra và phân tích đầu ra của biên dịch. Xem nó theo cách này mã nguồn không phải là bản thân kiểm tra đơn vị, mà chỉ là một tài nguyên để chạy thử nghiệm đơn vị. – SpaceTrucker

+0

có thể trùng lặp của [Kiểm tra xác nhận rằng một cái gì đó không được biên dịch] (http://stackoverflow.com/questions/15125457/testing-an-assertion-that-something-must-not-compile) –

+0

Xem [câu hỏi trước của tôi] (http://stackoverflow.com/q/15125457/334519), [Câu trả lời của Miles Sabin] (http://stackoverflow.com/a/15132961/334519) và macro ['illTyped' mới trong Shapeless 2.0] (https://github.com/milessabin/shapeless/blob/49ef0311cadb648653c3749ae057127fe1f265d6/core/src/main/scala/shapeless/test/typechecking.scala). –

Trả lời

10

Như tôi lưu ý trong một nhận xét ở trên, Shapeless 2.0 (chưa được phát hành nhưng hiện có sẵn dưới dạng cột mốc) có triển khai rất tốt chức năng bạn đang tìm kiếm, dựa trên giải pháp của Stefan Zeiger. Tôi đã thêm một bản demo vào dự án của bạn here (lưu ý rằng tôi đã cập nhật lên Scala 2.10, vì giải pháp này sử dụng macro). Nó hoạt động như thế này:

import shapeless.test.illTyped 

//this version won't even compile 
illTyped("getIdx(C.Ooga)") 

//We can have multiple enum unions exist side by side 
import Union_B_C._ 
B.values().foreach {b => Union_B_C.getIdx(b) should be (b.ordinal())} 
C.values().foreach {c => Union_B_C.getIdx(c) should be (c.ordinal() + 2)} 

//Though A exists in some union type, Union_B_C still doesn't know about it, 
// so this won't compile 
illTyped(""" 
    A.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())} 
""") 

Nếu chúng ta thay đổi mã trong các cuộc gọi thứ hai để illTyped để cái gì đó sẽ biên dịch:

B.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())} 

chúng tôi nhận được lỗi biên dịch sau đây:

[error] .../EnumUnionTest.scala:56: Type-checking succeeded unexpectedly. 
[error] Expected some error. 
[error]  illTyped(""" 
[error]   ^
[error] one error found 
[error] (core/test:compile) Compilation failed 

Nếu bạn muốn thử nghiệm không thành công, bạn có thể dễ dàng điều chỉnh the implementation in Shapeless. Xem Miles's answer đến my previous question để biết thêm một số thảo luận bổ sung.

+0

Cảm ơn Travis, xin lỗi vì câu hỏi trùng lặp. Bạn đã vượt lên trên và vượt ra ngoài bằng cách sửa đổi mã của tôi cho tôi! Ngoài ra, xem câu trả lời của tôi dưới đây, về vấn đề sử dụng nó trong repl, tò mò nếu bạn có bất kỳ suy nghĩ về điều đó. –

0

Câu trả lời của Travis Brown là hoàn toàn chính xác. Chỉ vì lợi ích của sự hoàn chỉnh, tôi muốn thêm rằng điều này cũng hoạt động cho các macro thử nghiệm, như được minh họa here.

Một điểm nhỏ: kiểm tra illTyped dường như không hoạt động trong bản phát lại. Nó không bao giờ ném lỗi, ngay cả khi biểu thức đã cho thực hiện kiểm tra loại. Nhưng đừng để điều đó đánh lừa bạn, nó hoạt động tốt.

> test:console 
Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_65). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> def foo(s: String) = s 
foo: (s: String)String 

scala> import shapeless.test.illTyped 
import shapeless.test.illTyped 

scala> foo(1) 
<console>:10: error: type mismatch; 
found : Int(1) 
required: String 
       foo(1) 
       ^

scala> illTyped("""foo(1)""") 

scala> illTyped("""foo("hi there")""") // <--- works, but shouldn't! 
+0

Vấn đề là trong REPL ngữ cảnh mà mã đang được kiểm tra loại trong không nhất thiết là những gì bạn mong đợi. 'illTyped (" 2 + 2 ")' sẽ thất bại như mong đợi, vì cũng sẽ đánh máy mã bằng cách sử dụng bất kỳ lớp có sẵn nào được định nghĩa bên ngoài REPL. Tuy nhiên, ngữ cảnh macro không thấy định nghĩa 'foo', vì vậy nó xem xét' foo ("hi there") 'bị đánh máy và cho phép nó qua. Tôi thừa nhận điều này là khó hiểu, nhưng nó hoàn toàn là một vấn đề REPL. –

5

Scalatest cũng có thể thực hiện việc này.

Checking that a snippet of code does not compile

Often when creating libraries you may wish to ensure that certain arrangements of code that represent potential “user errors” do not compile, so that your library is more error resistant. ScalaTest Matchers trait includes the following syntax for that purpose:

"val a: String = 1" shouldNot compile 

If you want to ensure that a snippet of code does not compile because of a type error (as opposed to a syntax error), use:

"val a: String = 1" shouldNot typeCheck 

Note that the shouldNot typeCheck syntax will only succeed if the given snippet of code does not compile because of a type error. A syntax error will still result on a thrown TestFailedException .

If you want to state that a snippet of code does compile, you can make that more obvious with:

"val a: Int = 1" should compile 

Although the previous three constructs are implemented with macros that determine at compile time whether the snippet of code represented by the string does or does not compile, errors are reported as test failures at runtime.

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