-
Notifications
You must be signed in to change notification settings - Fork 285
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
How to describe your methods #2
Comments
Why use (dot) classMethod rather than ::classMethod? It seems like in other places where the distinction is made with symbols (eg, ruby doc), it's :: and # |
For me this notation is too technical. RSpec was created to express specs/tests using human language. |
+1 to :: |
@farski I think that's a matter of taste given that both @tilsammans depending on the context of the examples, I agree. When dealing with small units (e.g. a model), I tend to focus on describing the behaviour of individual methods. When dealing with something like a Rails controller, I'll describe the behaviour of the request, in which case I'd do something like this: describe "GET #new" do
# ...
end
describe "POST #create" do
context "an admin user" do
end
context "an authenticated user" do
end
end |
How I write specsThis is how I've been writing my specs lately. I'm intentionally deviating from the norm to explore and find out if I can find some better practices. One of my goals is to explore writing a formatter that outputs rdoc from passing tests. require 'spec_helper'
require 'blocking_spec_helper'
require 'persistence/post'
describe 'app/persistence/post.rb' do
before do
@post = ::Persistence::Post.new title: Faker::Name.name, body: Faker::Lorem.paragraph, author: "Johnneylee"
end
describe ::Persistence::Post, 'validates' do
%w[title body author].each do |attribute|
specify "presence of #{attribute}" do
setter = attribute + "="
@post.send setter.to_sym, nil
@post.should be_invalid
end
end
specify 'uniqueness of title' do
title = Faker::Name.name
::Persistence::Post.create! title: title, body: Faker::Lorem.paragraph, author: Faker::Name.name
@post.title = title
@post.should be_invalid
end
end
describe ::Persistence::Post, 'methods' do
describe "#author" do
it 'should be a string' do
@post.author.should be_an_instance_of String
end
end
end
end Which outputs this. app/persistence/post.rb
Persistence::Post validates
presence of title
presence of body
presence of author
uniqueness of title
Persistence::Post methods
#author
should be a string
Finished in 0.70489 seconds
5 examples, 0 failures ClosingI want to achieve a level of bliss where I can write expressive specs that output real documentation that can be used in a release. |
@Spaceghost the |
@binarycode I definitely agree. I wrote a small method that just wraps #it and I use that. I called it #specify as well. But it seems it was already in rspec! |
@Spaceghost would you mind editing your comment above to use "specify" instead of "it" in that case? It'll help me sleep at night. Same with |
If your describe is ONLY a method name, like The point, generally, is to describe functionalities in terms of observable behaviors and then the code shows how those behaviors are manifested. Here is an example: describe "Admin user" do
before :each do
@user = AdminUser.new
end
it "should be detectable" do
@user.admin?.should be_true
end |
Just a general rule of thumb I have is to never use "should" in my example. Your spec is saying what it does, not what it should be doing. it "validates presence of name" |
@evanphx consider: describe Stack do
context "when empty" do
it "returns nil for #peek"
end
end
describe Stack do
context "when empty" do
describe "#peek" do
it "returns nil"
end
end
end
describe Stack do
describe "#peek" do
it "returns nil when empty"
end
end Output:
Each of these approaches has pros and cons and I can't really argue that one of them is always correct or incorrect, but I think the last one, which uses |
@dchelimsky The funny part was that I have this in my def specify description, &blk
self.send :it, description, blk
end I never saw the |
@evanphx While I agree that you should be describing the behaviours of your objects rather than the implementations in your describe blocks, I'm intentionally not doing that so I can explore outside of the recommended use cases. @dchelimsky how do you feel about how I was writing my specs above? Too weird? Maybe there's a good point in here. If I wrote proper behavioral specs and then unit tests separately, that might do me a bit of good in terms of separating out capturing the domain. |
@Spaceghost I'd let "validates" show up at the front of the validation examples - I didn't see "validates" at first and didn't understand what Here's what I'd write for the same set of examples: require 'spec_helper'
require 'blocking_spec_helper'
require 'persistence/post'
describe ::Persistence::Post do
def build_post(overrides={})
::Persistence::Post.new { title: Faker::Name.name, body: Faker::Lorem.paragraph, author: Faker::Name.name }.merge(overrides)
end
def create_post(overrides={})
build_post(overrides).save!
end
it "is valid with valid attributes" do
build_post.should be_valid
end
%w[title body author].each do |attribute|
it "validates presence of #{attribute}" do
build_post( attribute.to_sym => nil ).should_not be_valid
end
end
it 'validates uniqueness of title' do
pre_existing_post = create_post
build_post(title: pre_existing_post.title).should_not be_valid
end
end |
@Spaceghost I accidentally hit submit and had to go back and edit that (a few times). Looks like what I'd do now. |
@dchelimsky thanks for array of attributes example, but should not there be one more And for describing methods, I am on the @evanphx side. I am always trying to describe behavior. Except for things like |
@pepe - yep, fixed, thanks. |
@pepe, @evanphx I want the descriptions to give me an overview of what the behavior is and the code examples to give me the detail, but "overview" means different things in different contexts. For example, I would say "Money supports addition using +" rather than just "Money supports addition" because I can read that in the output and learn quite a bit of useful information from that. I would be very unlikely, however, to cite a specific examplee.g. "Money returns $5 for $4 + $1". That said, I think describing a specific return value is reasonable when talking about a behavior like "Array[index bigger than size] returns nil" it describes the Array's behavior in response to a class of inputs. |
I originally disagreed with this recommendation, then I read @dchelimsky's post and thought "Hmm, this works well for documenting APIs". |
@dchelimsky I know you've handed the reins over to @alindeman and @myronmarston for rspec, but I really like your methods I'm going to do some braining on this, partly with my fingers, and see what rises out of it. Thank you again for all the love/hate/beer you've had in the name of RSpec. |
@Spaceghost I actually picked that up from somebody else. When I remember who I'll try to also remember to post back here to give credit where credit is due. |
👍 |
I just wrote a small gem that allows a much more terse syntax in this very situation. https://github.com/brett-richardson/rspec-describe-method The syntax is like this: describe 'test' do
describe_method '#upcase' do
it{ should eq 'TEST' }
end
when_calling '#concat', 'ing' do # Alternative syntax (and arguments)
it{ should eq 'testing' }
end
end Essentially you can infer the value of Essentially, under the hood... this is happening: Allows some really nice specs. Also allows nesting. Does anyone see any value in this? Let me know. |
* Refactor the spec http://blog.davidchelimsky.net/blog/2012/05/13/spec-smell-explicit-use-o f-subject/ betterspecs/betterspecs#2 (comment) 711
Something the guideline isn't clear on: What's the suggested way of describing a method that takes arguments? For example def foo(bar)
...
end
describe "#foo(bar)"
# vs
describe "#foo" |
@dideler When describing a class method, I use I also include the method signature in the string like |
Thanks @Spaceghost |
Fyi |
As some people already said, I think this syntax is amazing for describing methods, but it's not good enough for describing behaviors. It won't be useful for developers that are not familiar with the code. Maybe I'm getting too far here, but I see it like redundant comments for getter and setters that don't add any additional value.
Doesn't seem very DRY. |
For reference, here's the guideline
The behaviour is described by the inner blocks. E.g. describe Item do
describe '#purchase' do
context 'when given a quantity' do
it 'purchases n items'
end
end
end
The naming convention provides consistency across tests and ensures related tests are grouped, so readers can easily navigate and find what they're looking for, and test output clearly shows what part of the system is under test.
Be careful not to make your tests too DRY. Tests should be DAMP (Descriptive and Meaningful Phrases) which favours readability over anything else while still eliminating duplication where possible if it enhances readability. |
👍 @dideler right on |
I fundamentally disagree with describing methods -- I write specs to describe behavior. For me, one of the most useful parts of RSpec is building a living documentation of what my application is doing -- in non-technical lingo. I intentionally do not refer to methods inside my |
(And |
@jerzygangi could you provide an example? |
One thing to consider is that there are different kinds of tests. Unit, There's definitely a lot of space for multiple distinct approaches ~Johnneylee On Sat, Mar 19, 2016 at 9:19 PM, Maxim Kashchenko notifications@github.com
|
@jerzygangi That is exactly what I find so confusing. I'm learning RoR and RSpec now. In the bootcamp I joined, it was made clear that RSpec is meant to provide test output that is understandable to all, especially non-programmers. This GOOD vs BAD example does the exact opposite? |
I think that, when describing class methods, the My reasons for preferring
What do you think? |
I wonder what is the correct way to write the description of a scope. |
@shasticdev I do: describe (scope.some_scope_name)
it '...'
end At least it communicates that it is a scope ;) |
To join in on the discussion with |
I would argue that the fact that it's similar to css would confuse whoever reads the test. They might think you're testing html/css code. (Obviously this depends on who is reading your test cases, and I do think this is highly unlikely) Also, After reading this discussion spanning ~7 years, I can conclude that best practices are in the eyes of the beholder. |
I want to start by setting some definitions clear to remove some of the confusions that I have Having said that, corroborating what @jerzygangi and @irisbune said before, providing a best Additionally, a BDD description can and should be decouple from the implementation details. If you The only advantage of this practice is to make it faster to write a test as there is no need to Please watch BDD Explained and Example of a BDD description: describe Car do
subject(:car) { Car.new(engine: started) }
context "when engine is turned off" do
let(:started) { false }
it "does not make noise" do
expect(car).to be_silent
end
end
context "when engine is already on" do
let(:started) { true }
it "makes noise" do
expect(car).not_to be_silent
end
end
end Notice that the behavior is explained but there is no need show implementation details on the output
we could even rename the method from or even a description that points to the describe Car, "noise" do
subject(:car) { Car.new(engine: started) }
context "when engine is turned off" do
let(:started) { false }
it "is lacking" do
expect(car).to be_silent
end
end
context "when engine is already on" do
let(:started) { true }
it "is present" do
expect(car).not_to be_silent
end
end
end
I'm letting this here as a reference an with the expectation that this "best practice" be corrected. |
Write your thoughts about the "how to describe your methods" best practice.
The text was updated successfully, but these errors were encountered: