SlideShare uma empresa Scribd logo
1 de 44
Baixar para ler offline
Обновленная версия доклада:
http://www.slideshare.net/MitinPavel/rubyconfua-2010-unittestantipatterns




Вступление

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

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

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


Однако в этом выступлении речь пойдет о тест-специфических
паттернах и антипаттернах
Правило Шапокляк

describe PopularityCalculator, "#popular?" do
  it "should take into account the comment count" do
    subject.popular?(post).should be_true
  end
end
Правило Шапокляк

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)
  end
end
Правило Шапокляк

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
Правило Шапокляк

THRESHOLD = 10

def popular?(post)
  THRESHOLD < post.comment_count
end
Правило Шапокляк

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


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


Мотивация:
 • борьба с дублированием
 • работы с неконтролируемыми аспектами системы (время,
   дисковое пространство и т.д.)
Правило Шапокляк




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

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
Правило Шапокляк
Бенефиты:
 • тесты проще понять
 • тесты содержат меньше ошибок
Дублирование алгоритма

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

class PopularityCalculator
  THRESHOLD = 10

  def popular?(post)
    THRESHOLD < post.comment_count
  end
end
Дублирование алгоритма

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
Дублирование алгоритма

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


Ошибка: тесты содержат алгоритм, который используется
функциональным кодом (часто это copy-paste)


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

it "should take into account the comment count" do
  actual = subject.popular? post_with_comment_count(999)
  actual.should be_true
end
Дублирование алгоритма

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

describe NotificationService, "#notify_about" do
  it "should notify the post author by email" do
    service.comment_was_added comment
  end
  it "should notify the post author by sms"
end
Пионер, ты в ответе за всё!

describe NotificationService, "#notify_about" do
  it "should notify the post author by email" do
    service.comment_was_added 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
Пионер, ты в ответе за всё!

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
Пионер, ты в ответе за всё!
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


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


Причина: непонимание разницы между моками и стабами
Пионер, ты в ответе за всё!
Пионер, ты в ответе за всё!

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
Пионер, ты в ответе за всё!

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

describe PostRepository, "#popular" do
  it "should return all popular posts" do
    repository.popular.should include(popular_posts)
  end
end
“Реальная” фикстура

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
“Реальная” фикстура

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
“Реальная” фикстура

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


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

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
“Реальная” фикстура

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

describe BullshitProfitCalculator, "#calculate" do
  it "should return the projected profit" do
    actual = subject.calculate 'dummy author'
    actual.should == '$123'.to_money
  end
end
Ясный красный

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
Ясный красный
'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 ==)
Ясный красный

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

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

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

module TestMoneyFormatter
  def inspect
    format
  end
end

class Money
  include TestMoneyFormatter
end
Ясный красный
Было:

'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 ==)
Ясный красный
Было: "красный                   -> зеленый -> рефакторинг"
Стало: "красный -> ясный красный -> зеленый -> рефакторинг"
Еще антипаттерны

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

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

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




 • Экстремальное программирование. Разработка через тестирование,
   Кент Бек
 • Growing Object-Oriented Software, Guided by Tests by Steve Freeman
   and Nat Pryce
Исходный код примеров

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

 • 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/

Mais conteúdo relacionado

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

Алексей Ильенко "In real-time with Apache Kafka"
Алексей Ильенко "In real-time with Apache Kafka"Алексей Ильенко "In real-time with Apache Kafka"
Алексей Ильенко "In real-time with Apache Kafka"Fwdays
 
Behat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и MinkBehat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и Minktyomo4ka
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?Vasil Remeniuk
 
Сергей Крапивенский
Сергей КрапивенскийСергей Крапивенский
Сергей КрапивенскийCodeFest
 
Леонид Динерштейн - Разработка программ через тестирование поведения средства...
Леонид Динерштейн - Разработка программ через тестирование поведения средства...Леонид Динерштейн - Разработка программ через тестирование поведения средства...
Леонид Динерштейн - Разработка программ через тестирование поведения средства...SQALab
 
CodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлению
CodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлениюCodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлению
CodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлениюCodeFest
 
Михаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с CodeceptionМихаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с CodeceptionAlbina Tiupa
 
Эффективный C++
Эффективный C++Эффективный C++
Эффективный C++Andrey Karpov
 
М. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с CodeceptionМ. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с CodeceptionAlbina Tiupa
 
Cобачники против кинофобов
Cобачники против кинофобовCобачники против кинофобов
Cобачники против кинофобовLidiya Myalkina
 
Автоматизация функционального тестирования REST API: секреты, тонкости и подв...
Автоматизация функционального тестирования REST API: секреты, тонкости и подв...Автоматизация функционального тестирования REST API: секреты, тонкости и подв...
Автоматизация функционального тестирования REST API: секреты, тонкости и подв...SQALab
 
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...2ГИС Технологии
 
Автоматизация функционального тестирования REST API
Автоматизация функционального тестирования REST APIАвтоматизация функционального тестирования REST API
Автоматизация функционального тестирования REST APIPavel Asanov
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Ivan Tsyganov
 
TDD или как я стараюсь писать код
TDD или как я стараюсь писать кодTDD или как я стараюсь писать код
TDD или как я стараюсь писать кодMoscowDjango
 
PHP Tricks
PHP TricksPHP Tricks
PHP TricksBlackFan
 

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

Javascript 1
Javascript 1Javascript 1
Javascript 1
 
Алексей Ильенко "In real-time with Apache Kafka"
Алексей Ильенко "In real-time with Apache Kafka"Алексей Ильенко "In real-time with Apache Kafka"
Алексей Ильенко "In real-time with Apache Kafka"
 
Behat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и MinkBehat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и Mink
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?
 
Сергей Крапивенский
Сергей КрапивенскийСергей Крапивенский
Сергей Крапивенский
 
Леонид Динерштейн - Разработка программ через тестирование поведения средства...
Леонид Динерштейн - Разработка программ через тестирование поведения средства...Леонид Динерштейн - Разработка программ через тестирование поведения средства...
Леонид Динерштейн - Разработка программ через тестирование поведения средства...
 
CodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлению
CodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлениюCodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлению
CodeFest 2012. Корсаков С. — Cucumber. Некоторые советы по приготовлению
 
Михаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с CodeceptionМихаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с Codeception
 
Эффективный C++
Эффективный C++Эффективный C++
Эффективный C++
 
Being SOLID
Being SOLIDBeing SOLID
Being SOLID
 
М. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с CodeceptionМ. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с Codeception
 
Cобачники против кинофобов
Cобачники против кинофобовCобачники против кинофобов
Cобачники против кинофобов
 
Автоматизация функционального тестирования REST API: секреты, тонкости и подв...
Автоматизация функционального тестирования REST API: секреты, тонкости и подв...Автоматизация функционального тестирования REST API: секреты, тонкости и подв...
Автоматизация функционального тестирования REST API: секреты, тонкости и подв...
 
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
 
Автоматизация функционального тестирования REST API
Автоматизация функционального тестирования REST APIАвтоматизация функционального тестирования REST API
Автоматизация функционального тестирования REST API
 
Tdd php
Tdd phpTdd php
Tdd php
 
бегун
бегунбегун
бегун
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
 
TDD или как я стараюсь писать код
TDD или как я стараюсь писать кодTDD или как я стараюсь писать код
TDD или как я стараюсь писать код
 
PHP Tricks
PHP TricksPHP Tricks
PHP Tricks
 

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

  • 1. Обновленная версия доклада: http://www.slideshare.net/MitinPavel/rubyconfua-2010-unittestantipatterns Вступление О себе: • Ruby on Rails разработчик • 4 года практики в стиле test-driven development • http://novembermeeting.blogspot.com
  • 2. Вступление О чем будем говорить: • распространенные • антипаттерны • автоматического • модульного • тестирования
  • 3. Вступление Большинство практик написания чистого кода применимо к тестам: • содержательные имена • компактные методы/функции • принцип единственной ответственности • … Однако в этом выступлении речь пойдет о тест-специфических паттернах и антипаттернах
  • 4. Правило Шапокляк describe PopularityCalculator, "#popular?" do it "should take into account the comment count" do subject.popular?(post).should be_true end end
  • 5. Правило Шапокляк 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) end end
  • 6. Правило Шапокляк 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
  • 7. Правило Шапокляк THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end
  • 8. Правило Шапокляк Название: Indented Test Code Ошибка: тестовый код содержит циклы и/или условные конструкции Мотивация: • борьба с дублированием • работы с неконтролируемыми аспектами системы (время, дисковое пространство и т.д.)
  • 9. Правило Шапокляк Это хорошо ... хорошо, что Вы зеленый и плоский
  • 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. Правило Шапокляк Бенефиты: • тесты проще понять • тесты содержат меньше ошибок
  • 12. Дублирование алгоритма Используем функциональный код предыдущего примера class PopularityCalculator THRESHOLD = 10 def popular?(post) THRESHOLD < post.comment_count end end
  • 13. Дублирование алгоритма 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. Дублирование алгоритма Название: Test Logic in Prouction Ошибка: тесты содержат алгоритм, который используется функциональным кодом (часто это copy-paste) Мотивация: получение актуального значения в тестовом окружении
  • 16. Дублирование алгоритма it "should take into account the comment count" do actual = subject.popular? post_with_comment_count(999) actual.should be_true end
  • 17. Дублирование алгоритма Бенефиты: все выгоды тестирования методом черного ящика
  • 18. Пионер, ты в ответе за всё! describe NotificationService, "#notify_about" do it "should notify the post author by email" do service.comment_was_added comment end it "should notify the post author by sms" end
  • 19. Пионер, ты в ответе за всё! describe NotificationService, "#notify_about" do it "should notify the post author by email" do service.comment_was_added 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. Пионер, ты в ответе за всё! 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. Пионер, ты в ответе за всё! 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>)
  • 22. Пионер, ты в ответе за всё! Название: Too Many Expectations Ошибка: моки используются вместо стабов Причина: непонимание разницы между моками и стабами
  • 23. Пионер, ты в ответе за всё!
  • 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. Пионер, ты в ответе за всё! Бенефиты: одна ошибка -- один падающий тест
  • 26. “Реальная” фикстура describe PostRepository, "#popular" do it "should return all popular posts" do repository.popular.should include(popular_posts) end end
  • 27. “Реальная” фикстура 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
  • 28. “Реальная” фикстура 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
  • 29. “Реальная” фикстура Ошибка: фикстура содержит данных больше, чем это необходимо для конкретного теста Мотивация: • попытка воспроизвести "реальные" данные в тестовом окружение
  • 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. “Реальная” фикстура Бенефиты: • простой setup • сообщение о падении теста не перегружено лишними данными • профилактика "медленных" тестов
  • 32. Ясный красный describe BullshitProfitCalculator, "#calculate" do it "should return the projected profit" do actual = subject.calculate 'dummy author' actual.should == '$123'.to_money end end
  • 33. Ясный красный 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
  • 34. Ясный красный '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 ==)
  • 35. Ясный красный Название: Diagnostics Aren't a First-Class Feature Ошибка: непонятное сообщение о падающем тесте (многословное или малоинформативное)
  • 36. Ясный красный Возможное решение: module TestMoneyFormatter def inspect format end end class Money include TestMoneyFormatter end
  • 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. Ясный красный Было: "красный -> зеленый -> рефакторинг" Стало: "красный -> ясный красный -> зеленый -> рефакторинг"
  • 39. Еще антипаттерны • глобальные фикстуры • функциональный код, используемый только в тестах • нарушение изоляции тестов • зависимости из других слоев приложения • тестирование кода фреймворка
  • 40. Антипаттерны в mocking TDD • мокание методов тестируемого модуля • мокание объектов-значений
  • 41. И еще • “медленные” тесты • …
  • 42. Рекомендуемая литература • Экстремальное программирование. Разработка через тестирование, Кент Бек • Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce
  • 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/