Files
2025-11-29 18:47:18 +08:00

128 lines
3.7 KiB
Plaintext

ERT (Emacs Lisp Regression Testing) - Built-in Testing Framework
CORE CONCEPTS
- Test definition: (ert-deftest name () [:tags (...)] body)
- Assertions: should, should-not, should-error
- Test selectors: t, "regex", :tag symbol, :failed, :passed
- Interactive debugging with integrated backtrace inspection
- Batch mode for CI/CD with exit codes
KEY FUNCTIONS
Test Definition:
(ert-deftest NAME () [:tags (TAG...)] BODY)
- Define test with optional tags for organization
Assertions:
(should FORM) - Assert FORM is non-nil
(should-not FORM) - Assert FORM is nil
(should-error FORM [:type TYPE]) - Assert FORM signals error
Running Tests:
M-x ert RET SELECTOR - Run tests interactively
(ert-run-tests-batch-and-exit) - Batch mode, exit with status
(ert-run-tests-batch) - Batch mode, no exit
Skip Tests:
(skip-unless CONDITION) - Skip if CONDITION nil
(skip-when CONDITION) - Skip if CONDITION non-nil
INTERACTIVE DEBUGGING (in *ert* buffer)
. - Jump to test definition
d - Re-run with debugger enabled
b - Show backtrace
r - Re-run test at point
R - Re-run all tests
l - Show executed should forms
m - Show test messages
BEST PRACTICES
Environment Isolation:
(with-temp-buffer ...) - Isolated buffer operations
(let ((var value)) ...) - Local variable binding
(unwind-protect (test-code) (cleanup)) - Ensure cleanup
Mocking:
(cl-letf (((symbol-function 'fn) (lambda () "mock"))) ...)
(flet ((fn () "mock")) ...)
Test Organization:
- Name: package-test-feature format
- Tags: :tags '(quick unit) or '(slow integration)
- Selectors: Run by tag, regex, or status
Fixtures:
(defun with-fixture (body)
(let ((setup (do-setup)))
(unwind-protect (funcall body setup)
(do-teardown setup))))
COMMON PATTERNS
Test Errors:
(should-error (/ 1 0) :type 'arith-error)
Test Buffer State:
(with-temp-buffer
(my-mode)
(should (eq major-mode 'my-mode)))
Test Interactive Commands:
(with-temp-buffer
(insert "text")
(call-interactively 'my-cmd)
(should (= (point) expected)))
BATCH MODE
emacs -batch -l ert -l tests.el -f ert-run-tests-batch-and-exit
emacs -batch -l ert -l tests.el \
--eval '(ert-run-tests-batch-and-exit "^test-prefix-")'
emacs -batch -l ert -l tests.el \
--eval '(let ((ert-quiet t)) (ert-run-tests-batch-and-exit))'
CI/CD INTEGRATION
- Exit code 0 for success, non-zero for failure
- Use ert-run-tests-batch-and-exit for automated testing
- Set EMACS_TEST_VERBOSE for detailed failure output
- Test across multiple Emacs versions in matrix builds
TEST FILE TEMPLATE
;;; package-test.el --- Tests for package -*- lexical-binding: t -*-
(require 'ert)
(require 'package)
(ert-deftest package-test-feature ()
"Test feature implementation."
(should (package-function)))
(provide 'package-test)
;;; package-test.el ends here
KEY FEATURES
- No external dependencies (built into Emacs 24+)
- Interactive debugging with Emacs integration
- Detailed failure reporting with subexpression values
- Flexible test selection and organization
- Dynamic binding enables easy mocking
- Support for both unit and integration tests
- Batch mode for automated testing
- Test skipping based on conditions
TYPICAL WORKFLOW
1. Write test with ert-deftest
2. Run interactively: M-x ert RET t RET
3. Debug failures: press 'd' or 'b' on failed test
4. Iterate until green
5. Tag appropriately: :tags '(quick) or '(slow)
6. Integrate into CI/CD with batch mode
PITFALLS TO AVOID
- Tests depending on global state
- Missing cleanup (use unwind-protect)
- Overly broad assertions (split into multiple should forms)
- Not testing error cases
- Tests interfering with each other
- Relying on filesystem/network without mocking