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