We were very inconsistent regarding these rules.
Personally I prefer no empty lines around blocks, clases, etc... as
recommended by the Ruby style guide [1], and they're the default values
in rubocop, so those are the settings I'm applying.
The exception is the `private` access modifier, since we were leaving
empty lines around it most of the time. That's the default rubocop rule
as well. Personally I don't have a strong preference about this one.
[1] https://rubystyle.guide/#empty-lines-around-bodies
Having exceptions is better than having silent bugs.
There are a few methods I've kept the same way they were.
The `RelatedContentScore#score_with_opposite` method is a bit peculiar:
it creates scores for both itself and the opposite related content,
which means the opposite related content will try to create the same
scores as well.
We've already got a test to check `Budget::Ballot#add_investment` when
creating a line fails ("Edge case voting a non-elegible investment").
Finally, the method `User#send_oauth_confirmation_instructions` doesn't
update the record when the email address isn't already present, leading
to the test "Try to register with the email of an already existing user,
when an unconfirmed email was provided by oauth" fo fail if we raise an
exception for an invalid user. That's because updating a user's email
doesn't update the database automatically, but instead a confirmation
email is sent.
There are also a few false positives for classes which don't have bang
methods (like the GraphQL classes) or destroying attachments.
For these reasons, I'm adding the rule with a "Refactor" severity,
meaning it's a rule we can break if necessary.
We couldn't declare them inside the block because they would be
considered local variables and its value would be lost when the block
was finished. So we were using instance variables instead.
However, with instance variables we don't get any warnings when we
misspell their names. We can avoid them by declaring the local variables
before the block starts.
We had four headings, some of them had investments, and some of them
didn't, and it was very hard to scan the code and check which investment
belongs to which heading.
Grouping the investments inside the block creating the heading makes
that task much easier, even if the code is still not 100% readable.
We also avoid unused variables which were there to keep the code
vertically algined.
There's a very common pattern in our test, where the setup only has two
lines:
variable = create(:something)
unused_variable = create(:something_else, something: variable)
In this case, since there's a blank line below these ones and then we'll
get to the body of the test, and the second variable is going to be
created based on the first variable, we can remove the useless
assignment and the readability is still OK.
Another option we almost unanimously discarded was:
variable = create(:something)
_unused_variable = create(:something_else, something: variable)
We don't use it anywhere else, either.
One more option we considered but found a bit too much for simple tests:
variable = create(:something) do |something|
create(:something_else, something: variable)
end
Then of course we could move the setup to `let` and `before` blocks, but
the tests could get over-structured really quickly.
These variables can be considered a block, and so removing them doesn't
make the test much harder to undestand.
Sometimes these variables formed the setup, sometimes they formed an
isolated part of the setup, and sometimes they were the part of the test
that made the test different from other tests.
We were creating records with a title we manually set, so to be
consistent with the rest of the code, in the test we check the title is
present using a string literal.
This way we can also remove useless assignments while keeping the code
vertically aligned.
This way we write the tests from the user's point of view: users can see
(for example) a proposal with the title "Make everything awesome", but
they don't see a proposal with a certain ID.
There are probably dozens, if not hundreds, of places where we could
write tests this way. However, it's very hard to filter which ones are
safe to edit, since not many of them have an HTML class we can use in
the tests, and adding a class might generate conflicts with CSS styles.
So, for now, I'm only changing the ones allowing us to cleanly remove
useless assignements while maintaining the code vertically aligned.
The attribute made sense before we changed it in commit ba1a6b4c. Since
then, all milestones have the same date, so the attribute doesn't affect
the test at all.
The group is automatically assigned when we assign the heading. The
budget isn't needed either, except for a special case related to the
reason to be rejected.
The "name" attribute is automatically generated by the budget heading
factory. And the "price" attribute is out of context and not needed
since this test doesn't create investments.
Now factories define default headings for investments, so there's no
need to create a group and a heading to create an investment.
Likewise, in order to create a heading it isn't necessary to specify a
group anymore; specifying the budget is enough.
It's possible that there are more similar cases we haven't simplified
yet; I'm only addressing the obvious ones.
These settings are enabled by default.
It could be argued explicitely enabling the features makes tests more
consistent, because they'll work if we change the default setting. It
could also be argued that it makes tests more expressive because it
makes the reader realize certain things will only work if a setting is
enabled.
However, we were only doing so in a few tests. The truth is, thousands
of our tests depend on certain features being enabled. So IMHO we should
be consistent and either set them on every test, or not at all. I'm
choosing the latter option for simplicity.
Settings are stored in the database, and so any changes to the settings
done during the tests are automatically rolled back between one test and
the next one.
There were also a few places where we weren't using an `after` block but
changing the setting at the end of the test.
We accidentally removed the `count` option in commit 55fb14ac, which
made the translation return a hash.
The test is a bit hacky, which makes me think changing the user
interface would probably be a better solution.