• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Антипаттерны модульного тестирования
 

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

on

  • 1,146 views

RubyConfUa2010 speech

RubyConfUa2010 speech

Statistics

Views

Total Views
1,146
Views on SlideShare
1,127
Embed Views
19

Actions

Likes
1
Downloads
3
Comments
0

3 Embeds 19

http://wiki.dev.localnet 8
http://cl.localnet 7
http://www.linkedin.com 4

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

    • Антипаттерны модульного тестирования Митин Павел, RubyConfUa 2010
    • О себе • Ruby on Rails разработчик • 4 года практики в стиле test-driven development • http://novembermeeting.blogspot.com
    • 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
    • Indented test code vs Правило Шапокляк class PopularityCalculator def popular?(post) end end
    • 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 end end
    • Indented test code vs Правило Шапокляк class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
    • 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 end end
    • Indented test code vs Правило Шапокляк Мотивация: • борьба с дублированием • работы с неконтролируемыми аспектами системы(время, дисковое пространство и т.д.)
    • Indented test code vs Правило Шапокляк Правило Шапокляк: Это хорошо ... хорошо, что Вы зеленый и плоский
    • 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
    • Indented test code vs Правило Шапокляк Бенефиты: • тесты проще понять • тесты содержат меньше ошибок
    • Production Logic in Test class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
    • 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 == expected end
    • 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 == expected end
    • Production Logic in Test Мотивация: упростить получение ожидаемого значения
    • Production Logic in Test
    • 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
    • Production Logic in Test Бенефиты: мы действительно тестируем, а не только улучшаем тестовое покрытие :)
    • 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
    • Too Many Expectations class NotificationService < Struct.new(:email_service, :sms_service) def notify_about(comment) end end
    • 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_service end
    • 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
    • Too Many Expectations def notify_about(comment) email_service.deliver_new_comment_email comment sms_service.deliver_new_comment_sms comment end
    • 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
    • Too Many Expectations def notify_about(comment) # email_service.deliver_new_comment_email comment sms_service.deliver_new_comment_sms comment end
    • 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>)
    • Too Many Expectations
    • Too Many Expectations Причины воспроизведения паттерна: отсутствие знаний о стаб-объектах
    • Too Many Expectations before do @sms_service = stub_everything @email_service = stub_everything @notification_service = NotificationService. new @email_service, @sms_service end
    • 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
    • Too Many Expectations Бенефиты: одна ошибка - один падающий тест
    • Redundant Fixture describe PostRepository, "#popular" do it "should return all popular posts" do repository.popular.should include(popular_post) end end
    • Redundant Fixture class PostRepository def popular [] end end
    • 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
    • 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
    • Redundant Fixture Мотивация: желание получить в тестовом окружении “реальные” данные
    • Redundant Fixture VS
    • 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
    • Redundant Fixture Бенефиты: • простой setup • сообщение о падении теста не перегружено лишними данными • профилактика "медленных" тестов
    • 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
    • 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 ==)
    • Neglected Diagnostic vs Ясный красный
    • Neglected Diagnostic vs Ясный красный module TestMoneyFormatter def inspect format end end class Money include TestMoneyFormatter end
    • Neglected Diagnostic vs Ясный красный 'BullshitProfitCalculator#calculate should return the projected profit' FAILED expected: $123.00, got: $1.00 (using ==)
    • Neglected Diagnostic vs Ясный красный Было: Стало: красный красный ясный красный зеленый зеленый рефакторинг рефакторинг
    • Neglected Diagnostic vs Ясный красный Бенефиты: ясное диагностическое сообщение упрощает дальнейшую поддержку теста
    • Еще антипаттерны • глобальные фикстуры • функциональный код, используемый только в тестах • нарушение изоляции тестов • зависимости из других слоев приложения • тестирование кода фреймворка
    • Антипаттерны в mocking TDD • мокание методов тестируемого модуля • мокание объектов-значений
    • Еще антипаттерны • “медленные” тесты • …
    • Рекомендуемая литература • Экстремальное программирование. Разработка через тестирование, Кент Бек • Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce
    • Исходный код примеров http://github.com/MitinPavel/test_antipatterns.git
    • Использованные изображения • 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
    • Спасибо за внимание RubyConfUa 2010