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

1,274 views

Published on

RubyConfUa2010 speech

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,274
On SlideShare
0
From Embeds
0
Number of Embeds
24
Actions
Shares
0
Downloads
5
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

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

  1. 1. Антипаттерны модульного тестирования Митин Павел, RubyConfUa 2010
  2. 2. О себе • Ruby on Rails разработчик • 4 года практики в стиле test-driven development • http://novembermeeting.blogspot.com
  3. 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
  4. 4. Indented test code vs Правило Шапокляк class PopularityCalculator def popular?(post) end end
  5. 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 end end
  6. 6. Indented test code vs Правило Шапокляк class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
  7. 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 end end
  8. 8. Indented test code vs Правило Шапокляк Мотивация: • борьба с дублированием • работы с неконтролируемыми аспектами системы(время, дисковое пространство и т.д.)
  9. 9. Indented test code vs Правило Шапокляк Правило Шапокляк: Это хорошо ... хорошо, что Вы зеленый и плоский
  10. 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
  11. 11. Indented test code vs Правило Шапокляк Бенефиты: • тесты проще понять • тесты содержат меньше ошибок
  12. 12. Production Logic in Test class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
  13. 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 == expected end
  14. 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 == expected end
  15. 15. Production Logic in Test Мотивация: упростить получение ожидаемого значения
  16. 16. Production Logic in Test
  17. 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
  18. 18. Production Logic in Test Бенефиты: мы действительно тестируем, а не только улучшаем тестовое покрытие :)
  19. 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
  20. 20. Too Many Expectations class NotificationService < Struct.new(:email_service, :sms_service) def notify_about(comment) end end
  21. 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_service end
  22. 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
  23. 23. Too Many Expectations def notify_about(comment) email_service.deliver_new_comment_email comment sms_service.deliver_new_comment_sms comment end
  24. 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
  25. 25. Too Many Expectations def notify_about(comment) # email_service.deliver_new_comment_email comment sms_service.deliver_new_comment_sms comment end
  26. 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>)
  27. 27. Too Many Expectations
  28. 28. Too Many Expectations Причины воспроизведения паттерна: отсутствие знаний о стаб-объектах
  29. 29. Too Many Expectations before do @sms_service = stub_everything @email_service = stub_everything @notification_service = NotificationService. new @email_service, @sms_service end
  30. 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
  31. 31. Too Many Expectations Бенефиты: одна ошибка - один падающий тест
  32. 32. Redundant Fixture describe PostRepository, "#popular" do it "should return all popular posts" do repository.popular.should include(popular_post) end end
  33. 33. Redundant Fixture class PostRepository def popular [] end end
  34. 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
  35. 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
  36. 36. Redundant Fixture Мотивация: желание получить в тестовом окружении “реальные” данные
  37. 37. Redundant Fixture VS
  38. 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
  39. 39. Redundant Fixture Бенефиты: • простой setup • сообщение о падении теста не перегружено лишними данными • профилактика "медленных" тестов
  40. 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
  41. 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 ==)
  42. 42. Neglected Diagnostic vs Ясный красный
  43. 43. Neglected Diagnostic vs Ясный красный module TestMoneyFormatter def inspect format end end class Money include TestMoneyFormatter end
  44. 44. Neglected Diagnostic vs Ясный красный 'BullshitProfitCalculator#calculate should return the projected profit' FAILED expected: $123.00, got: $1.00 (using ==)
  45. 45. Neglected Diagnostic vs Ясный красный Было: Стало: красный красный ясный красный зеленый зеленый рефакторинг рефакторинг
  46. 46. Neglected Diagnostic vs Ясный красный Бенефиты: ясное диагностическое сообщение упрощает дальнейшую поддержку теста
  47. 47. Еще антипаттерны • глобальные фикстуры • функциональный код, используемый только в тестах • нарушение изоляции тестов • зависимости из других слоев приложения • тестирование кода фреймворка
  48. 48. Антипаттерны в mocking TDD • мокание методов тестируемого модуля • мокание объектов-значений
  49. 49. Еще антипаттерны • “медленные” тесты • …
  50. 50. Рекомендуемая литература • Экстремальное программирование. Разработка через тестирование, Кент Бек • Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce
  51. 51. Исходный код примеров http://github.com/MitinPavel/test_antipatterns.git
  52. 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
  53. 53. Спасибо за внимание RubyConfUa 2010

×