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 APIPavel Asanov
 
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...«Автоматизация функционального тестирования REST API: секреты, тонкости и под...
«Автоматизация функционального тестирования REST API: секреты, тонкости и под...2ГИС Технологии
 
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/