-
-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't Recommend let or let! #94
Comments
To be clear (and for the benefit of future readers skimming this issue) what you're advocating for instead is using instance variables instead, right? |
This is a very difficult one because You are in good company though, as Thoughtbot take a similar position: https://thoughtbot.com/blog/lets-not In my own experience, I would certainly support adding a substantial warning of the drawbacks of using it, illustrated with some alternatives. But if the aim to completely eliminate It would be disingenuous for the styleguide to ban |
Agree with your points @sshaw. You may also be interested in those two open issues in the guide: Speaking of the example you provided back then, would you consider the code below a good approach to testing?
The reason I changed the expectation is that Since we're speaking about
This is possible and works as you would expect. This is some middle ground It comes with a downside though, if it happens that your examples or setup code are mutating those objects, the state would leak between examples. Primitive objects can be frozen, but it's not that simple with objects that reference other objects. |
@dgollahon yes.
@andyw8, no, the aim is to act as a guide to RSpec not to advocate for addition or removal of features from the library.
@andyw8, I disagree. It's more disingenuous to keep it when acknowledge it's "typically heavily overused" and you aim to "use it minimally" and want to offer a "substantial warning". Why? It's possibly the most useless and confusing thing in the history of Ruby development. Stop the madness. ✂️ 🗑
@pirj yes, I saw that. Maybe I'll add my 2 cents 😈 but again,
@pirj not sure about this.
@pirj, well the before do
macro_hood = Fabricate(:macro_neighborhood)
hood = Fabricate(:neighborhood, :macro_neighborhood => macro_hood, :name => hood_name)
_restaurant = Fabricate(:restaurant, :neighborhood => hood, name => restaurant_name)
end
let(:hood_name) { "Jersey" }
let(:restaurant_name) { "Cowley's" }
context "given the name of a neighborhood" do
it "returns restaurants in that neighborhood" do
result = RestaurantSearch.search(hood_name)
expect(result.size).to eq(1)
expect(result[0].name).to eq(restaurant_name)
end
end To: before do
macro_hood = Fabricate(:macro_neighborhood)
@hood = Fabricate(:neighborhood, :macro_neighborhood => macro_hood, :name => "Jersey")
@restaurant = Fabricate(:restaurant, :neighborhood => hood, :name => "Cowley's")
end
context "given the name of a neighborhood" do
it "returns restaurants in that neighborhood" do
result = RestaurantSearch.search(@hood.name)
expect(result.size).to eq(1)
expect(result[0].name).to eq(@restaurant.name)
end
end Less noisy and to the point.
ID, where's that used? Maybe I'm missing something.
describe "#search" do
before do
# ...
end
hood_name = "Jersey"
restaurant_name = "Cowley's"
context "given the name of a neighborhood" do
it "returns restaurants in that neighborhood" do
result = RestaurantSearch.search(hood_name)
expect(result.size).to eq(1)
expect(result[0].name).to eq(restaurant_name)
end
end Better, but not much. We must be clear about what we're testing: |
@sshaw It took me a while, now I understand that you primarily propose to remove the "Use let definitions instead of instance variables" recommendation. Your arguments that I'm not convinced enough though that using instance variables in
With no single doubt that you can write good code with @cupakromer's comment brings up some interesting points as well to that discussion. |
@sshaw What do you think? |
Not going to argue about ones preference really. It may be flexible in production if you need to go back and add a cache or increment/decrement a counter. You can do so without breaking contract of callers, e.g.,
This statement is silly. What are we proving? That we disagree with the KISS principle? The proof is in the assertion.
context "something amaaaaaaaazing" do
before do
@foo = Object.new
end
end In this context, let variable 🤷♂️
Before you run your tests you need tests fixtures. It's that simple: context "something amaaaaaaaazing" do
before do
@foo = MyDomainObject.new
def @foo.bar
123
end
@baz = MyClass.new(@foo)
end
end How can that be improved with let? I don't even think you could write that with
So X% of tests use
Not sure I understand argument here.
Maybe maybe not. But one thing is 100% certain: |
Also it appears from this commit message that Commit for |
But nevertheless thank you @pirj for entertaining/understanding my argument. |
@sshaw Nice historical research. Indeed there's not much rationale or context given in those commit messages introducing Looking forward, this is the first usage example of
and I find it really hard to explain why RSpec went that way, as opposed to:
that should have already been available at that moment, and is just as lazy as This commit is the first one when
I completely agree that the introduction of |
I hope you realize that I try to be as unbiased as possible, to listen and understand rather than stand my point, since I clearly realize I might be wrong, and the guide should not even rely on my opinion or anyone else's opinion directly and rather be a common ground, include established practices, some of which may be good, and some not so good, but that can be easily understood by the readers of the code. Some of the practices, though, are so deep-rooted ( I'm totally not opposed to using instance variables in To avoid holy wars, edit wars, etc, the point is to pick one style in a given company/project and stick to it. |
1.
Let me elaborate a bit here with some examples that I believe demonstrate that usage of
This issue can be solved by an undocumented
But it's quite complicated. And the complexity will keep growing, if, for example you're going to add nested contexts to cover pricing in other cities of the same state. 2.
This is basically the same example, but instead of additional instance variables used to instantiate objects in other instance variables, that would be an example with calling methods on some objects in setup phase. Sorry for late response, it took me a while to collect my thoughts. |
I don't have any objections on relaxing the "Use let" rule, especially in cases with no nested contexts, and when the instance variable declarations are not inter-dependent. However, I'm still not convinced enough that it's way better in other cases. Closing this for now until we have comprehensive and clear good, bad, and fair examples. |
Opening this per @bbatsov's response to my comment on Reddit.
The meat of my argument (as copied from here):
I've seen Marston's arguments from 2011 and mostly have never agreed. Here ~7 years later in 2018, it seems he may be starting to realize they're bad:
No. There's never a compelling benefit: interpreter detecting your typo and lazy loading objects under test are not compelling.
If I was concerned about an interpreter detecting a typo I'd be using a different language.
Lazy loading, what is the point? If your tests are consuming resources to the point that you need to lazy evaluate them your tests have a problem.
Remember, we're talking maintenance phase here: people need to go back to tests written months and years ago. Tracing through lazy loaded
let
hierarchies sprinkled with eager loadedlet!
withbefore
hooks and shared examples that depend on the caller to define morelet
s is not maintainable.Obscure tests is not a compelling benefit.
And really, we're talking maintainability and style, so consistency is important. Let's throwout the very important argument above and consider this. It's safe to say (I think) that after 20+ years the TMTOWTDI camp has lost out to the There should be one—and preferably only one—obvious way to do it camp. I mean that's why we're here talking style guides, right? We don't want TMTOWTDI.
In this case not everything can be done with
let
, you needlet!
. And not everything can be done withlet
, you needbefore
. Why have some tests use X and some use Y and some use X and Y and maybe Z? Be consistent. KISS: usebefore
. It's all you need.The text was updated successfully, but these errors were encountered: