Skip to content

Latest commit

 

History

History
230 lines (145 loc) · 19.4 KB

anti-patterns.md

File metadata and controls

230 lines (145 loc) · 19.4 KB

ТДД Анти-шаблони

Повремено је потребно прегледати своје ТДД технике и подсетити се понашања које треба избегавати.

ТДД поступак је концептуално једноставан за праћење, али док га будете чинили ћете му изазовом ваше дизајнерске вештине. Немојте ово заменити јер је ТДД тежак, тежак је дизајн!

У овом поглављу је наведен низ ТДД-а и тестирање анти-образаца, као и начини њиховог отклањања.

Уопште не радим ТДД

Наравно, могуће је написати одличан софтвер без ТДД-а, али много проблема које сам видео са дизајном кода и квалитетом тестова било би врло тешко доћи да је коришћен дисциплинован приступ ТДД-у.

Једна од предности ТДД-а је у томе што вам даје формални поступак за разбијање проблема, разумевање онога што покушавате да постигнете (црвено), спровођење (зелено), а затим добро размислите како то исправити ( плава / рефактор).

Без тога је процес често ад-хоц и лабав, што може отежати инжењеринг него што би могао бити.

Неразумевање ограничења корака рефакторирања

Био сам на већем броју радионица, мобинга или упаривања где је неко положио пробу и налази се у фази рефакторирања. Након мало размишљања, мисле да би било добро апстраховати неки код у нову структуру; пупави педант виче:

То вам није дозвољено! Прво бисте требали написати тест за ово, ми радимо ТДД!

Чини се да је ово уобичајени неспоразум. Коду можете да радите шта год желите када су тестови зелени, једино што не смете је да додате или промените понашањ.

Поента ових тестова је да вам пруже слободу рефакторисања, пронађу праве апстракције и олакшају промену и разумевање кода.

Имати тестове који неће успети (или зимзелене тестове)

Запањујуће је колико често се ово појављује. Почињете да отклањате грешке или мењате неке тестове и схватате: не постоје сценарији у којима овај тест може да пропадне. Или бар неће успети на начин на који је тест предпостављен да би се заштитио.

Ово је неод немогућег са ТДД-ом ако следите први корак,

Напишите тест, пазите да не успе

То се готово увек ради када програмери напишу тестове након што је написан код и / или прогоне покривеност тестом, уместо да креирају користан пакет за тестирање.

Бескорисне тврдње

Да ли сте икад радили на систему и положили сте тест, видите ли ово?

false was not equal to true

Знам да лажно није једнако тачном. Ово није корисна порука; не говори ми шта сам сломио. Ово је симптом непоштовања ТДД процеса и не читања поруке о грешци грешке.

Враћајући се на таблу за цртање,

Напишите тест, уверите се да је пропао (и не стидите се поруке о грешци)

Тврдња о небитним детаљима

Пример за то је изношење тврдњи о сложеном објекту, када је у пракси све што вас занима у тесту вредност једног од поља.

// not this, now your test is tightly coupled to the whole object
if !cmp.Equal(complexObject, want) {
    t.Error("got %+v, want %+v", complexObject, want)
}

// be specific, and loosen the coupling
got := complexObject.fieldYouCareAboutForThisTest
if got != want{
    t.Error("got %q, want %q", got, want)
}

Додатне тврдње не само да отежавају читање теста стварањем шума у вашој документацији, већ и непотребно спајају тест са подацима до којих му није стало. То значи да ако случајно промените поља за свој објекат или начин њиховог понашања, можете добити неочекиване проблеме са компилацијом или неуспехе током тестова.

Ово је пример недовољног строгог праћења црвене фазе.

  • Допуштање постојећем дизајну да утиче на то како пишете тест, уместо да размишљате о жељеном понашању
  • Недовољно разматрање поруке о грешци неуспелог теста

Много тврдњи у оквиру једног сценарија за јединичне тестове

Многе тврдње могу отежати читање тестова и изазов за њихово отклањање када не успеју.

Често се увлаче постепено, посебно ако је подешавање теста компликовано јер не желите да поновите исту грозну поставку да бисте тврдили на нечем другом. Уместо овога, требало би да решите проблеме у свом дизајну који отежавају тврдње о новим стварима.

Корисно правило је тежити да се по једној тврдњи изнесе једна тврдња. У програму Го искористите субтестове да бисте јасно разграничили тврдње у приликама у којима то требате. Ово је такође згодна техника за раздвајање тврдњи о понашању и детаљима примене.

За остале тестове код којих време постављања или извршавања може представљати ограничење (нпр. Тест прихватања за управљање веб прегледачем), потребно је да измерите предности и недостатке мало незгоднијег како бисте тестове отклонили у односу на време извршавања теста.

Не слушам своје тестове

Даве Фарлеи у свом видеу „Кад ТДД пође по злу“ истиче,

ТДД вам пружа најбрже могуће повратне информације о вашем дизајну

Из мог сопственог искуства, многи програмери покушавају да вежбају ТДД, али често игноришу сигнале који им се враћају из ТДД процеса. Тако да су и даље заглављени са крхким, досадним системима, са лошим тест пакетом.

Једноставно речено, ако је тестирање вашег кода тешко, онда је и употреба вашег кода тешка. Третирајте своје тестове као првог корисника вашег кода и тада ћете видети да ли је са вашим кодом пријатно радити или не.

Ово сам много нагласио у књизи и поновићу слушајте своје тестове.

Прекомерно подешавање, превише пробних дублова итд.

Да ли сте икад погледали тест са 20, 50, 100, 200 редова поставног кода пре него што се деси било шта занимљиво у тесту? Да ли тада морате да промените код и поново посетите неред и желите ли да имате другачију каријеру?

Који су овде сигнали? Слушајте, компликовани тестови == компликовани код. Зашто је ваш код компликован? Да ли мора бити?

  • Када у тестовима имате пуно пробних двоструких резултата, то значи да код који тестирате има пуно зависности - што значи да ваш дизајн треба да ради.
  • Ако се ваш тест ослања на подешавање различитих интеракција са лажним порукама, то значи да ваш код остварује пуно интеракција са својим зависностима. Запитајте се да ли би ове интеракције могле бити једноставније.

Пропусни интерфејси

Ако сте прогласили interface који има много метода, то указује на пропусну апстракцију. Размислите о томе како бисте могли да дефинишете ту сарадњу са обједињенијим низом метода, идеално једним.

Размислите о врстама тестних дублова које користите

  • "Mocks" су понекад корисна, али су изузетно моћна и стога их је лако злоупотребити. Покушајте себи да дате ограничење употребе уметака.
  • Провера детаља примене код шпијуна је понекад корисна, али покушајте то да избегнете. Имајте на уму да детаљ ваше имплементације обично није важан и не желите да ваши тестови буду повезани са њима ако је могуће. Покушајте да тестове упарите са корисним понашањем, а не случајним детаљима.
  • Прочитајте моје постове о именовању тестних дублова ако је таксономија тестних дублова мало нејасна

Објединити зависности

Ево неколико кодова за http.HandlerFunc који обрађује регистрације нових корисника за веб локацију.

type User struct {
	// Some user fields
}

type UserStore interface {
	CheckEmailExists(email string) (bool, error)
	StoreUser(newUser User) error
}

type Emailer interface {
	SendEmail(to User, body string, subject string) error
}

func NewRegistrationHandler(userStore UserStore, emailer Emailer) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
        // издвајање корисника из тела захтева (грешка при руковању)
        // провера корисника да постоји (руковање дупликатима, грешке)
        // складиштење корисника (руковање грешкама)
        // састављање и слање е-поште са потврдом (грешка при обради)
        // ако смо стигли тако далеко, вратите 2кк одговор
	}
}

У први мах је разумно рећи да дизајн није тако лош. Има само 2 зависности!

Поново процените дизајн узимајући у обзир одговорности руковаоца:

  • Анализирајте тело захтева на User
  • Користите UserStore да бисте проверили да ли корисник постоји ❓
  • Користите UserStore за складиштење корисника ❓
  • Саставите е-поруку ❓
  • Користите Emailer за слање е-поште ❓
  • Вратите одговарајући хттп одговор, у зависности од успеха, грешака итд. ✅

Да бисте вежбали овај код, мораћете да напишете много тестова са различитим степенима двоструких поставки теста, шпијуна итд

  • Шта ако се захтеви прошире? Преводи за имејлове? Шаљете ли и СМС потврду? Да ли вам има смисла да морате да промените ХТТП руковач да бисте се прилагодили овој промени?
  • Да ли сматрате да је важно да правило „требали бисмо послати е-пошту“ налази се у ХТТП обрађивачу?
    • Зашто морате проћи церемонију креирања ХТТП захтева и читања одговора да бисте верификовали то правило?

Слушајте своје тестове. Писање тестова за овај код на ТДД начин брзо би требало да вам учини да се осећате нелагодно (или барем изнервирајте лењег програмера у себи). Ако се осећате болно, зауставите се и размислите.

Шта ако је уместо тога дизајн овако?

type UserService interface {
	Register(newUser User) error
}

func NewRegistrationHandler(userService UserService) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		// parse user
		// register user
		// check error, send response
	}
}
  • Једноставно тестирање руковаоца ✅
  • Промене правила око регистрације изоловане су од ХТТП-а, па их је и једноставније тестирати ✅

Кршење енкапсулације

Капсулација је веома важна. Постоји разлог због којег све у пакету не извозимо (или јавно). Желимо кохерентне АПИ-је са малом површином како бисмо избегли чврсто повезивање.

Људи ће понекад доћи у искушење да неку функцију или метод учине јавним како би нешто тестирали. На овај начин погоршавате дизајн и шаљете збуњујуће поруке одржаваоцима и корисницима вашег кода.

Резултат овога могу бити програмери који покушавају да отклоне грешке у тесту, а затим на крају схвате да се функција која се тестира позива само из тестова. Што је очигледно ужасан исход и губљење времена.

У Го, сматрајте своју подразумевану позицију за писање тестова из перспективе потрошача вашег пакета. Ово можете учинити ограничењем времена компајлирања тако што ћете своје тестове објавити у тест пакету, нпр. package gocoin_test. Ако то учините, имат ћете приступ само извезеним члановима пакета, тако да неће бити могуће повезати се са детаљима имплементације.

Компликовани тестови табеле

Табеларни тестови су одличан начин вежбања низа различитих сценарија када је подешавање теста исто, а ви само желите да мењате улазе.

Али они могу бити неуредни за читање и разумевање када покушате да обучете друге врсте тестова под називом један славни сто.

cases := []struct {
    X int
    Y int
    Z int
    err error
    IsFullMoon bool
    IsLeapYear bool
    AtWarWithEurasia bool
}

Не плашите се да избијете из своје табеле и напишете нове тестове, уместо да додајете нова поља и логичке вредности у struct табеле.

Треба имати на уму приликом писања софтвера,

Једноставно није лако

„Само“ додавање поља у табелу може бити лако, али ствари може учинити далеко од једноставних.

Резиме

Већина проблема са јединичним тестовима се обично може пратити до:

  • Програмери који не прате ТДД поступак
  • Лош дизајн

Дакле, научите о добром дизајну софтвера!

Добра вест је да вам ТДД може помоћи да побољшате своје дизајнерске вештине јер како је речено на почетку:

Главна сврха ТДД-а је да пружи повратне информације о вашем дизајну. Милионити пут слушајте своје тестове, они вам одражавају дизајн.

Будите искрени у погледу квалитета својих тестова слушајући повратне информације које вам дају и постаћете бољи програмер за то.