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 four types of test statements:
require statements for loading refactoring suites
header statements for defining common code used in all tests
test and no-change-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 input-code-block ...+ expected-code-block)
#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) |
-------------------- |
|
test: "should remove old-condition from and expressions" |
- (and old-condition x) |
- (and x old-condition) |
- (and old-condition x old-condition) |
- x |
test statement
(no-change-test description-string input-code-block)
#lang resyntax/test |
|
no-change-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.