jonatas/fast

View on GitHub
docs/experiments.md

Summary

Maintainability
Test Coverage
# Experiments

<center>![](assets/logo.png)</center>

Experiments allow us to play with AST and do some code transformation, execute
some code and continue combining successful transformations.

The major idea is try a new approach without any promise and if it works
continue transforming the code.

## Replace `FactoryBot#create` with `build_stubbed`.

Let's look into the following spec example:

```ruby
describe "my spec" do
  let(:user) { create(:user) }
  let(:address) { create(:address) }
  # ...
end
```

Let's say we're amazed with `FactoryBot#build_stubbed` and want to build a small
bot to make the changes in a entire code base. Skip some database
touches while testing huge test suites are always a good idea.

First we can hunt for the cases we want to find:

```
$ ruby-parse -e "create(:user)"
(send nil :create
  (sym :user))
```

Using `fast` in the command line to see real examples in the `spec` folder:

```
$ fast "(send nil create)" spec
```

If you don't have a real project but want to test, just create a sample ruby
file with the code example above.

Running it in a big codebase will probably find a few examples of blocks.

The next step is build a replacement of each independent occurrence to use
`build_stubbed` instead of create and combine the successful ones, run again and
combine again, until try all kind of successful replacements combined.

Considering we have the following code in `sample_spec.rb`:

```ruby
describe "my spec" do
  let(:user) { create(:user) }
  let(:address) { create(:address) }
  # ...
end
```

Let's create the experiment that will contain the nodes that are target to be
executed and what we want to do when we find the node.

```ruby
experiment = Fast.experiment('RSpec/ReplaceCreateWithBuildStubbed') do
  search '(send nil create)'
  edit { |node| replace(node.loc.selector, 'build_stubbed') }
end
```

If we use `Fast.replace_file` it will replace all occurrences in the same run
and that's one of the motivations behind create the `ExperimentFile` class.

Executing a partial replacement of the first occurrence:

```ruby
experiment_file = Fast::ExperimentFile.new('sample_spec.rb', experiment) }
puts experiment_file.partial_replace(1)
```

The command will output the following code:

```ruby
describe "my spec" do
  let(:user) { build_stubbed(:user) }
  let(:address) { create(:address) }
  # ...
end
```

## Remove useless before block

Imagine the following code sample:

```ruby
describe "my spec" do
  before { create(:user) }
  # ...
  after { User.delete_all }
end
```

And now, we can define an experiment that removes the entire code block and run
the experimental specs.

```ruby
experiment = Fast.experiment('RSpec/RemoveUselessBeforeAfterHook') do
  lookup 'spec'
  search '(block (send nil {before after}))'
  edit { |node| remove(node.loc.expression) }
  policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
end
```

To run the experiment you can simply say:

```ruby
experiment.run
```

Or drop the code into `experiments` folder and use the `fast-experiment` command
line tool.

    $ fast-experiment RSpec/RemoveUselessBeforeAfterHook spec
 
## DSL

- In the `lookup` you can pass files or folders.
- The `search` contains the expression you want to match
- With `edit` block you can apply the code change
- And the `policy` is executed to check if the current change is valuable

If the file contains multiple `before` or `after` blocks, each removal will
occur independently and the successfull removals will be combined as a
secondary change. The process repeates until find all possible combinations.

See more examples in [experiments](experiments) folder.

To run multiple experiments, use `fast-experiment` runner:

```
fast-experiment <experiment-names> <files-or-folders>
```

You can limit experiments or file escope:

```
fast-experiment RSpec/RemoveUselessBeforeAfterHook spec/models/**/*_spec.rb
```

Or a single file:

```
fast-experiment RSpec/ReplaceCreateWithBuildStubbed spec/models/my_spec.rb
```