TL;DR
It's possible to enforce and test contracts defined at the module boundary within a test
module in the same file. Do this by require
'ing (submod "..")
in the test
module:
(module+ test
(require
...
(submod ".."))
...)
Found at Confused about racket contracts - Stack Overflow
Discussion
Usually my modules are of the form
#lang racket/base
(require racket/contract)
(provide
(contract-out
...)
)
; Definitions
(define ...)
; Tests
(module+ test
...
)
Testing the module mostly works fine, however the contracts defined with contract-out
aren't applied in the tests. It would be possible to work around this by defining module contents with define/contract
etc., but this has the disadvantage that the contracts are checked for all uses in the module which might slow down the code too much.
When I was looking for a way to test the contracts defined with contract-out
from within the same file, I found the above StackOverflow answer. Due to the (submod "..")
import in the test
module it accesses the module in the same file across the file's module boundary.
2 Likes
Another tip related to testing contracts: If a contract is relatively complicated and especially if it's difficult to test procedures where the contract is used, it may make sense to put the contract in its own predicate function and test this function.
For example, today I wrote this code:
(define omit-fields/c
(and/c (listof (or/c 'completed? 'priority 'completion-date 'creation-date))
(lambda (omit-fields)
; These aren't actual dates, but they work the same way for
; `task-date-combination/c`.
(define completion-date (index-of omit-fields 'completion-date))
(define creation-date (index-of omit-fields 'creation-date))
; In the context of `task->string` we're interested in omitting the
; fields, i.e. _not_ having them.
(task-date-combination/c (not completion-date) (not creation-date)))))
(the concrete background isn't relevant here), and I test omit-fields/c
on its own in the tests
module:
...
(run-tests
(test-suite "Contracts"
; `task-date-combination/c`
(test-case
"Valid date combinations"
(check-true (task-date-combination/c #f #f))
(check-true (task-date-combination/c #f "2022-01-04"))
(check-true (task-date-combination/c "2022-01-04" "2022-01-03")))
(test-false
"Completion date without creation date"
(task-date-combination/c "2022-01-04" #f))
; `omit-fields/c`
(test-false
"Invalid task field symbol"
(omit-fields/c '(priority invalid)))
(test-true
"Omit both dates"
(omit-fields/c '(priority creation-date completion-date)))
(test-true
"Omit completion date, but keep creation date"
(omit-fields/c '(priority completion-date)))
(test-true
"Omit neither date"
(omit-fields/c '(priority)))
(test-false
"Omit creation date, but keep completion date"
(omit-fields/c '(priority creation-date)))
))
...
1 Like