4 Testing Refactoring Rules
#lang resyntax/test | package: resyntax |
The resyntax/test language provides a convenient domain-specific language for testing refactoring rules. This language makes it easy to write comprehensive tests that verify refactoring rules work correctly across a variety of inputs, and that they properly handle edge cases without making unwanted transformations.
4.1 Basic Test Syntax
Tests are written using #lang resyntax/test and consist of a series of test statements. Each statement begins with a keyword followed by a colon, and the statement’s body follows. Strings of code are written using code blocks. Here’s a simple example:
#lang resyntax/test |
|
require: my-rules my-suite |
|
header: |
- #lang racket |
|
test: "my rule transforms code as expected" |
- (old-pattern 1 2 3) |
- (new-pattern 1 2 3) |
4.2 Code Blocks
A code block is a delimited section of Racket code used within test statements. There are two types of code blocks:
Single-line code blocks are preceded by a single dash and a space (- ).
Multi-line code blocks are delimited by lines of at least three consecutive dashes (---). As a convenience, two adjacent multi-line code blocks can be separated by a single line of equals signs (===) instead of two lines of dashes.
Code blocks are essentially string literals, and can contain code written in any language. For this reason, it’s common for Resyntax tests to include a header: test statement which specifies what #lang each code block in that file is written in.
4.3 Test Statements
The resyntax/test language supports three types of test statements:
require: statements for loading refactoring suites
header: statements for defining common code used in all tests
test: statements for defining individual test cases
test statement
(require: module-path suite-name)
#lang resyntax/test |
|
require: resyntax/default-recommendations list-shortcuts |
require: my-custom-rules my-suite |
test statement
(header: code-block)
#lang resyntax/test |
|
header: |
-------------------- |
#lang racket/base |
(require racket/list) |
-------------------- |
test statement
(test: description-string test-body ...)
#lang resyntax/test |
|
test: "should rewrite old function to new function" |
-------------------- |
#lang racket |
(old-function 1 2 3) |
==================== |
#lang racket |
(new-function 1 2 3) |
-------------------- |
If more than two code blocks are provided, the last code block is the desired code and the test case checks that Resyntax refactors each of the preceding code blocks into the desired code:
#lang resyntax/test |
|
test: "should remove old-condition from and expressions" |
- (and old-condition x) |
- (and x old-condition) |
- (and old-condition x old-condition) |
- x |
When only a single code block is provided, the resulting test checks that Resyntax does not make any changes to the code block:
#lang resyntax/test |
|
test: "should not rewrite old function to new function in higher-order uses" |
-------------------- |
#lang racket |
(map old-function (list 1 2 3)) |
-------------------- |
4.4 Running Resyntax Tests
Tests written in resyntax/test are integrated with RackUnit and can be run using the standard raco test command:
% raco test my-rule-test.rkt |
raco test: (submod "my-rule-test.rkt" test) |
5 tests passed |
Each test: statement becomes a RackUnit test case, and the entire test file becomes a module with a single submodule named test. Clicking the Run button in DrRacket will execute each test in the file. Just like in RackUnit, failing tests are highlighted by DrRacket and print failure messages.
When executing tests, the resyntax/test language enables Resyntax’s debug logging and captures all of its logs. Failing test cases include the captured Resyntax logs in their printed output. This output can be somewhat verbose, but it makes it much easier to tell why Resyntax did or didn’t refactor code and how Resyntax came to produce a malformed suggestion.