Comments (41)
For me this notation is too technical. RSpec was created to express specs/tests using human language.
from betterspecs.
I want to start by setting some definitions clear to remove some of the confusions that I have
noticed since the start of the thread.
First, BDD (Behavioral Driven Design) is focused on the behavior of the system or part of it, not
methods
. Secondly, Unit test refers to a system's unit, not a method
as well. So a Car
class,
for instance, is a Unit in the system and its unit tests mock all its dependencies and test its
behavior in isolation.
Having said that, corroborating what @jerzygangi and @irisbune said before, providing a best
practice as .method_name
that says very little about the behavior (if you are lucky to choose a
good explaining name from the start) is counter productive for many reasons. First, you will need to
consult the actual method to understand its intent, as a expect .method_name to return true
tells
nothing about the behavior (why true?) only how it is written. Secondly, a rename refactor is a
very common refactor and using .method_name
forces the developer to touch or rename tests
descriptions even though no behavior changed at all. More than that, and probably more important, is
the the knowledge of the behavior of the system is missing from the specifications which in the
medium to long term leads to a harder understanding of the system and a more difficult onboarding of
new member into a project.
Additionally, a BDD description can and should be decouple from the implementation details. If you
are using Object Oriented Programming OR Functional Programming or any other solution, the same
description should suffice to represent the behavior of the system or its units.
The only advantage of this practice is to make it faster to write a test as there is no need to
think from the behavior perspective but only "this method" perspective. And this, in fact, goes
against the very concept of BDD. More specifically, the economy, for example, from 1 minute to write
a BDD spec to 20s for a shorter description is a short-view economy as it leads to increasing time
on subsequent learning, onboarding and description of system behavior on the medium / long term life
of a project.
Please watch BDD Explained and
BDD | Better Executable Specifications,
from Dave Farley, that was involved in the origin of the BDD practice. Subscribe and check the rest
of his channel as it provides great content on best practices, BDD, TDD, CD, etc.
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
returned:
# Car
# when engine is turned off
# does not make noise
# when engine is already on
# makes noise
we could even rename the method from silent?
to quiet?
and no behavior or description would
need change.
or even a description that points to the noise
concept in a Car
:
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
# Car's noise
# when engine is turned off
# is lacking
# when engine is already on
# is present
I'm letting this here as a reference an with the expectation that this "best practice" be corrected.
Over and over again I need to clear this concepts when a project's test suite tells nothing about
the behavior of the system and the onboarding of a new member gets harder and harder.
from betterspecs.
How I write specs
This 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
Closing
I want to achieve a level of bliss where I can write expressive specs that output real documentation that can be used in a release.
from betterspecs.
@farski I think that's a matter of taste given that both ::
and .
are acceptable notation for class methods. It's probably more important that your team agree on one or the other :)
@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
from betterspecs.
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 it
or describe
or context
descriptions. The result is a very easy to read, easy to scan, documentation of what my code is doing.
from betterspecs.
@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?
from betterspecs.
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"
from betterspecs.
If your describe is ONLY a method name, like describe ".admin?"
then you're not longer describing behaviors, you're now writing unit tests.
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
from betterspecs.
For reference, here's the guideline
BAD
describe 'the authenticate method for User' do describe 'if the user is an admin' doGOOD
describe '.authenticate' do describe '#admin?' do
it's not good enough for describing behaviors
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
I see it like redundant comments for getter and setters that don't add any additional value.
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.
Doesn't seem very DRY.
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.
from betterspecs.
I think that, when describing class methods, the ::
syntax should be preferable to .
My reasons for preferring ::
:
- it's used by Ruby documentation (see for example https://ruby-doc.org/core-2.5.1/File.html)
- according to
man ri
the notation.
is ambiguous, while::
refers only to class methods
A `.' matches either class or instance methods, while #method matches only instance and ::method matches only class methods.
What do you think?
from betterspecs.
@Spaceghost the it 'uniqueness of title' do
parts look quite ugly, i think it's much clearer to write specify 'uniqueness of title' do
instead
from betterspecs.
@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:
Stack
when empty
returns nil for #peek
Stack
when empty
#peek
returns nil
Stack
#peek
returns nil when empty
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 describe "#peek"
is perfectly reasonable due to the context of describe Stack
.
from betterspecs.
@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.
from betterspecs.
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 subject
by using the following call: describe_method "#upcase" do
The gem detects instance and class methods and delegates them to the required object.
Essentially, under the hood... this is happening:
subject{ super().send method_name[1..-1], *args }
(when it's an instance method).
Allows some really nice specs. Also allows nesting.
Does anyone see any value in this? Let me know.
from betterspecs.
(And rspec --format documentation
is great for this)
from betterspecs.
One thing to consider is that there are different kinds of tests. Unit,
integration, and functional. I always describe behaviour, but at different
granularities depending on what kind of test it is.
There's definitely a lot of space for multiple distinct approaches
depending on the type of test you're writing. I write tests a bit more
similar to what I think you're describing, but those are functional tests
that express the inputs and outputs of the system, the parts the user
interacts with.
~Johnneylee
On Sat, Mar 19, 2016 at 9:19 PM, Maxim Kashchenko [email protected]
wrote:
@jerzygangi https://github.com/jerzygangi could you provide an example?
—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#2 (comment)
from betterspecs.
To join in on the discussion with ::
vs .
I prefer .
, it is a less distracting notation and using .
for calling class methods is something I see done a lot more. Also, it is similar to css's class selector, which is another anchor in many people's minds, so I think it is more natural.
from betterspecs.
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 #
from betterspecs.
+1 to ::
from betterspecs.
@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!
from betterspecs.
@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 it "presence of attribute"
. Thx.
from betterspecs.
@dchelimsky The funny part was that I have this in my common_spec_helper.rb
in a private repo for work.
def specify description, &blk
self.send :it, description, blk
end
I never saw the #specify
or #example
methods before. Thanks for being a champ.
from betterspecs.
@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.
from betterspecs.
@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 specify "presence of title"
meant. Also, the only case in that example that maybe shows what a valid post looks like is "uniqueness of title", because it happens to use create!
, and I think that means it'll raise an error if it doesn't get valid data.
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
from betterspecs.
@Spaceghost I accidentally hit submit and had to go back and edit that (a few times). Looks like what I'd do now.
from betterspecs.
@dchelimsky thanks for array of attributes example, but should not there be one more end
after dynamic example.
And for describing methods, I am on the @evanphx side. I am always trying to describe behavior. Except for things like it { should be_empty }
.
from betterspecs.
@pepe - yep, fixed, thanks.
from betterspecs.
I originally disagreed with this recommendation, then I read @dchelimsky's post and thought "Hmm, this works well for documenting APIs".
I'm not a big fan of RDOC, JavaDoc etc, and I'm still on the fence about this one. Perhaps it would be less controversial if the recommendation were titled "How to describe your public API"?
from betterspecs.
@dchelimsky I know you've handed the reins over to @alindeman and @myronmarston for rspec, but I really like your methods #build_post
and #create_post
.
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.
from betterspecs.
@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.
from betterspecs.
👍
from betterspecs.
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"
from betterspecs.
@dideler When describing a class method, I use .
and for an instance method, I use #
.
I also include the method signature in the string like #foo(bar=:bar, baz={})
from betterspecs.
Thanks @Spaceghost
from betterspecs.
Fyi ::
is called the scope-resolution operator.
from betterspecs.
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.
describe '#purchase' do
# ...
item.purchase
# ...
Doesn't seem very DRY.
from betterspecs.
👍 @dideler right on
from betterspecs.
@jerzygangi could you provide an example?
from betterspecs.
I wonder what is the correct way to write the description of a scope.
should it be:
describe ".my_scope"
or
describe "#my_scope"
from betterspecs.
@shasticdev I do:
describe (scope.some_scope_name)
it '...'
end
At least it communicates that it is a scope ;)
from betterspecs.
To join in on the discussion with
::
vs.
I prefer.
, it is a less distracting notation and using.
for calling class methods is something I see done a lot more. Also, it is similar to css's class selector, which is another anchor in many people's minds, so I think it is more natural.
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, .
is so small it's easy to miss, unlike ::
.
After reading this discussion spanning ~7 years, I can conclude that best practices are in the eyes of the beholder.
from betterspecs.
Related Issues (20)
- Move from Heroku to Github Pages HOT 50
- Question / Possible addition HOT 5
- Adapt all "translation pages" to the new layout HOT 1
- Use an i18n tool to maintain translations HOT 9
- Merge latest PR changes into gh-pages HOT 14
- Bring back "Resources" section to gh-pages HOT 1
- Move translated texts to corresponding YAML file HOT 3
- Homepage stored in GitHub metadata is not a URL HOT 3
- one-liner `should` syntax should be 'GOOD' HOT 5
- Usage of described_class HOT 2
- Our very first issue! Yay! #1
- Provide a version for Chai.js HOT 4
- Feature / integration / acceptance spec styles HOT 2
- Invalid SSL certificate for https://www.betterspecs.org HOT 2
- Old Content HOT 1
- Call for maintainer HOT 11
- Help needed to launch the new version of Betterspecs HOT 9
- Broken link on "Here a nice presentation explaining how to mix them together."
- Integration specs: what about of using expectations inside "let" blocks
- Link to self-hosted, non-Relishapp RSpec documentation
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from betterspecs.