Git Product home page Git Product logo

Comments (41)

tilsammans avatar tilsammans commented on July 28, 2024 78

For me this notation is too technical. RSpec was created to express specs/tests using human language.

from betterspecs.

jvortmann avatar jvortmann commented on July 28, 2024 22

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.

Spaceghost avatar Spaceghost commented on July 28, 2024 13

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.

jherdman avatar jherdman commented on July 28, 2024 9

@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.

jerzygangi avatar jerzygangi commented on July 28, 2024 6

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.

iriscoopers avatar iriscoopers commented on July 28, 2024 4

@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.

bobbytables avatar bobbytables commented on July 28, 2024 3

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.

evanphx avatar evanphx commented on July 28, 2024 2

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.

dideler avatar dideler commented on July 28, 2024 2

For reference, here's the guideline

BAD

describe 'the authenticate method for User' do
describe 'if the user is an admin' do

GOOD

describe '.authenticate' do
describe '#admin?' do

@diegoguemes

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.

spaghetticode avatar spaghetticode commented on July 28, 2024 2

I think that, when describing class methods, the :: syntax should be preferable to .

My reasons for preferring :::

A `.' matches either class or instance methods, while #method matches only instance and ::method matches only class methods.

What do you think?

from betterspecs.

binarycode avatar binarycode commented on July 28, 2024 1

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024 1

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024 1

@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.

brett-richardson avatar brett-richardson commented on July 28, 2024 1

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.

jerzygangi avatar jerzygangi commented on July 28, 2024 1

(And rspec --format documentation is great for this)

from betterspecs.

Spaceghost avatar Spaceghost commented on July 28, 2024 1

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.

abuisman avatar abuisman commented on July 28, 2024 1

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.

farski avatar farski commented on July 28, 2024

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.

MattRogish avatar MattRogish commented on July 28, 2024

+1 to ::

from betterspecs.

Spaceghost avatar Spaceghost commented on July 28, 2024

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024

@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.

Spaceghost avatar Spaceghost commented on July 28, 2024

@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.

Spaceghost avatar Spaceghost commented on July 28, 2024

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024

@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.

pepe avatar pepe commented on July 28, 2024

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024

@pepe - yep, fixed, thanks.

from betterspecs.

andypalmer avatar andypalmer commented on July 28, 2024

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.

Spaceghost avatar Spaceghost commented on July 28, 2024

@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.

dchelimsky avatar dchelimsky commented on July 28, 2024

@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.

zolzaya avatar zolzaya commented on July 28, 2024

👍

from betterspecs.

dideler avatar dideler commented on July 28, 2024

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.

Spaceghost avatar Spaceghost commented on July 28, 2024

@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.

dideler avatar dideler commented on July 28, 2024

Thanks @Spaceghost

from betterspecs.

adibsaad avatar adibsaad commented on July 28, 2024

Fyi :: is called the scope-resolution operator.

from betterspecs.

diegoguemes avatar diegoguemes commented on July 28, 2024

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.

wakproductions avatar wakproductions commented on July 28, 2024

👍 @dideler right on

from betterspecs.

mkaschenko avatar mkaschenko commented on July 28, 2024

@jerzygangi could you provide an example?

from betterspecs.

shasticdev avatar shasticdev commented on July 28, 2024

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.

abuisman avatar abuisman commented on July 28, 2024

@shasticdev I do:

describe (scope.some_scope_name)
   it '...'
end

At least it communicates that it is a scope ;)

from betterspecs.

aesyondu avatar aesyondu commented on July 28, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.