Вступление

О себе:
 • Ruby on Rails разработчик
 • 4 года практики в стиле test-driven development
 • http://novembermeet...
Вступление

О чем будем говорить:
 • распространенные
 • антипаттерны
 • автоматического
 • модульного
 • тестирования
Вступление

Большинство практик написания чистого кода применимо к
тестам:
 • содержательные имена
 • компактные методы/фу...
Правило Шапокляк

Название: Indented Test Code


Описание: тестовый код содержит циклы и/или условные
конструкции


Мотива...
Правило Шапокляк


Начинаем с тестового кода

describe PopularityCalculator, "#popular?" do
  it "should take into account...
Правило Шапокляк


Начинаем с тестового кода

describe PopularityCalculator, "#popular?" do
  it "should take into account...
Правило Шапокляк

И через минуту имеем тест

it "should take into account the comment count" do
  posts = (0..20).map { |i...
Правило Шапокляк

Добавляем функциональный код для теста

class PopularityCalculator
  THRESHOLD = 10

  def popular?(post...
Правило Шапокляк




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

Правильно написанный тестовый метод

it "should return true if the comment count /
    is more then the ...
Правило Шапокляк


Бенефиты:
 • тесты проще понять
 • тесты содержат меньше ошибок
Дублирование алгоритма

Название: Prouction Logic in Test


Описание: тесты содержат алгоритм, который используется
функци...
Дублирование алгоритма

Используем функциональный код предыдущего примера

class PopularityCalculator
  THRESHOLD = 10

  ...
Дублирование алгоритма

Вторая строка теста содержит алгоритм функционального
кода

it "should take into account the comme...
Дублирование алгоритма
Дублирование алгоритма

Правильная реализация теста

it "should take into account the comment count" do
  actual = subject...
Дублирование алгоритма

Бенефиты: все выгоды тестирования методом черного ящика
Пионер, ты в ответе за всё!

Название: Too Many Expectations


Описание: моки используются вместо стабов


Причина: непони...
Пионер, ты в ответе за всё!

Начинаем писать тест

describe NotificationService, "#notify_about" do
  it "should notify th...
Пионер, ты в ответе за всё!

before do
  @author, @author_repository, @email_service =
    mock, mock, mock
end

it "shoul...
Пионер, ты в ответе за всё!

Закомментируем вызов одного из сервисов в функциональном
коде

def notify_about(comment)
  au...
Пионер, ты в ответе за всё!
Был нарушен только один аспект поведения функционального
кода, а упали оба теста
1)
Mocha::Exp...
Пионер, ты в ответе за всё!
Пионер, ты в ответе за всё!

Правильная реализация

before do
 @author_repository = stub ...
 @sms_service = stub ...
 @em...
Пионер, ты в ответе за всё!

Бенефиты: одна ошибка -- один падающий тест
“Реальная” фикстура

Описание: фикстура содержит данных больше, чем это
необходимо для конкретного теста


Мотивация:
 • п...
“Реальная” фикстура

Опять начинаем с тестового кода

describe PostRepository, "#popular" do
  it "should return all popul...
“Реальная” фикстура

Опять начинаем с тестового кода

describe PostRepository, "#popular" do
  it "should return all popul...
“Реальная” фикстура

Воспроизводим антипаттерн

before do
  @popular_posts = (1..2).map { build_popular_post }
  unpopular...
“Реальная” фикстура

Правильно написанный тест


it "should return a popular post" do
  post = build_popular_post
  reposi...
“Реальная” фикстура

Бенефиты:
 • простой setup
 • сообщение о падении теста не перегружено лишними
   данными
 • профилак...
Ясный красный

Название: Diagnostics Aren't a First-Class Feature

Ошибка: непонятное сообщение о падающем тесте
(многосло...
Ясный красный

Тест

describe BullshitProfitCalculator, "#calculate" do
  it "should return the projected profit" do
    a...
Ясный красный

Тест

describe BullshitProfitCalculator, "#calculate" do
  it "should return the projected profit" do
    a...
Ясный красный

Сообщение об ошибке
'BullshitProfitCalculator#calculate should return the projected
profit' FAILED
expected...
Ясный красный

Возможное решение

module TestMoneyFormatter
  def inspect
    format
  end
end

class Money
  include Test...
Ясный красный
Было:

'BullshitProfitCalculator#calculate should return the projected
profit' FAILED
expected: #<Money:0xb7...
Ясный красный
Было: "красный                   -> зеленый -> рефакторинг"
Стало: "красный -> ясный красный -> зеленый -> р...
Еще антипаттерны

 • глобальные фикстуры
 • функциональный код, используемый только в тестах
 • нарушение изоляции тестов
...
Антипаттерны в mocking TDD

 • мокание методов тестируемого модуля
 • мокание объектов-значений
И еще

 • “медленные” тесты
 • …
Рекомендуемая литература




 • Экстремальное программирование. Разработка через тестирование,
   Кент Бек
 • Growing Obje...
Исходный код примеров

http://github.com/MitinPavel/test_antipatterns.git
Использованные изображения

 • http://www.content.su/?p=318
 • http://www.inquisitr.com/39089/former-police-officer-sues-f...
Upcoming SlideShare
Loading in …5
×

Антипаттерны модульного тестирования (Донецкий кофе-и-код Сентябрь 2010)

1,055 views

Published on

Слайды к докладу на встрече "Донецкий кофе-и-код"

Дата: 18 сентября 2010
Подробности: http://cnc.dn.ua/meeting/september-2010-meeting-unit-tests-antipatterns-spravochnik.html

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,055
On SlideShare
0
From Embeds
0
Number of Embeds
108
Actions
Shares
0
Downloads
4
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Антипаттерны модульного тестирования (Донецкий кофе-и-код Сентябрь 2010)

  1. 1. Вступление О себе: • Ruby on Rails разработчик • 4 года практики в стиле test-driven development • http://novembermeeting.blogspot.com
  2. 2. Вступление О чем будем говорить: • распространенные • антипаттерны • автоматического • модульного • тестирования
  3. 3. Вступление Большинство практик написания чистого кода применимо к тестам: • содержательные имена • компактные методы/функции • принцип единственной ответственности • … Однако в этом выступлении речь пойдет о тест-специфических паттернах и антипаттернах
  4. 4. Правило Шапокляк Название: Indented Test Code Описание: тестовый код содержит циклы и/или условные конструкции Мотивация: • борьба с дублированием • работы с неконтролируемыми аспектами системы (время, дисковое пространство и т.д.)
  5. 5. Правило Шапокляк Начинаем с тестового кода describe PopularityCalculator, "#popular?" do it "should take into account the comment count" do subject.popular?(post).should be_true end end
  6. 6. Правило Шапокляк Начинаем с тестового кода describe PopularityCalculator, "#popular?" do it "should take into account the comment count" do subject.popular?(post).should be_true end end Добавляем минимальную реализацию class PopularityCalculator def popular?(post) true end end
  7. 7. Правило Шапокляк И через минуту имеем тест it "should take into account the comment count" do posts = (0..20).map { |i| post_with_comment_count i } 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. Правило Шапокляк Добавляем функциональный код для теста class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
  9. 9. Правило Шапокляк Это хорошо ... хорошо, что Вы зеленый и плоский
  10. 10. Правило Шапокляк Правильно написанный тестовый метод 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. Правило Шапокляк Бенефиты: • тесты проще понять • тесты содержат меньше ошибок
  12. 12. Дублирование алгоритма Название: Prouction Logic in Test Описание: тесты содержат алгоритм, который используется функциональным кодом (часто это copy-paste) Мотивация: получение актуального значения в тестовом окружении
  13. 13. Дублирование алгоритма Используем функциональный код предыдущего примера class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
  14. 14. Дублирование алгоритма Вторая строка теста содержит алгоритм функционального кода 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. Дублирование алгоритма
  16. 16. Дублирование алгоритма Правильная реализация теста it "should take into account the comment count" do actual = subject.popular? post_with_comment_count(11) actual.should be_true end
  17. 17. Дублирование алгоритма Бенефиты: все выгоды тестирования методом черного ящика
  18. 18. Пионер, ты в ответе за всё! Название: Too Many Expectations Описание: моки используются вместо стабов Причина: непонимание разницы между моками и стабами
  19. 19. Пионер, ты в ответе за всё! Начинаем писать тест describe NotificationService, "#notify_about" do it "should notify the post author by email" do service.notify_about comment end it "should notify the post author by sms" end Добавляем функциональный код class NotificationService < Struct.new(:email_service, :sms_service, :author_repository) def notify_about(comment) end end
  20. 20. Пионер, ты в ответе за всё! before do @author, @author_repository, @email_service = mock, mock, mock end it "should notify the post author by email" do @author_repository.expects(:get).returns @author @email_service.expects(:deliver_new_comment_email) .with @comment, @author @sms_service.expects :deliver_new_comment_sms notification_service.notify_about @comment end it "should notify the post author by sms" do @author_repository.expects(:get).returns @author @email_service.expects :deliver_new_comment_email @sms_service.expects(:deliver_new_comment_sms) .with @comment, @author notification_service.notify_about @comment end
  21. 21. Пионер, ты в ответе за всё! Закомментируем вызов одного из сервисов в функциональном коде def notify_about(comment) author = author_repository.get comment.post.author_id # email_service.deliver_new_comment_email( # comment, author) sms_service.deliver_new_comment_sms comment, author end
  22. 22. Пионер, ты в ответе за всё! Был нарушен только один аспект поведения функционального кода, а упали оба теста 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>)
  23. 23. Пионер, ты в ответе за всё!
  24. 24. Пионер, ты в ответе за всё! Правильная реализация before do @author_repository = stub ... @sms_service = stub ... @email_service = stub ... end 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
  25. 25. Пионер, ты в ответе за всё! Бенефиты: одна ошибка -- один падающий тест
  26. 26. “Реальная” фикстура Описание: фикстура содержит данных больше, чем это необходимо для конкретного теста Мотивация: • попытка воспроизвести "реальные" данные в тестовом окружение
  27. 27. “Реальная” фикстура Опять начинаем с тестового кода describe PostRepository, "#popular" do it "should return all popular posts" do repository.popular.should include(popular_posts) end end
  28. 28. “Реальная” фикстура Опять начинаем с тестового кода describe PostRepository, "#popular" do it "should return all popular posts" do repository.popular.should include(popular_posts) end end Добавляем функциональный код class PostRepository def popular all_posts.select { true } end end
  29. 29. “Реальная” фикстура Воспроизводим антипаттерн before 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 end it "should return all popular posts" do actual = @repository.popular actual.should include(@popular_posts.first) actual.should include(@popular_posts.last) end
  30. 30. “Реальная” фикстура Правильно написанный тест it "should return a popular post" do post = build_popular_post repository = PostRepository.new [post] repository.popular.should include(post) end it "shouldn't return an unpopular post" do post = build_unpopular_post repository = PostRepository.new [post] repository.popular.should_not include(post) end
  31. 31. “Реальная” фикстура Бенефиты: • простой setup • сообщение о падении теста не перегружено лишними данными • профилактика "медленных" тестов
  32. 32. Ясный красный Название: Diagnostics Aren't a First-Class Feature Ошибка: непонятное сообщение о падающем тесте (многословное или малоинформативное)
  33. 33. Ясный красный Тест describe BullshitProfitCalculator, "#calculate" do it "should return the projected profit" do actual = subject.calculate 'dummy author' actual.should == '$123'.to_money end end
  34. 34. Ясный красный Тест 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
  35. 35. Ясный красный Сообщение об ошибке '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 ==)
  36. 36. Ясный красный Возможное решение module TestMoneyFormatter def inspect format end end class Money include TestMoneyFormatter end
  37. 37. Ясный красный Было: '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 ==) Стало: 'BullshitProfitCalculator#calculate should return the projected profit' FAILED expected: $123.00, got: $1.00 (using ==)
  38. 38. Ясный красный Было: "красный -> зеленый -> рефакторинг" Стало: "красный -> ясный красный -> зеленый -> рефакторинг"
  39. 39. Еще антипаттерны • глобальные фикстуры • функциональный код, используемый только в тестах • нарушение изоляции тестов • зависимости из других слоев приложения • тестирование кода фреймворка
  40. 40. Антипаттерны в mocking TDD • мокание методов тестируемого модуля • мокание объектов-значений
  41. 41. И еще • “медленные” тесты • …
  42. 42. Рекомендуемая литература • Экстремальное программирование. Разработка через тестирование, Кент Бек • Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce
  43. 43. Исходный код примеров http://github.com/MitinPavel/test_antipatterns.git
  44. 44. Использованные изображения • http://www.content.su/?p=318 • http://www.inquisitr.com/39089/former-police-officer-sues-for- discrimination-over-his-alcoholism-disability/ • http://www.skaskin.com/

×