Top Banner
How to Write Better Code with Mutation Testing John Backus - @backus - [email protected]
77

How to Write Better Code with Mutation Testing

Jan 23, 2017

Download

Software

John Backus
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: How to Write Better Code with Mutation Testing

How to Write Better Code with Mutation Testing

John Backus - @backus - [email protected]

Page 2: How to Write Better Code with Mutation Testing

Talk Outline

Introduction

Page 3: How to Write Better Code with Mutation Testing

Talk Outline

Introduction

Improving coverage

Page 4: How to Write Better Code with Mutation Testing

Talk Outline

Introduction

Improving coverage

Learning about Ruby and the code you rely on

Page 5: How to Write Better Code with Mutation Testing

Line Coverage

Lines of Code Run by Tests Total Lines of Code÷

Page 6: How to Write Better Code with Mutation Testing

Mutation Coverage

How much of your code can I change

without breaking your tests?

Page 7: How to Write Better Code with Mutation Testing

Mutation Testing by Hand

Page 8: How to Write Better Code with Mutation Testing

1 class Gluttons 2 def initialize(twitter_client) 3 @twitter = twitter_client 4 end 5 6 def recent 7 query = @twitter.search('"I really enjoy #pizza"') 8 9 query.first(2).map { |tweet| "@#{tweet.author}" } 10 end 11 end

1 RSpec.describe Gluttons do 2 it 'lists the two most recent gluttonous tweeters' do 3 tweets = [double(author: 'John'), double(author: 'Jane')] 4 gluttons = Gluttons.new(double(search: tweets)) 5 6 expect(gluttons.recent).to eql(%w[@John @Jane]) 7 end 8 end

Page 9: How to Write Better Code with Mutation Testing
Page 10: How to Write Better Code with Mutation Testing
Page 11: How to Write Better Code with Mutation Testing
Page 12: How to Write Better Code with Mutation Testing
Page 13: How to Write Better Code with Mutation Testing
Page 14: How to Write Better Code with Mutation Testing
Page 15: How to Write Better Code with Mutation Testing
Page 16: How to Write Better Code with Mutation Testing
Page 17: How to Write Better Code with Mutation Testing
Page 18: How to Write Better Code with Mutation Testing
Page 19: How to Write Better Code with Mutation Testing
Page 20: How to Write Better Code with Mutation Testing
Page 21: How to Write Better Code with Mutation Testing
Page 22: How to Write Better Code with Mutation Testing
Page 23: How to Write Better Code with Mutation Testing
Page 24: How to Write Better Code with Mutation Testing
Page 25: How to Write Better Code with Mutation Testing
Page 26: How to Write Better Code with Mutation Testing

1 class Gluttons 2 def recent 3 - query = @twitter.search('"I really enjoy #pizza"') 4 + query = @twitter.search('"I really enjoy #hotdogs"')

1 example, 0 failures

Page 27: How to Write Better Code with Mutation Testing

1 class Gluttons 2 def recent 3 - query = @twitter.search('"I really enjoy #pizza"') 4 + query = @twitter.search('')

1 example, 0 failures

Page 28: How to Write Better Code with Mutation Testing

1 class Gluttons 2 def recent 3 - query = @twitter.search('"I really enjoy #pizza"') 4 + query = @twitter.search

1 example, 0 failures

Page 29: How to Write Better Code with Mutation Testing

1 class Gluttons 2 def recent 3 - query.first(2).map { |tweet| "@#{tweet.author}" } 4 + query.first(1).map { |tweet| "@#{tweet.author}" }

Failure

expected: ["@John", "@Jane"] got: ["@John"]

Page 30: How to Write Better Code with Mutation Testing

1 - query.first(2).map { |tweet| "@#{tweet.author}" } 2 + query.first(3).map { |tweet| "@#{tweet.author}" } 3 end

1 example, 0 failures

Page 31: How to Write Better Code with Mutation Testing

Manual Mutation Testing

Tedious

Page 32: How to Write Better Code with Mutation Testing

Manual Mutation Testing

Hard to outsmart yourself

Page 33: How to Write Better Code with Mutation Testing
Page 34: How to Write Better Code with Mutation Testing
Page 35: How to Write Better Code with Mutation Testing
Page 36: How to Write Better Code with Mutation Testing
Page 37: How to Write Better Code with Mutation Testing
Page 38: How to Write Better Code with Mutation Testing
Page 39: How to Write Better Code with Mutation Testing
Page 40: How to Write Better Code with Mutation Testing
Page 41: How to Write Better Code with Mutation Testing
Page 42: How to Write Better Code with Mutation Testing
Page 43: How to Write Better Code with Mutation Testing
Page 44: How to Write Better Code with Mutation Testing

1 def recent 2 - query = @twitter.search('"I really enjoy #pizza"') 3 + query = @twitter.search

Page 45: How to Write Better Code with Mutation Testing

1 def recent 2 - query = @twitter.search('"I really enjoy #pizza"') 3 + query = @twitter.search(nil)

Page 46: How to Write Better Code with Mutation Testing

1 - query.first(2).map { |tweet| "@#{tweet.author}" } 2 + query.last(2).map { |tweet| "@#{tweet.author}" } 3 end

Page 47: How to Write Better Code with Mutation Testing

1 - query.first(2).map { |tweet| "@#{tweet.author}" } 2 + query.map { |tweet| "@#{tweet.author}" } 3 end

Page 48: How to Write Better Code with Mutation Testing

1 it 'lists the two most recent gluttonous tweeters' do 2 tweets = [ 3 double(author: 'John'), 4 double(author: 'Jane'), 5 double(author: 'Devon') 6 ] 7 8 client = double('Client') 9 gluttons = Gluttons.new(client) 10 11 allow(client) 12 .to receive(:search) 13 .with('"I really enjoy #pizza"') 14 .and_return(tweets) 15 16 expect(gluttons.recent).to eql(%w[@John @Jane]) 17 end

Page 49: How to Write Better Code with Mutation Testing

Mutation Testing with mutant

Automated

Page 50: How to Write Better Code with Mutation Testing

Probably more clever than you

Mutation Testing with mutant

Page 51: How to Write Better Code with Mutation Testing

Example #1: Internal API

Page 52: How to Write Better Code with Mutation Testing

1 it 'returns a user when given a valid id' do 2 expect(get(:show, id: 1)).to eq(id: 1, name: 'John') 3 end 4 5 it 'renders JSON error when given an invalid id' do 6 expect(get(:show, id: 0)) 7 .to eq(error: "Could not find User with 'id'=0") 8 end

1 class UsersController < ApplicationController 2 def show 3 render json: User.find(params[:id].to_i) 4 rescue User::RecordNotFound => error 5 render json: { error: error.to_s } 6 end 7 end

Page 53: How to Write Better Code with Mutation Testing

1 def show 2 - render json: User.find(params[:id].to_i) 3 + render json: User.find(Integer(params[:id])) 4 rescue User::RecordNotFound => error 5 render json: { error: error.to_s } 6 end

Page 54: How to Write Better Code with Mutation Testing

1 def show 2 - render json: User.find(params[:id].to_i) 3 + render json: User.find(params.fetch(:id).to_i) 4 rescue User::RecordNotFound => error 5 render json: { error: error.to_s } 6 end

Page 55: How to Write Better Code with Mutation Testing

1 def show 2 - render json: User.find(params[:id].to_i) 3 + render json: User.find(Integer(params.fetch(:id))) 4 rescue User::RecordNotFound => error 5 render json: { error: error.to_s } 6 end

Page 56: How to Write Better Code with Mutation Testing

1 class UsersController < ApplicationController 2 def created_after 3 after = Date.parse(params[:after]) 4 render json: User.recent(after) 5 end 6 end

Page 57: How to Write Better Code with Mutation Testing

1 def created_after 2 - after = Date.parse(params[:after]) 3 + after = Date.iso8601(params[:after]) 4 render json: User.recent(after) 5 end

Page 58: How to Write Better Code with Mutation Testing

1 def created_after 2 - after = Date.parse(params[:after]) 3 + after = Date.iso8601(params[:after]) 4 render json: User.recent(after) 5 end

Page 59: How to Write Better Code with Mutation Testing

1 def created_after 2 - after = Date.parse(params[:after]) 3 + after = Date.iso8601(params[:after]) 4 render json: User.recent(after) 5 end

“2017-05-01""H29.05.01""Tue May 01 00:00:00 2017""Tue, 01 May 2017 00:00:00 +0000""Tue, 01 May 2017 00:00:00 GMT""May""I may be complete garbage"

“2017-05-01"

Date.parseDate.iso8601

Page 60: How to Write Better Code with Mutation Testing

Example #2: Hardening Regular Expressions

Page 61: How to Write Better Code with Mutation Testing

1 usernames.select do |username| 2 username =~ /^(John|Alain).+$/ 3 end

Page 62: How to Write Better Code with Mutation Testing

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username =~ /\A(John|Alain).+$/ 4 end

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username =~ /^(John|Alain).+\z/ 4 end

Page 63: How to Write Better Code with Mutation Testing

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username =~ /^(Alain).+$/ 4 end

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username =~ /^(John).+$/ 4 end

Page 64: How to Write Better Code with Mutation Testing

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username =~ /^(?:John|Alain).+$/ 4 end

Page 65: How to Write Better Code with Mutation Testing

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username.match?(/^(John|Alain).+$/) 4 end

Page 66: How to Write Better Code with Mutation Testing

1 usernames.select do |username| 2 - username =~ /^(John|Alain).+$/ 3 + username.match?(/\A(?:John|Alain).+\z/) 4 end

Page 67: How to Write Better Code with Mutation Testing

Example #3: Learning about your HTTP Client

Page 68: How to Write Better Code with Mutation Testing

1 def stars_for(repo) 2 url = "https://api.github.com/repos/#{repo}" 3 data = HTTParty.get(url).to_h 4 5 data['stargazers_count'] 6 end

Page 69: How to Write Better Code with Mutation Testing

1 def stars_for(repo) 2 url = "https://api.github.com/repos/#{repo}" 3 - data = HTTParty.get(url).to_h 4 + data = HTTParty.get(url) 5 6 data['stargazers_count'] 7 end

Page 70: How to Write Better Code with Mutation Testing

Practicality

Page 71: How to Write Better Code with Mutation Testing

/^(John|Alain).+$/

/\A(John|Alain).+$/

/^(John|Alain).+\z/

/^(Alain).+$/

/^(John).+$/

/^(?:John|Alain).+$/

Page 72: How to Write Better Code with Mutation Testing

$ mutant --use rspec --since master

Page 73: How to Write Better Code with Mutation Testing

$ mutant \--use rspec \--since master \‘YourApp::User’

Page 74: How to Write Better Code with Mutation Testing

1 module YourApp 2 class User < ActiveRecord::Base 3 # Dozens of includes, scopes, class methods, rails magic 4 # 100+ methods 5 6 def validate_email 7 # Simple method you're fixing 8 end 9 end 10 end

Page 75: How to Write Better Code with Mutation Testing

1 RSpec.describe YourApp::User do 2 # 100s of tests and setup unrelated to your task 3 4 describe '#validate_email' do 5 # Half dozen tests you are focusing on 6 end 7 end

Page 76: How to Write Better Code with Mutation Testing

mutant - Your Secret Weapon

Page 77: How to Write Better Code with Mutation Testing

Thanks!

John Backus - @backus - [email protected]