Антипаттерны модульного тестирования

53
Антипаттерны модульного тестирования Митин Павел, RubyConfUa 2010

Upload: mitinpavel

Post on 20-Jun-2015

1.113 views

Category:

Documents


1 download

DESCRIPTION

RubyConfUa2010 speech

TRANSCRIPT

Page 1: Антипаттерны модульного тестирования

Антипаттерны

модульного тестирования

Митин Павел, RubyConfUa 2010

Page 2: Антипаттерны модульного тестирования

О себе

• Ruby on Rails разработчик

• 4 года практики в стиле test-driven development

• http://novembermeeting.blogspot.com

Page 3: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

describe PopularityCalculator, '#popular?' do it 'should take into account \ the comment count' do subject.popular?(post).should be_false end

# ...end

Page 4: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

class PopularityCalculator def popular?(post) endend

Page 5: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

it 'should take into account the comment count' do posts = (0..19).map do |n| post_with_comment_count n end

posts.each do |post| if 10 < post.comment_count subject.popular?(post).should be_true else subject.popular?(post).should be_false end endend

Page 6: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

class PopularityCalculator THRESHOLD = 10

def popular?(post) THRESHOLD < post.comment_count endend

Page 7: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

it 'should take into account the comment count' do posts = (0..19).map do |n| post_with_comment_count n end

posts.each do |post| if 10 < post.comment_count subject.popular?(post).should be_true else subject.popular?(post).should be_false end endend

Page 8: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

Мотивация:

• борьба с дублированием

• работы с неконтролируемыми аспектами системы(время, дисковое пространство и т.д.)

Page 9: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

Правило Шапокляк: Это хорошо ... хорошо, что Вы зеленый и плоский

Page 10: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

it "should return true if the comment count / is more then the popularity threshold" do

post = post_with_comment_count THRESHOLD + 1 subject.popular?(post).should be_true

post = post_with_comment_count THRESHOLD + 100 subject.popular?(post).should be_true end

Page 11: Антипаттерны модульного тестирования

Indented test code vs Правило Шапокляк

Бенефиты:

• тесты проще понять

• тесты содержат меньше ошибок

Page 12: Антипаттерны модульного тестирования

Production Logic in Test

class PopularityCalculator THRESHOLD = 10

def popular?(post) THRESHOLD < post.comment_count end end

Page 13: Антипаттерны модульного тестирования

Production Logic in Test

it "should take into account the comment count" do post = post_with_comment_count 11 expected = THRESHOLD < post.comment_count actual = subject.popular? post actual.should == expectedend

Page 14: Антипаттерны модульного тестирования

Production Logic in Test

it "should take into account the comment count" do post = post_with_comment_count 11 expected = THRESHOLD < post.comment_count actual = subject.popular? post actual.should == expectedend

Page 15: Антипаттерны модульного тестирования

Production Logic in Test

Мотивация: упростить получение ожидаемого значения

Page 16: Антипаттерны модульного тестирования

Production Logic in Test

Page 17: Антипаттерны модульного тестирования

Production Logic in Test

it "should take into account the comment count" do

actual = subject.popular? post_with_comment_count(11)

actual.should be_true

end

Page 18: Антипаттерны модульного тестирования

Production Logic in Test

Бенефиты: мы действительно тестируем, а не только улучшаем тестовое покрытие :)

Page 19: Антипаттерны модульного тестирования

Too Many Expectations

describe NotificationService, "#notify_about" do it "should notify the post author by email" do notification_service.notify_about @comment end

it "should notify the post author by sms" end

Page 20: Антипаттерны модульного тестирования

Too Many Expectations

class NotificationService < Struct.new(:email_service, :sms_service)

def notify_about(comment) end end

Page 21: Антипаттерны модульного тестирования

Too Many Expectations

before do @email_service, @sms_service = mock, mock @comment = Comment.new 'some text', 'dummy post' @notification_service = NotificationService. new @email_service, @sms_serviceend

Page 22: Антипаттерны модульного тестирования

Too Many Expectations

it "should notify the post author by email" do

@email_service.expects(:deliver_new_comment_email).

with @comment

@sms_service.expects :deliver_new_comment_sms

@notification_service.notify_about @comment

end Too Many Expectations

it "should notify the post author by sms" do @email_service.expects :deliver_new_comment_email @sms_service.expects(:deliver_new_comment_sms). with @comment @notification_service.notify_about @comment end

Page 23: Антипаттерны модульного тестирования

Too Many Expectations

def notify_about(comment) email_service.deliver_new_comment_email comment sms_service.deliver_new_comment_sms comment end

Page 24: Антипаттерны модульного тестирования

Too Many Expectations

it "should notify the post author by email" do

@email_service.expects(:deliver_new_comment_email).

with @comment

@sms_service.expects :deliver_new_comment_sms

@notification_service.notify_about @comment

end

it "should notify the post author by sms" do

@email_service.expects :deliver_new_comment_email

@sms_service.expects(:deliver_new_comment_sms).

with @comment

@notification_service.notify_about @comment

end

Page 25: Антипаттерны модульного тестирования

Too Many Expectations

def notify_about(comment) # email_service.deliver_new_comment_email comment sms_service.deliver_new_comment_sms comment end

Page 26: Антипаттерны модульного тестирования

Too Many Expectations

1)

Mocha::ExpectationError in 'NotificationService#notify_about should notify the post author by email'

not all expectations were satisfied

unsatisfied expectations:

- expected exactly once, not yet invoked: #<Mock:0xb74cdd64>.deliver_new_comment_email(#<Comment:0xb74cdb70>,

#<Mock:0xb74cdf08>)

satisfied expectations:

- expected exactly once, already invoked once: #<Mock:0xb74cde2c>.get(any_parameters)

- expected exactly once, already invoked once: #<Mock:0xb74cdc9c>.author_id(any_parameters)

- expected exactly once, already invoked once: nil.deliver_new_comment_sms(any_parameters)

2)

Mocha::ExpectationError in 'NotificationService#notify_about should notify the post author by sms'

not all expectations were satisfied

unsatisfied expectations:

- expected exactly once, not yet invoked: #<Mock:0xb74c937c>.deliver_new_comment_email(any_parameters)

satisfied expectations:

- expected exactly once, already invoked once: #<Mock:0xb74c9444>.get(any_parameters)

- expected exactly once, already invoked once: #<Mock:0xb74c92b4>.author_id(any_parameters)

- expected exactly once, already invoked once: nil.deliver_new_comment_sms(#<Comment:0xb74c9188>,

#<Mock:0xb74c9520>)

Page 27: Антипаттерны модульного тестирования

Too Many Expectations

Page 28: Антипаттерны модульного тестирования

Too Many Expectations

Причины воспроизведения паттерна: отсутствие знаний о стаб-объектах

Page 29: Антипаттерны модульного тестирования

Too Many Expectations

before do

@sms_service = stub_everything

@email_service = stub_everything

@notification_service = NotificationService. new @email_service, @sms_serviceend

Page 30: Антипаттерны модульного тестирования

Too Many Expectations

it "should notify the post author by email" do

@email_service.expects(:deliver_new_comment_email).

with @comment, @author

@notification_service.notify_about @comment

end

it "should notify the post author by sms" do

@sms_service.expects(:deliver_new_comment_sms).

with @comment, @author

@notification_service.notify_about @comment

end

Page 31: Антипаттерны модульного тестирования

Too Many Expectations

Бенефиты: одна ошибка - один падающий тест

Page 32: Антипаттерны модульного тестирования

Redundant Fixture

describe PostRepository, "#popular" do it "should return all popular posts" do repository.popular.should include(popular_post) end end

Page 33: Антипаттерны модульного тестирования

Redundant Fixture

class PostRepository def popular [] end end

Page 34: Антипаттерны модульного тестирования

Redundant Fixture

it "should return all popular posts" do popular_posts = (1..2).map { build_popular_post } unpopular_posts = (1..3). map { build_unpopular_post } posts = (popular_posts + unpopular_posts).shuffle repository = PostRepository.new posts

actual = repository.popular

actual.should have(2).posts actual.should include(popular_posts.first, popular_posts.last) end

Page 35: Антипаттерны модульного тестирования

Redundant Fixture

it "should return all popular posts" do popular_posts = (1..2).map { build_popular_post } unpopular_posts = (1..3). map { build_unpopular_post } posts = (popular_posts + unpopular_posts).shuffle repository = PostRepository.new posts

actual = repository.popular

actual.should have(2).posts actual.should include(popular_posts.first, popular_posts.last) end

Page 36: Антипаттерны модульного тестирования

Redundant Fixture

Мотивация: желание получить в тестовом окружении “реальные” данные

Page 37: Антипаттерны модульного тестирования

Redundant Fixture

VS

Page 38: Антипаттерны модульного тестирования

Redundant Fixture

before do @popular_post = build_popular_post @unpopular_post = build_unpopular_post @repository = PostRepository.new( [@popular_post, @unpopular_post] )end it "should return a popular post" do @repository.popular.should include(@popular_post) end it "shouldn't return an unpopular post" do @repository.popular. should_not include(@unpopular_post) end

Page 39: Антипаттерны модульного тестирования

Redundant Fixture

Бенефиты:

• простой setup

• сообщение о падении теста не перегружено лишними данными

• профилактика "медленных" тестов

Page 40: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

describe BullshitProfitCalculator, "#calculate" do it "should return the projected profit" do actual = subject.calculate 'dummy author' actual.should == '$123'.to_money end end

class BullshitProfitCalculator def calculate(author) '$1'.to_money end end

Page 41: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

'BullshitProfitCalculator#calculate should return the projected profit' FAILED

expected: #<Money:0xb7447ebc @currency=#<Money::Currency id: usd

priority: 1, iso_code: USD, name: United States Dollar, symbol: $,

subunit: Cent, subunit_to_unit: 100, separator: ., delimiter: ,>,

@cents=12300, @bank=#<Money::VariableExchangeBank:0xb74dabb8

@rates={}, @mutex=#<Mutex:0xb74dab7c>, @rounding_method=nil>>,

got: #<Money:0xb7448038 @currency=#<Money::Currency id: usd

priority: 1, iso_code: USD, name: United States Dollar, symbol: $,

subunit: Cent, subunit_to_unit: 100, separator: ., delimiter: ,>,

@cents=100, @bank=#<Money::VariableExchangeBank:0xb74dabb8 @rates={},

@mutex=#<Mutex:0xb74dab7c>, @rounding_method=nil>> (using ==)

Page 42: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

Page 43: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

module TestMoneyFormatter def inspect format end end

class Money include TestMoneyFormatter end

Page 44: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

'BullshitProfitCalculator#calculate should return the projected profit' FAILED expected: $123.00, got: $1.00 (using ==)

Page 45: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

Было: Стало:

красный красный

ясный красный

зеленый зеленый

рефакторинг рефакторинг

Page 46: Антипаттерны модульного тестирования

Neglected Diagnostic vs Ясный красный

Бенефиты: ясное диагностическое сообщение упрощает дальнейшую поддержку теста

Page 47: Антипаттерны модульного тестирования

Еще антипаттерны

• глобальные фикстуры

• функциональный код, используемый только в тестах

• нарушение изоляции тестов

• зависимости из других слоев приложения

• тестирование кода фреймворка

Page 48: Антипаттерны модульного тестирования

Антипаттерны в mocking TDD

• мокание методов тестируемого модуля

• мокание объектов-значений

Page 49: Антипаттерны модульного тестирования

Еще антипаттерны

• “медленные” тесты

• …

Page 50: Антипаттерны модульного тестирования

Рекомендуемая литература

• Экстремальное программирование. Разработка через тестирование, Кент Бек

• Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce

Page 51: Антипаттерны модульного тестирования

Исходный код примеров

http://github.com/MitinPavel/test_antipatterns.git

Page 52: Антипаттерны модульного тестирования

Использованные изображения

• http://dreamworlds.ru/kartinki/27030-otricatelnye-personazhi-v-multfilmakh.html

• http://www.inquisitr.com/39089/former-police-officer-sues-for-discrimination-over-his-alcoholism-disability/

• http://teachpro.ru/source/obz11/Html/der11163.htm

• http://bigpicture.ru/?p=4302

• http://giga.satel.com.ua/index.php?newsid=25654

Page 53: Антипаттерны модульного тестирования

Спасибо за внимание

RubyConfUa 2010