racket / rackunit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow definitions in `#:before` for `test-suite`

kstrafe opened this issue · comments

In the tests for rkt-glfw we need to set up a window and that window is stored as a variable. Right now I use a set! but this feels dirty (hey, it's mutation). So it would be really cool if there's something like #:literal-before that could paste the code right before each test-case. Here's what I'm looking for:

(define-test-suite window-functions
    #:literal-before (begin (glfwInit) (define window (glfwCreateWindow 400 300 "" #f #f)))
    ;; Literal-after can only reference identifiers from literal-before, so tests can't overwrite by accident
    #:literal-after (begin (glfwDestroyWindow window) (glfwTerminate))
    ...)

To clarify, the expectation is that this:

(test-suite "some-test-suite"
  #:literal-before (begin before-body ...)
  #:literal-after (begin after-body ...)
  test-body ...)

...would be roughly equivalent to this:

(test-suite "some-test-suite"
  (begin (begin before-body ...
                test-body)
         after-body ...)
  ...)

Is that correct?

Assuming that's what you're looking for, I'm not sure this is the best approach. It's heavy on macros in a way that can create confusing scopes and error messages, and it might encourage a lot of copy-pasting of before and after code.

You might be interested in my fixture package which provides a way to abstract over this sort of test resource setup and teardown code. Using fixtures, I think the tests you linked to would look like this:

(define (create-test-window)
  (glfwInit)
  (glfwCreateWindow 400 300 "Main window" #f #f))

(define (destroy-test-window window)
  (glfwDestroyWindow window)
  (glfwTerminate))

(define-fixture test-window (disposable create-test-window destroy-test-window))

(test-case/fixture window-functions
  #:fixture test-window

  ;; now everybody gets their own window via (current-test-window)
  (test-case "Setting and getting window size succeeds"
    (glfwSetWindowSize (current-test-window) 350 250)
    (sleep 1) ; There is a slight delay before the size is actually set
    (let-values ([(x y) (glfwGetWindowSize (current-test-window))])
      (check-equal? x 350)
      (check-equal? y 250)))

  (test-case "Setting window position succeeds"
    (glfwSetWindowPos (current-test-window) 10 10))

  (test-case "Setting window title succeeds"
    (glfwSetWindowTitle (current-test-window) "Test title"))
 
  (test-case "Window iconify and restore succeeds"
    (glfwIconifyWindow (current-test-window))
    (glfwRestoreWindow (current-test-window))))

The test-case/fixture form specifies that the test and each nested test should all get their own instance of each of the fixtures listed in the #:fixture clauses. You can included nested uses of test-case/fixture to specify that some nested tests get additional fixtures. Also, the current value of each fixture is automatically shown in the test failure output. Would this sort of thing work for you?

This looks good. If possible there should be a link/mention to/of fixture for the before/after and #:before/#:after documentation. I didn't know about this at all. And additionally make it rackunit/fixture.

Doc links are a good idea. There's a few other community packages (as in, not "main distribution" packages) that extend RackUnit in various ways; a section in the docs mentioning them would be a helpful addition. I'll file an issue.

As for rackunit/fixture naming: the fixture library's core API is available as fixture with the RackUnit-specific bits available as fixture/rackunit. The core API is meant to be usable by other test frameworks, hence the standalone name.