Top Banner
Getting Answers to Your Testing Questions Josh Justice
85

Getting Answers to Your Testing Questions

Apr 14, 2017

Download

Software

jasnow
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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