branch: elpa/buttercup commit a277b0e7d1860cb0880c701267e32c56c534763d Author: Jorgen Schaefer <cont...@jorgenschaefer.de> Commit: Jorgen Schaefer <cont...@jorgenschaefer.de>
Setup and teardown: before-each, after-each, before-all, after-all --- README.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++++------ buttercup-test.el | 81 +++++++++++++++++++++++++++++++- buttercup.el | 81 ++++++++++++++++++++++++++++++-- 3 files changed, 280 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6d3d202..db8149b 100644 --- a/README.md +++ b/README.md @@ -155,11 +155,122 @@ that are not included below. (expect bar :to-throw 'void-variable '(a)))))) ``` -## Spies +## Grouping Related Specs with `describe` -Buttercup provides a way of _spying_ on a function, something usually -called mocking, but Jasmine calls it _spies_, and so do we. Did I -mention Buttercup is heavily inspired by Jasmine? +The `describe` macro is for grouping related specs. The string +parameter is for naming the collection of specs, and will be +concatenated with specs to make a spec’s full name. This aids in +finding specs in a large suite. If you name them well, your specs read +as full sentences in traditional +[BDD](http://en.wikipedia.org/wiki/Behavior-driven_development) style. + +```Lisp +(describe "A spec" + (it "is just a function, so it can contain any code" + (let ((foo 0)) + (setq foo (1+ foo)) + + (expect foo :to-equal 1))) + + (it "can have more than one expectation" + (let ((foo 0)) + (setq foo (1+ foo)) + + (expect foo :to-equal 1) + (expect t :to-equal t)))) +``` + +### Setup and Teardown + +To help a test suite DRY up any duplicated setup and teardown code, +Buttercup provides the `before-each`, `after-each`, `before-all` and +`after-all` special forms. + +As the name implies, code blocks defined with `before-each` are called +once before each spec in the `describe` is run, and the `after-each` +code blocks are called once after each spec. + +Here is the same set of specs written a little differently. The +variable under test is defined at the top-level scope — the `describe` +block — and initialization code is moved into a `before-each` block. +The `after-each` block resets the variable before continuing. + +```Lisp +(describe "A spec using `before-each' and `after-each'" + (let ((foo 0)) + (before-each + (setq foo (1+ foo))) + + (after-each + (setq foo 0)) + + (it "is just a function, so it can contain any code" + (expect foo :to-equal 1)) + + (it "can have more than one expectation" + (expect foo :to-equal 1) + (expect t :to-equal t)))) +``` + +The `before-all` form is called only once before all the specs in +`describe` are run, and the `after-all` form is called after all specs +finish. These functions can be used to speed up test suites with +expensive setup and teardown. + +However, be careful using `before-all` and `after-all`! Since they are +not reset between specs, it is easy to accidentally leak state between +your specs so that they erroneously pass or fail. + +```Lisp +(describe "A spec using `before-all' and `after-all'" + (let (foo) + (before-all + (setq foo 1)) + + (after-all + (setq foo 0)) + + (it "sets the iniital value of foo before specs run" + (expect foo :to-equal 1) + (setq foo (1+ foo))) + + (it "does not reset foo between specs" + (expect foo :to-equal 2)))) +``` + +### Nesting `describe` Blocks + +Calls to `describe` can be nested, with specs defined at any level. +This allows a suite to be composed as a tree of functions. Before a +spec is executed, Buttercup walks down the tree executing each +`before-each` function in order. After the spec is executed, Buttercup +walks through the `after-each` functions similarly. + +```Lisp +(describe "A spec" + (let (foo) + (before-each + (setq foo 0) + (setq foo (1+ foo))) + + (after-each + (setq foo 0)) + + (it "is just a function, so it can contain any code" + (expect foo :to-equal 1)) + + (it "can have more than one expectation" + (expect foo :to-equal 1) + (expect t :to-equal t)) + + (describe "nested inside a second describe" + (let (bar) + (before-each + (setq bar 1)) + + (it "can reference both scopes as needed" + (expect foo :to-equal bar)))))) +``` ## Test Runners @@ -167,13 +278,12 @@ Evaluating `describe` forms just stores the suites. You need to use a test runner to actually evaluate them. Buttercup comes with two test runners by default: -- `buttercup-run-suite-at-point` — Evaluate the topmost `describe` - form at point and run the suite it creates directly. Useful for - interactive development. But be careful, this uses your current - environment, which might not be clean (due to said interactive - development). -- `buttercup-discover` — Find files in directories specified on the - command line, load them, and then run all suites defined therein. - Useful for being run in batch mode. -- `buttercup-markdown-runner` — Run code in markdown files. Used to +- `buttercup-run-at-point` — Evaluate the topmost `describe` form at + point and run the suite it creates directly. Useful for interactive + development. But be careful, this uses your current environment, + which might not be clean (due to said interactive development). +- `buttercup-run-discover` — Find files in directories specified on + the command line, load them, and then run all suites defined + therein. Useful for being run in batch mode. +- `buttercup-run-markdown` — Run code in markdown files. Used to run this file’s code. diff --git a/buttercup-test.el b/buttercup-test.el index 933c770..b3e4cb9 100644 --- a/buttercup-test.el +++ b/buttercup-test.el @@ -19,6 +19,9 @@ (require 'buttercup) +;;;;;;;;;; +;;; expect + (describe "The buttercup-failed signal" (it "can be raised" (expect (lambda () @@ -125,7 +128,13 @@ (buttercup--apply-matcher :not-defined '(1 2))) :to-throw))) -;; Built-in matchers are tested in README.md +;;;;;;;;;;;;;;;;;;;;; +;;; Built-in matchers + +;; Are tested in README.md + +;;;;;;;;;;;;;;;;;;;; +;;; Suites: describe (describe "The `buttercup-suite-add-child' function" (it "should add an element at the end of the list" @@ -187,6 +196,9 @@ :to-equal desc2))))) +;;;;;;;;;;;;; +;;; Specs: it + (describe "The `it' macro" (it "should expand to a call to the `buttercup-it' function" (expect (macroexpand '(it "description" body)) @@ -211,3 +223,70 @@ (expect (funcall (buttercup-spec-function spec)) :to-equal 23))))) + +;;;;;;;;;;;;;;;;;;;;;; +;;; Setup and Teardown + +(describe "The `before-each' macro" + (it "expands to a function call" + (expect (macroexpand '(before-each (+ 1 1))) + :to-equal + '(buttercup-before-each (lambda () (+ 1 1)))))) + +(describe "The `buttercup-before-each' function" + (it "adds its argument to the before-each list of the current suite" + (let* ((suite (make-buttercup-suite)) + (buttercup--current-suite suite)) + (buttercup-before-each 23) + + (expect (buttercup-suite-before-each suite) + :to-equal + (list 23))))) + +(describe "The `after-each' macro" + (it "expands to a function call" + (expect (macroexpand '(after-each (+ 1 1))) + :to-equal + '(buttercup-after-each (lambda () (+ 1 1)))))) + +(describe "The `buttercup-after-each' function" + (it "adds its argument to the after-each list of the current suite" + (let* ((suite (make-buttercup-suite)) + (buttercup--current-suite suite)) + (buttercup-after-each 23) + + (expect (buttercup-suite-after-each suite) + :to-equal + (list 23))))) + +(describe "The `before-all' macro" + (it "expands to a function call" + (expect (macroexpand '(before-all (+ 1 1))) + :to-equal + '(buttercup-before-all (lambda () (+ 1 1)))))) + +(describe "The `buttercup-before-all' function" + (it "adds its argument to the before-all list of the current suite" + (let* ((suite (make-buttercup-suite)) + (buttercup--current-suite suite)) + (buttercup-before-all 23) + + (expect (buttercup-suite-before-all suite) + :to-equal + (list 23))))) + +(describe "The `after-all' macro" + (it "expands to a function call" + (expect (macroexpand '(after-all (+ 1 1))) + :to-equal + '(buttercup-after-all (lambda () (+ 1 1)))))) + +(describe "The `buttercup-after-all' function" + (it "adds its argument to the after-all list of the current suite" + (let* ((suite (make-buttercup-suite)) + (buttercup--current-suite suite)) + (buttercup-after-all 23) + + (expect (buttercup-suite-after-all suite) + :to-equal + (list 23))))) diff --git a/buttercup.el b/buttercup.el index 6b73a3d..f0b2b7f 100644 --- a/buttercup.el +++ b/buttercup.el @@ -232,7 +232,11 @@ MATCHER is either a matcher defined with (cl-defstruct buttercup-suite description - children) + children + before-each + after-each + before-all + after-all) (defun buttercup-suite-add-child (parent child) "Add a CHILD suite to a PARENT suite." @@ -287,6 +291,46 @@ form.") :description description :function body-function))) +;;;;;;;;;;;;;;;;;;;;;; +;;; Setup and Teardown + +(defmacro before-each (&rest body) + (declare (indent 0)) + `(buttercup-before-each (lambda () ,@body))) + +(defun buttercup-before-each (function) + (setf (buttercup-suite-before-each buttercup--current-suite) + (append (buttercup-suite-before-each buttercup--current-suite) + (list function)))) + +(defmacro after-each (&rest body) + (declare (indent 0)) + `(buttercup-after-each (lambda () ,@body))) + +(defun buttercup-after-each (function) + (setf (buttercup-suite-after-each buttercup--current-suite) + (append (buttercup-suite-after-each buttercup--current-suite) + (list function)))) + +(defmacro before-all (&rest body) + (declare (indent 0)) + `(buttercup-before-all (lambda () ,@body))) + +(defun buttercup-before-all (function) + (setf (buttercup-suite-before-all buttercup--current-suite) + (append (buttercup-suite-before-all buttercup--current-suite) + (list function)))) + +(defmacro after-all (&rest body) + (declare (indent 0)) + `(buttercup-after-all (lambda () ,@body))) + +(defun buttercup-after-all (function) + (setf (buttercup-suite-after-all buttercup--current-suite) + (append (buttercup-suite-after-all buttercup--current-suite) + (list function)))) + + ;; (let* ((buttercup--descriptions (cons description ;; buttercup--descriptions)) ;; (debugger (lambda (&rest args) @@ -326,10 +370,26 @@ form.") (mapc #'buttercup-run-suite buttercup-suites) (error "No suites defined"))) +(defvar buttercup--before-each nil + "A list of functions to call before each spec. + +Do not change the global value.") + +(defvar buttercup--after-each nil + "A list of functions to call after each spec. + +Do not change the global value.") + (defun buttercup-run-suite (suite &optional level) (let* ((level (or level 0)) - (indent (make-string (* 2 level) ?\s))) + (indent (make-string (* 2 level) ?\s)) + (buttercup--before-each (append buttercup--before-each + (buttercup-suite-before-each suite))) + (buttercup--after-each (append (buttercup-suite-after-each suite) + buttercup--after-each))) (message "%s%s" indent (buttercup-suite-description suite)) + (dolist (f (buttercup-suite-before-all suite)) + (funcall f)) (dolist (sub (buttercup-suite-children suite)) (cond ((buttercup-suite-p sub) @@ -338,9 +398,21 @@ form.") (message "%s%s" (make-string (* 2 (1+ level)) ?\s) (buttercup-spec-description sub)) - (funcall (buttercup-spec-function sub))))) + (dolist (f buttercup--before-each) + (funcall f)) + (funcall (buttercup-spec-function sub)) + (dolist (f buttercup--after-each) + (funcall f))))) + (dolist (f (buttercup-suite-after-all suite)) + (funcall f)) (message ""))) +(defun buttercup-run-at-point () + (let ((buttercup-suites nil) + (lexical-binding t)) + (eval-defun nil) + (buttercup-run))) + (defun buttercup-markdown-runner () (let ((lisp-buffer (generate-new-buffer "elisp"))) (dolist (file command-line-args-left) @@ -352,7 +424,8 @@ form.") (with-current-buffer lisp-buffer (insert code)))))) (with-current-buffer lisp-buffer - (setq lexical-binding t) + (setq lexical-binding t + debug-on-error t) (eval-region (point-min) (point-max))) (buttercup-run)))