Getting Answers to Your Testing Questions

85
Getting Answers to Your Testing Questions Josh Justice

Transcript of Getting Answers to Your Testing Questions

Page 1: Getting Answers to Your Testing Questions

Getting Answers to Your Testing Questions

Josh Justice

Page 2: Getting Answers to Your Testing Questions

CodingItWrong

CodingItWrong

Page 3: Getting Answers to Your Testing Questions

@bignerdranch bignerdranch.com

Page 4: Getting Answers to Your Testing Questions

Ruby on Rails course June 27–July 1

bit.ly/nerdrails

Page 5: Getting Answers to Your Testing Questions

How do I get started testing? 🤔

Page 6: Getting Answers to Your Testing Questions

Questions! 🤔• Do I write the test or production code first?

• What do I test first?

• How many acceptance/unit tests do I write?

• How much test code do I write at a time? Production code?

• Do I test every line of code and configuration?

• How much do I use test doubles? What do I test for?

Page 7: Getting Answers to Your Testing Questions

Everyone agrees! 🙈

Page 8: Getting Answers to Your Testing Questions

"Unit tests are good!" "Unit tests are bad!" "Mocks are good!" "Mocks are bad!"

😩

Page 9: Getting Answers to Your Testing Questions
Page 10: Getting Answers to Your Testing Questions

What if we didn’t have to worry about all those

questions at once?

Page 11: Getting Answers to Your Testing Questions

Test-Driven Development:

An approach to testing that provides a consistent set of answers to those questions.

Page 12: Getting Answers to Your Testing Questions

“TDD is dead”…?

Page 13: Getting Answers to Your Testing Questions

Too rigid?

• Not “follow this exactly or you’re a bad developer.”

• Not “this is the only way it can be done.”

• Give it a whole-hearted try. Then you’ll know when to apply it and when not to.

• Or, just take a principle or two and see if they help.

Page 14: Getting Answers to Your Testing Questions

Goals

• Show TDD applied to a small real-world example.

• Show how it answers those questions about how to get started in testing.

• Motivate you to try it if you haven’t (or if you haven’t strictly).

Page 15: Getting Answers to Your Testing Questions

Not Goals

• Convince you testing is a good idea.

• Introduce testing concepts and terms.

• Provide rationale for individual points of TDD.

Page 16: Getting Answers to Your Testing Questions

learntdd.in/rails

Page 17: Getting Answers to Your Testing Questions

Requirement: as a user, I want to be able to create a blog post.

(of course)

Page 18: Getting Answers to Your Testing Questions

Do I write the test or production code first?

Write tests first.

🤔

Page 19: Getting Answers to Your Testing Questions

Why tests first?

• To make sure there's time to test.

• To make sure your code is covered by tests.

• To make sure your code is easy to test.

• To let tests drive your design.

Page 20: Getting Answers to Your Testing Questions

What do I test first?

Start outside the system.

🤔

Page 21: Getting Answers to Your Testing Questions

How much test do I write at a time?

Write a whole acceptance test for one feature.

🤔

Page 22: Getting Answers to Your Testing Questions

# spec/features/creating_a_blog_post_spec.rb require 'rails_helper'

describe 'Creating a blog post' do

it 'saves and displays the resulting blog post' do visit '/blog_posts/new'

fill_in 'Title', with: 'Hello, World!' fill_in 'Body', with: 'Hello, I say!'

click_on 'Create Blog Post'

blog_post = BlogPost.order("id").last expect(blog_post.title).to eq('Hello, World!') expect(blog_post.body).to eq('Hello, I say!')

expect(page).to have_content('Hello, World!') expect(page).to have_content('Hello, I say!') end

end

Page 23: Getting Answers to Your Testing Questions

# spec/features/creating_a_blog_post_spec.rb require 'rails_helper'

describe 'Creating a blog post' do

it 'saves and displays the resulting blog post' do visit '/blog_posts/new'

fill_in 'Title', with: 'Hello, World!' fill_in 'Body', with: 'Hello, I say!'

click_on 'Create Blog Post'

blog_post = BlogPost.order("id").last expect(blog_post.title).to eq('Hello, World!') expect(blog_post.body).to eq('Hello, I say!')

expect(page).to have_content('Hello, World!') expect(page).to have_content('Hello, I say!') end

end How much do I use test doubles?

In acceptance tests, don’t use test doubles.

🤔

Page 24: Getting Answers to Your Testing Questions

Run the test and watch it fail, to know what to

implement first.

Page 25: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: visit '/blog_posts/new'

ActionController::RoutingError: No route matches [GET] "/blog_posts/new"

Page 26: Getting Answers to Your Testing Questions

# config/routes.rb Rails.application.routes.draw do resources :blog_posts end

Page 27: Getting Answers to Your Testing Questions

# config/routes.rb Rails.application.routes.draw do resources :blog_posts end

Do I test every line?

No, you can fix trivial errors directly.

🤔

Page 28: Getting Answers to Your Testing Questions

# config/routes.rb Rails.application.routes.draw do resources :blog_posts end

How much production code do I write at a time?

Just enough to fix the current error.

🤔

Page 29: Getting Answers to Your Testing Questions

Red-Green-Refactor

Page 30: Getting Answers to Your Testing Questions

A note on refactoring.

Page 31: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: visit '/blog_posts/new'

ActionController::RoutingError: uninitialized constant BlogPostsController

Page 32: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController end

Page 33: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: visit '/blog_posts/new'

AbstractController::ActionNotFound: The action 'new' could not be found for BlogPostsController

Page 34: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController def new end end

Page 35: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: visit '/blog_posts/new'

ActionView::MissingTemplate: Missing template blog_posts/new

Page 36: Getting Answers to Your Testing Questions

<%# app/views/blog_posts/new.html.erb %>

Page 37: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: fill_in 'Title', with: 'Hello, World!'

Capybara::ElementNotFound: Unable to find field "Title"

Page 38: Getting Answers to Your Testing Questions

<%# app/views/blog_posts/new.html.erb %> <%= form_for @blog_post do |f| %> <div> <%= f.label :title %> <%= f.text_field :title %> </div> <div> <%= f.label :body %> <%= f.text_area :body %> </div> <%= f.submit 'Create Blog Post' %> <% end %>

Page 39: Getting Answers to Your Testing Questions

<%# app/views/blog_posts/new.html.erb %> <%= form_for @blog_post do |f| %> <div> <%= f.label :title %> <%= f.text_field :title %> </div> <div> <%= f.label :body %> <%= f.text_area :body %> </div> <%= f.submit 'Create Blog Post' %> <% end %>

How much production code do I write at a time?

Sometimes, more than enough to fix the current error.

🤔

Page 40: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: <%= form_for @blog_post do |f| %>

ActionView::Template::Error: First argument in form cannot contain nil or be empty

Page 41: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: <%= form_for @blog_post do |f| %>

ActionView::Template::Error: First argument in form cannot contain nil or be empty

When do I write unit tests?

Step down to a unit test when there are behavioral errors.

🤔

Page 42: Getting Answers to Your Testing Questions

Why unit test when there's already an acceptance test?

Page 43: Getting Answers to Your Testing Questions

Acceptance tests demonstrate external quality: whether the

system works.

Page 44: Getting Answers to Your Testing Questions

Acceptance tests don't demonstrate internal quality: whether the

code is maintainable.

Page 45: Getting Answers to Your Testing Questions

Unit tests expose internal quality. They drive design.

Page 46: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

describe '#new' do it 'returns a blog post' do blog_post = instance_double(BlogPost) expect(BlogPost).to receive(:new).and_return(blog_post) get :new expect(assigns[:blog_post]).to eq(blog_post) end end

end

Page 47: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

describe '#new' do it 'returns a blog post' do blog_post = instance_double(BlogPost) expect(BlogPost).to receive(:new).and_return(blog_post) get :new expect(assigns[:blog_post]).to eq(blog_post) end end

end How much test do I write?

Write only enough unit test to expose the behavioral error.

🤔

Page 48: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

describe '#new' do it 'returns a blog post' do blog_post = instance_double(BlogPost) expect(BlogPost).to receive(:new).and_return(blog_post) get :new expect(assigns[:blog_post]).to eq(blog_post) end end

end How much test do I write?

Specify one behavior per unit test case.

🤔

Page 49: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

describe '#new' do it 'returns a blog post' do blog_post = instance_double(BlogPost) expect(BlogPost).to receive(:new).and_return(blog_post) get :new expect(assigns[:blog_post]).to eq(blog_post) end end

end How much do I use test doubles?

In unit tests, use test doubles in place of any collaborators.

🤔

Page 50: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

describe '#new' do it 'returns a blog post' do blog_post = instance_double(BlogPost) expect(BlogPost).to receive(:new).and_return(blog_post) get :new expect(assigns[:blog_post]).to eq(blog_post) end end

end What do I test for?

In unit tests, behavior, not state. (Mostly.)

🤔

Page 51: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rbF

Failures:

1) BlogPostsController#new returns a blog post Failure/Error: blog_post = instance_double(BlogPost)

NameError: uninitialized constant BlogPost

Page 52: Getting Answers to Your Testing Questions

# db/migrate/20160223100510_create_blog_posts.rb class CreateBlogPosts < ActiveRecord::Migration def change create_table :blog_posts do |t| t.string :title t.text :body end end end

# app/models/blog_post.rb class BlogPost < ActiveRecord::Base end

Page 53: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rbF

Failures:

1) BlogPostsController#new returns a blog post Failure/Error: expect(assigns[:blog_post]).to eq(blog_post)

expected: #<InstanceDouble(BlogPost) (anonymous)> got: nil

(compared using ==)

Page 54: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController def new @blog_post = BlogPost.new end end

Page 55: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rb.

Finished in 0.03134 seconds (files took 1.46 seconds to load)1 example, 0 failures

Page 56: Getting Answers to Your Testing Questions

How often do I run which tests?

When the unit test passes, step back up to the acceptance test.

🤔

Page 57: Getting Answers to Your Testing Questions

Two Red-Green-Refactor Loops

Page 58: Getting Answers to Your Testing Questions

# spec/features/creating_a_blog_post_spec.rb require 'rails_helper'

describe 'Creating a blog post' do

it 'saves and displays the resulting blog post' do visit '/blog_posts/new'

fill_in 'Title', with: 'Hello, World!' fill_in 'Body', with: 'Hello, I say!'

click_on 'Create Blog Post'

blog_post = BlogPost.order("id").last expect(blog_post.title).to eq('Hello, World!') expect(blog_post.body).to eq('Hello, I say!')

expect(page).to have_content('Hello, World!') expect(page).to have_content('Hello, I say!') end

end

Page 59: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: click_on 'Create Blog Post'

AbstractController::ActionNotFound: The action 'create' could not be found for BlogPostsController

Page 60: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController def new ... end

def create end end

Page 61: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: click_on 'Create Blog Post'

ActionView::MissingTemplate: Missing template blog_posts/create

Page 62: Getting Answers to Your Testing Questions

<%# app/views/blog_posts/create.html.erb %>

Page 63: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: expect(blog_post.title).to eq('Hello, World!')

NoMethodError: undefined method `title' for nil:NilClass

Page 64: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

describe '#new' do ... end

describe '#create' do it 'creates a blog post record' do expect(BlogPost).to receive(:create).with(title: 'My Title', body: 'My Body') post :create, { blog_post: { title: 'My Title', body: 'My Body', } } end end end

Page 65: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rb.F

Failures:

1) BlogPostsController#create creates a blog post record Failure/Error: expect(BlogPost).to receive(:create).with(title: 'My Title', body: 'My Body')

(BlogPost(id: integer, title: string, body: text) (class)).create({:title=>"My Title", :body=>"My Body"}) expected: 1 time with arguments: ({:title=>"My Title", :body=>"My Body"}) received: 0 times

Page 66: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController ...

def create BlogPost.create(params[:blog_post]) end end

☝😧 Wait for iiiiiiiit…

Page 67: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rb..

Finished in 0.02774 seconds (files took 1.53 seconds to load)2 examples, 0 failures

Page 68: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: BlogPost.create(params[:blog_post])

ActiveModel::ForbiddenAttributesError

Page 69: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController ...

def create BlogPost.create(blog_post_params) end

private

def blog_post_params params.require(:blog_post).permit(:title, :body) end end

Page 70: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: expect(page).to have_content('Hello, World!') expected to find text "Hello, World!" in ""

Page 71: Getting Answers to Your Testing Questions

<%# app/views/blog_posts/create.html.erb %> <h1><%= @blog_post.title %></h1>

<div> <%= @blog_post.body %> </div>

Page 72: Getting Answers to Your Testing Questions

# rspec spec/features/creating_a_blog_post_spec.rbF

Failures:

1) Creating a blog post saves and displays the resulting blog post Failure/Error: <h1><%= @blog_post.title %></h1>

ActionView::Template::Error: undefined method `title' for nil:NilClass

Page 73: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

...

describe '#create' do it 'creates a blog post record' do ... end

it 'returns the new blog post to the view' do blog_post = instance_double(BlogPost) allow(BlogPost).to receive(:create).and_return(blog_post) post :create, { blog_post: { title: 'My Title', body: 'My Body', } } expect(assigns[:blog_post]).to eq(blog_post) end end end

Page 74: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rb..F

Failures:

1) BlogPostsController#create returns the new blog post to the view Failure/Error: expect(assigns[:blog_post]).to eq(blog_post)

expected: #<InstanceDouble(BlogPost) (anonymous)> got: nil

(compared using ==)

Page 75: Getting Answers to Your Testing Questions

# app/controllers/blog_posts_controller.rb class BlogPostsController < ApplicationController ...

def create @blog_post = BlogPost.create(blog_post_params) end

... end

Page 76: Getting Answers to Your Testing Questions

# rspec spec/controllers/blog_posts_controller_spec.rb...

Finished in 0.03122 seconds (files took 1.52 seconds to load)3 examples, 0 failures

# rspec....

Finished in 0.17293 seconds (files took 1.49 seconds to load)4 examples, 0 failures

# rspec spec/features/creating_a_blog_post_spec.rb.

Finished in 0.15204 seconds (files took 1.42 seconds to load)1 example, 0 failures

Page 77: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

...

describe '#create' do it 'creates a blog post record' do expect(BlogPost).to receive(:create).with(title: 'My Title', body: 'My Body') post :create, { blog_post: { title: 'My Title', body: 'My Body', } } end

it 'returns the new blog post to the view' do blog_post = instance_double(BlogPost) allow(BlogPost).to receive(:create).and_return(blog_post) post :create, { blog_post: { title: 'My Title', body: 'My Body', } } expect(assigns[:blog_post]).to eq(blog_post) end end end

Page 78: Getting Answers to Your Testing Questions

# spec/controllers/blog_posts_controller_spec.rb require 'rails_helper'

describe BlogPostsController do

...

describe '#create' do let(:post_params) { { blog_post: { title: 'My Title', body: 'My Body', } } }

it 'creates a blog post record' do expect(BlogPost).to receive(:create).with(title: 'My Title', body: 'My Body') post :create, post_params end

it 'returns the new blog post to the view' do blog_post = instance_double(BlogPost) allow(BlogPost).to receive(:create).and_return(blog_post) post :create, post_params expect(assigns[:blog_post]).to eq(blog_post) end end end

Page 79: Getting Answers to Your Testing Questions

# rspec....

Finished in 0.17293 seconds (files took 1.49 seconds to load)4 examples, 0 failures

Page 80: Getting Answers to Your Testing Questions

Imagine if testing the way you want to was second-

nature.

Page 81: Getting Answers to Your Testing Questions

TDD can help you get there.

…whether you end up embracing all of it or not.

Page 82: Getting Answers to Your Testing Questions

🤔 Questions?

Page 83: Getting Answers to Your Testing Questions

To Learn More

• learntdd.in/rails

• The RSpec Book

• Growing Object-Oriented Software, Guided By Tests

Page 84: Getting Answers to Your Testing Questions

Thanks! 🙃@CodingItWrong learntdd.in/rails

Page 85: Getting Answers to Your Testing Questions