tdd Оbjective-С
DESCRIPTION
TRANSCRIPT
© L
uxof
t Tra
inin
g 20
12
Test Driven Development
Разработка через тестирование
iOS, Bowling Game Kata
Ivan Dyachenko <[email protected]>
http://uamobile.in.ua/
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Экстремальное программирование (XP)
Разработка через тестирование (TDD)
Agile Manifesto в 2001 году.
Kent Beck
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Extreme Programming Practices
Whole Team
Small Releases
Planning Game
CustomerTests
CodingStandard
SustainablePace
CollectiveOwnership
ContinuousIntegration
Metaphor
Test-DrivenDevelopment
Simple Design
RefactoringPairProgramming
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Extreme Programming Practices
Whole Team
Small Releases
Planning Game
CustomerTests
CodingStandard
SustainablePace
CollectiveOwnership
ContinuousIntegration
Metaphor
Test-DrivenDevelopment
Simple Design
RefactoringPairProgramming
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
TDD
«Чистый код, который работает»
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Мифы TDD
Unit tests == TDD
TDD == 100% coverage
TDD == время * 2
TDD == серебряная пуля
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Два простых правила TDD
Удаляем дублирование
Пишем новый код только тогда, когда автоматический код не сработал
2
1
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Мантра TDD – “red, green, refactor”
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
3 Исправь ошибки
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
3 Исправь ошибки
4 Запусти и убедись что тесты упали
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
3 Исправь ошибки
4 Запусти и убедись что тесты упали
5 Напиши код
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
3 Исправь ошибки
4 Запусти и убедись что тесты упали
5 Напиши код
6 Запусти и убедись что тесты прошли
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
3 Исправь ошибки
4 Запусти и убедись что тесты упали
5 Напиши код
7 Рефакторинг
6 Запусти и убедись что тесты прошли
1 Напиши тест
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
RED / GREEN / REFACTOR
0 Подумай
2 Скомпилируй
3 Исправь ошибки
4 Запусти и убедись что тесты упали
5 Напиши код
7 Рефакторинг
6 Запусти и убедись что тесты прошли
1 Напиши тест
Повтори
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Ошибки TDD
Писать тест который сразу проходит
Писать тест после кода
Писать сразу много тестов
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Инструменты
§ OCUnit/SenTestingKit начиная IPhone SDK 2.2
§ GHUnit
§ Google Toolkit for Mac
§ OCMock
§ OCHamcrest
§ Kiwi
§ Cedar
§ …
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
OCUnit§ Object-oriented tests
§ Very low overhead
§ Tests are in the same language as the implementation
§ Test case classes cohabit with classes they test
§ Promotes aggressive refactoring
§ Helps capture requirements, expose dependencies
§ Total integration in the development process
§ Records testing expertise
§ Tests frameworks, bundles, or applications
§ Easy installation
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
OCUnit
Asserts
§ STAssertNil
§ STAssertNotNil
§ STAssertTrue
§ STAssertFalse
§ STAssertEquals
§ STAssertEqualObjects
§ STFail
§ ...
TestCase
§ test<Test Case>
§ setUp
§ tearDown
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Пример
Bowling Game Kata - подсчет очков игры в боулинг
www.objectmentor.comwiki.agiledev.ru
www.slideshare.net
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Правила игры в боулинг
Раунды
§ 10 раундов (frame-ов)
§ В одном frame – 2 броска
§ Цель – выбить кегли (pins)
§ Выигрывает набравший больше всех очков
Подсчет очков
§ 10 сразу (strike). Заканчивается досрочно.10 очков + pins + pins
§ 10 очков со второго броска (spare)10 очков + pins
§ Max 300 очков (12 strikes)
Последний frame
§ 1-й strike – дает возможность бросить еще два раза
§ 2-й spare – дает возможность бросить еще одни раз
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Надо написать
BowlingGame
+ roll(pins : int)+ score() : int
Написать класс BowlingGame, который имеет два метода
• roll (pins : int) – вызывается каждый раз когда игрок бросает шар. Pins – количество выбитых кеглей в этом броске
• score() : int – вызывается в конце игры. Показывает результат игры
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
A quick design session
BowlingGame
+ roll(pins : int)
Нам нужен BowlingGame класс
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
A quick design session
BowlingGame
+ roll(pins : int)
Frame
+ score() : int
Игра имеет 10 фреймов
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
A quick design session
BowlingGame
+ roll(pins : int)
Frame
+ score() : int
Roll
- pins : int
Каждый фрейм состоит из одного или
двух бросков
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
A quick design session
BowlingGame
+ roll(pins : int)
Frame
+ score() : int
Roll
- pins : int
Tenth FrameПоследний фрейм
содержит два или три броска
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
A quick design session
BowlingGame
+ roll(pins : int)
Frame
+ score() : int
Roll
- pins : int
Tenth Frame
Score функция должна пройтись по всем
фреймам и посчитать результат
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
A quick design session
BowlingGame
+ roll(pins : int)
Frame
+ score() : int
Roll
- pins : int
Tenth Frame
Strike или Spare зависит от количества выбитых кеглей в
FrameNext Frame
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Готовим среду для тестирования
+
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Еще раз повторим правила
Раунды
§ 10 раундов (frame-ов)
§ В одном frame – 2 броска
§ Цель – выбить кегли (pins)
§ Выигрывает набравший больше всех очков
Подсчет очков
§ 10 сразу (strike). Заканчивается досрочно.10 очков + pins + pins
§ 10 очков со второго броска (spare)10 очков + pins
§ Max 300 очков (12 strikes)
Последний frame
§ 1-й strike – дает возможность бросить еще два раза
§ 2-й spare – дает возможность бросить еще одни раз
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Пишем код
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Test List
Подумай/* should score zero on gutter game should score twenty when all one should score spare correctly should score all spares correctly should score strike correctly should score super game
*/
#import "BowlingGameTests.h”
@implementation BowlingGameTests
- (void)setUp{ [super setUp];}
- (void)tearDown{ [super tearDown];}
@end
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Assert First- (void)testGutterGame{ STAssertEquals(0, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
- (void)testGutterGame{ BowlingGame *game = [[BowlingGame alloc] init]; for (int i = 0; i < 20; i++) { [game roll:0]; } STAssertEquals(0, [game score], nil);}
Test First
Пишем тест игнорируя ошибки компилятора
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
@implementation BowlingGame
- (void)roll:(int)pins { }
- (int)score { return 0;}
@end
Исправляем ошибки, пишем код
Пишем минимум кода! KISS
- (void)testGutterGame{ BowlingGame *game = [[BowlingGame alloc] init]; for (int i = 0; i < 20; i++) { [game roll:0]; } STAssertEquals(0, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Следующий тестТест падает- (void)testAllOne
{ BowlingGame *game = [[BowlingGame alloc] init]; for (int i = 0; i < 20; i++) { [game roll:1]; } STAssertEquals(20, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Пишем код
Тест проходит
- (void)testAllOne{ BowlingGame *game = [[BowlingGame alloc] init]; for (int i = 0; i < 20; i++) { [game roll:1]; } STAssertEquals(20, [game score], nil);}
#import "BowlingGame.h"
@implementation BowlingGame
- (void)roll:(int)pins { score += pins;}
- (int)score { return score;}
@end
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
- (void)testGutterGame{ int n = 20; int pins = 0; for (int i = 0; i < n; i++) { [game roll:pins]; } STAssertEquals(0, [game score], nil);}
Убираем дублирование
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
- (void)testGutterGame { [self rollMany:0 times:20]; STAssertEquals(0, [game score], nil);}
- (void)testAllOne { [self rollMany:1 times:20]; STAssertEquals(20, [game score], nil);}
- (void)rollMany:(int)pins times:(int)n { for (int i = 0; i < n; i++) { [game roll:pins]; }}
Рефакторим
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Проверка на Spare
10 + 3
6
4
3
-
3
Тест падает
- (void)testScoreSpare { [game roll:5]; // spare [game roll:5]; [game roll:3]; [self rollMany:17 times:0]; STAssertEquals(16, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
#import "BowlingGame.h"
@implementation BowlingGame
- (void)roll:(int)pins { score += pins;}
- (int)score { return score;}
@end
Design reviewroll – занимается
подсчетом очков, но имя метода не указывает на это
score – не занимается подсчетом очков, хотя имя указывает, что
должна
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Redesign Добавляем метод в Ignore
//- (void)testScoreSpare {// [game roll:5]; // spare// [game roll:5];// [game roll:3];// [self rollMany:17 times:0];// STAssertEquals(16, [game score], nil);//}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Redesign#import "BowlingGame.h"
@implementation BowlingGame
- (void)roll:(int)pins { rolls[currentRoll++] = pins;}
- (int)score { int score = 0; for (int i = 0; i < 20; i++) score += rolls[i]; return score;}
@end
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Убираем Ignore
Тест падает
- (void)testScoreSpare { [game roll:5]; // spare [game roll:5]; [game roll:3]; [self rollMany:17 times:0]; STAssertEquals(16, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Пишем код
Хорошая идея! За одним исключением – это не работает
У нас все еще проблемы с дизайном. Очевидно, надо считать очки по фреймам
- (int)score { int score = 0; for (int i = 0; i < 20; i++) if (rolls[i] + rolls[i + 1] == 10) //spare score += 10 + rolls[i + 2]; ... score += rolls[i]; return score;}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Очередная сессия Redesign
Добавляем метод в Ignore
//- (void)testScoreSpare {// [game roll:5]; // spare// [game roll:5];// [game roll:3];// [self rollMany:17 times:0];// STAssertEquals(16, [game score], nil);//}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Refactoring
- (int)score { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) { score += rolls[i] + rolls[i + 1]; i += 2; } return score;}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Убираем Ignore
Тест падает
- (void)testScoreSpare { [game roll:5]; // spare [game roll:5]; [game roll:3]; [self rollMany:17 times:0]; STAssertEquals(16, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
- (int)score { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[i] + rolls[i + 1] == 10) {// spare score += 10 + rolls[i + 2]; i += 2; } else { score += rolls[i] + rolls[i + 1]; i += 2; } } return score;}
Пишем код
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Design review Плохое имя переменной
Плохо иметь комментарий в условии
- (int)score { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[i] + rolls[I + 1] == 10) {// spare score += 10 + rolls[i + 2]; i += 2; } else { score += rolls[i] + rolls[i + 1]; i += 2; } } return score;}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Refactoring
- (int)score { int score = 0; int ballIndex = 0; for (int frame = 0; frame < 10; frame++) { if ([self isSpare:ballIndex]) { score += 10 + rolls[ballIndex + 2]; ballIndex += 2; } else { score += rolls[ballIndex] + rolls[ballIndex + 1]; ballIndex += 2; } } return score;}
- (BOOL)isSpare:(int)ballIndex { return rolls[ballIndex] + rolls[ballIndex+1] == 10;}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
- (void)testScoreStrike { [game roll:10]; // strike [game roll:3]; [game roll:4]; [self rollMany:16 times:0]; STAssertEquals(24, [game score], nil);}
Проверка на Strike
10 + 3 + 4
10
*
3
4
3 + 4Комментарий в тесте
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Пишем код
- (int)score { int score = 0; int ballIndex = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[ballIndex] == 10) { score += 10 + ! ! ! ! ! ! rolls[ballIndex + 1] + ! ! ! ! ! ! ! ! ! rolls[ballIndex + 2]; ballIndex++; } else if ([self isSpare:ballIndex]) { score += 10 + rolls[ballIndex + 2]; ballIndex += 2; } else { score += rolls[ballIndex] + rolls[ballIndex + 1]; ballIndex += 2; } } return score;}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Design review
Комментарий для условия
Непонятное выражение
- (int)score { int score = 0; int ballIndex = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[ballIndex] == 10) { // strike score += 10 + rolls[ballIndex + 1] + rolls[ballIndex + 2]; ballIndex++; } else if ([self isSpare:ballIndex]) { score += 10 + rolls[ballIndex + 2]; ballIndex += 2; } else { score += rolls[ballIndex] + rolls[ballIndex + 1]; ballIndex += 2; } } return score;}
Thursday, December 6, 12
- (int)score { int score = 0; int ballIndex = 0; for (int frame = 0; frame < 10; frame++) { if ([self isStrike:ballIndex]) { score += 10 + [self strikeBonus:ballIndex]; ballIndex++; } else if ([self isSpare:ballIndex]) { score += 10 + [self spareBonus:ballIndex]; ballIndex += 2; } else { score += [self sumOfBallsInFrame:ballIndex]; ballIndex += 2; } } return score;}
© L
uxof
t Tra
inin
g 20
12
Refactoring
Thursday, December 6, 12
- (void)testScoreStrike { [self rollStrike]; [game roll:3]; [game roll:4]; [self rollMany:16 times:0]; STAssertEquals(24, [game score], nil);}
- (void)rollStrike { [game roll:10];}
© L
uxof
t Tra
inin
g 20
12
Refactoring
git checkout bowling-0.8
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Контрольный пример
- (void)testPrefectGame { [self rollMany:10 times:12]; STAssertEquals(300, [game score], nil);}
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Cтратегии запуска тестов
§ IDE
§ Console
§ CocoaPods
§ Maven
§ Test Runner
§ CI
§ File watcher
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Continuous integration
HudsonTeam City
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Преимущества от TDD
Меньше ошибок
Проще рефакторить
Документация
Лучший дизайн кода
Быстрее процесс разработки
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
BDD
Behavior Driven Development (BDD)
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
BDD – что это?
Language
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Difference between TDD, BDD & ATDD
TDD ATDD
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
BDD
Спецификация
Тесты
Документация
DDD-Domain Driven Testing + DSL + GIVEN / WHEN / THEN
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Kiwidescribe(@"BowlingGame", ^{
it(@"should score zero on gutter game", ^{ rollMany(20, 0); [[theValue([game score]) should] equal:theValue(0)]; });
it(@"should score twenty when all one", ^{ rollMany(20, 1); [[theValue([game score]) should] equal:theValue(20)]; });
it(@"should score spare correctly", ^{ [game roll:5]; [game roll:5]; [game roll:3]; rollMany(17, 0); [[theValue([game score]) should] equal:theValue(16)]; });
it(@"should score strike correctly", ^{ [game roll:10]; [game roll:3]; [game roll:4]; rollMany(16, 0); [[theValue([game score]) should] equal:theValue(24)]; });
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
*.feature# language: ruФункционал: Авторизация пользователей
Чтобы указывать себя автором снипетов, голосовать за снипеты и нарабатывать карму, пользователи должны иметь возможность регистрироваться
Сценарий: Успешная авторизация с указываемыми логином и паролем Допустим я зарегистрированный пользователь "User12" с паролем "User1212" И я на странице Авторизация Если ввожу в поле Логин "User12" И ввожу в поле Пароль "User1212" И кликаю кнопку "Войти" То я должен увидеть уведомление "Добро пожаловать!" И я должен оказаться на странице Страница пользователя
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
*.feature
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Вопросы ?
Thursday, December 6, 12
© L
uxof
t Tra
inin
g 20
12
Разработка через тестирование
git clone git://github.com/ivan-dyachenko/Trainings.git
https://github.com/ivan-dyachenko/Trainings
Thursday, December 6, 12