design for testability: mocks, stubs, refactoring
Post on 01-Nov-2014
393 Views
Preview:
DESCRIPTION
TRANSCRIPT
Design for TestabilityMocks, Stubs, Refactoring
by Sergey Teplyakov, @STeplyakov
Что не так с дизайном наших
систем?
Что приводит к плохому дизайну?
• Ошибки на начальных этапах?• Недопонимание требований• Жесткая архитектура• Предварительное обобщение• …
• Постоянные изменения требований?
Что такое «плохой дизайн»?
«Главный» критерий плохого дизайна
«А вот я бы сделал это не так!»
That’s not the way I would have done it, TNTWIWHDI
Критерии плохого дизайна• Жесткость (Rigidity)
• Хрупкость (Fragility)
• Неподвижность (Immobility)
Юнит-тесты, как лакмусовая бумажка хорошего дизайна
И как этого зверя тестировать?
ServiceLocator
+ Get<T>() : T
<<Interface>>
ILogger
+LogError(error) : void
Configuration
+Instance: Configuration
ClassUnderTest
Database
+GetEmployee(): Employee
<<Interface>>
IServiceProxy
+Compute(Data) : void
<<Interface>>
IViewModelManager
+Show(ViewModel) : void
«Предусловия» юнит тестов• Требуется ясный «контракт»
класса• Четкий «вход»• Четкий «выход»
• Минимальное количество связей
Test Doubles: Stubs & Mocks• Стабы - эмулируют состояние• Моки - проверяют поведение
Пример стаб-объектаLogger
<<Interface>>
ILogConfigurator
+GetConfig() : Config
LogConfiguratorStub
+GetConfig(): Config
Возвращает "поддельный" конфиг
// Добавляем в поддельную конфигурацию из 3-х аппендеровint appenders = 3;var stub = new LogConfiguratorStub(new Config(appenders));var logger = new Logger(stub);
// Проверяем, что логгер сконфигурирован корректноAssert.That( logger.GetAppenders().Count, Is.EqualTo(appenders));
Пример мок-объектаLogger
<<Interface>>
ILogAppender
+Write(message) : Void
LogAppenderMock
+Write(message) : Void+WritenMessage: String
Запоминает информацию о
вызовах
// Arrange var mock = new LogAppenderMock(); var logger = new Logger(mock);
// Act logger.Write("Msg");
// Assert Assert.That(mock.WrittenMessage, Is.Not.Null);
Юнит тесты – не серебряная пуля!
Наивная реализация модуля расчета заработной платы
Payroll
+ PayEmployees() : void11
CheckWriter
+ WriteCheck() : void
EmployeeDatabase
+ GetEmployee() : Employee+ PutEmployee(e: Employee)
1
1
Employee
+ CalculatePay() : Money+ PostPayment(m: Money)
1 1
«Плохой» дизайн
Выделяем интерфейсы!
Payroll
+ PayEmployees() : void11
CheckWriter
+ WriteCheck() : void
EmployeeDatabase
+ GetEmployee() : Employee+ PutEmployee(e: Employee)
1
1
Employee
+ CalculatePay() : Money+ PostPayment(m: Money)
1 1<<Interface>>
ICheckWriter
+WriteCheck() : void
<<Interface>>
IEmployeeDatabase
+ GetEmployee() : Employee+ PutEmployee(e: Employee)
<<Interface>>
IEmployee
+ CalculatePay() : Money+ PostPayment(m: Money)
Теперь дизайн тестируемый!
[Test]public void TestPayroll() { MockEmployeeDatabase db = new MockEmployeeDatabase(); MockCheckWriter w = new MockCheckWriter(); Payroll p = new Payroll(db, w); p.PayEmployees(); Assert.IsTrue(w.ChecksWereWrittenCorrectly()); Assert.IsTrue(db.PaymentsWerePostedCorrectly()); }
Стал ли дизайн лучше?
• Дизайн не изменился!• Груда кода в каждом тесте (*)• Сложность проверки граничных
условий• Динамическая типизация !=
хороший дизайн!
Альтернативный подход• Нужно ли выделять интерфейс для
CheckWriter-а?• Нужно ли выделять интерфейс для
EmployeeDatabase? • Нет ли скрытых абстракций?• Нужен ли IEmployee?• Не делает ли Employee слишком
много?
Альтернативный подход
Payroller
+ PayEmployee(Employee)+ CalculatePayment(Money)
1
1
CheckWriter
+ WriteCheck() : void
EmployeeDatabase
+ GetEmployee() : Employee+ PutEmployee(e: Employee)
1
1
Employee
+ PostPayment(m: Money)
1
1
Payroll
+ PayEmployees()
1
1
<<Interface>>
ICheckWriter
+WriteCheck() : void
Переносим логику из Payroll
Убираем метод CalculatePayment
Проверяем интеграционными
тестами
Идеальный дизайн для тестирования
PaymentCalculator
+Calculate(PaymentInfo) : Money
PaymentInfo
+ WorkScheduler: Scheduler
Money
+ Value: Decimal
Argument Result
Метод Calculate без побочных эффектов!
[TestCaseSource("GetPaymentInfo")]public void Test_Payment_Information(PaymentInfo pi, Money expectedPayment){ // Arrange var calculator = new PaymentCalculator();
// Act var actualPayment = calculator.Calculate(pi);
// Assert Assert.That(expectedPayment, Is.EqualTo(actualPayment));}
А в чем разница?
• Отделение инфраструктуры от логики
• Уменьшение связанности• Возможность повторного
использования• Простота тестов
Дизайн и борьба со сложностьюЛюбая сложная система строится на основе проверенных модулей более низкого уровня. Гради Буч
Аксиома управления зависимостямиThe more complex a class or component is, the more decoupled it should be.Ted Faison – Event-Based Programming
Слепое стремление к тестируемости ведет к …• нарушению инкапсуляции;• проблемам сопровождения;• неявной связности;
Важное следствие …
Хороший дизайн == тестируемый дизайнТестируемый дизайн != хороший дизайн
AS A RULE OF THUMB
Design for Testability…
От тестируемости к хорошему дизайну
От хорошего дизайна к тестируемости
Вопросы?
Design for Testability: Mocks, Stubs, Refactoring
Sergey Teplyakov
Visual C# MVP
SergeyTeplyakov.blogspot.com
Заповни АнкетуВиграй Приз
http://anketa.msswit.in.ua
top related