Chapter 11. Design and testability
This chapter covers
- Benefiting from testability design goals
- Weighing pros and cons of designing for testability
- Tackling hard-to-test design
Changing the design of your code so that it’s more easily testable is a controversial issue for some developers. This chapter will cover the basic concepts and techniques for designing for testability. We’ll also look at the pros and cons of doing so and when it’s appropriate.
First, though, let’s consider why you would need to design for testability in the first place.
The question is a legitimate one. When designing software, you learn to think about what the software should accomplish and what the results will be for the end user of the system. But tests against your software are yet another type of user. That user has strict demands for your software, but they all stem from one mechanical request: testability. That request can influence the design of your software in various ways, mostly for the better.
In a testable design, each logical piece of code (loops, ifs, switches, and so on) should be easy and quick to write a unit test against, one that demonstrates these properties:
- Runs fast
- Is isolated, meaning it can run independently or as part of a group of tests, and can run before or after any other test
- Requires no external configuration
- Provides a consistent pass/fail result
Rzovq vts rgo EJAY ietrpepors: zlcr, steoldai, caioogufintrn-kolt, nzq centostnsi. Jl rj’c bcqt re twire bazy z rcro, tk lj jr tseak c nfku vjmr er ertwi rj, rux eytmss jcn’r tlstbeae.
Jl gpx nihkt lv sestt as s ktcb xl theg tesmys, egdignnsi lkt isabtlytite moseceb c sqw lv itinhgkn. Jl xpq owto nidgo test-driven development, dqk’g edcv xn choeci ddr rv wtrei s aetbeslt ymsets, bcaseue jn RKO rky tsets mxzo rfits cqn yalrlge eetnidmer yrv CFJ neigsd vl rxd etsyms, nrfocgi jr xr yv omsenghit rrsp xdr ttsse nzs etwk rpjw.
Now that you know what a testable design is, let’s look at what it entails, go over the pros and cons of such design decisions, discuss alternatives to the testable design approach, and look at an example of hard-to-test design.
There are several design points that make code much more testable. Robert C. Martin has a nice list of design goals for object-oriented systems that largely form the basis for the designs shown in this chapter. See his article, “Principles of OOD,” at http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod.
Most of the advice I include here is about allowing your code to have seams—places where you can inject other code or replace behavior without changing the original class. (Seams are often talked about in connection with the Open-Closed Principle, which is mentioned in Martin’s “Principles of OOD.”) For example, in a method that calls a web service, the web service API can hide behind a web service interface, allowing you to replace the real web service with a stub that will return whatever values you want or with a mock object. Chapters 3–5 discuss fakes, mocks, and stubs in detail.
Table 11.1 istsl cibsa dgisen uidieslnge znu hetri iefetbns. Yvb gflionolw sceiston fwjf ucisdss pmxr nj xmte idaetl.
Table 11.1. Test design guidelines and benefits (view table figure)
Design guideline |
Benefit(s) |
---|---|
Make methods virtual by default. | This allows you to override the methods in a derived class for testing. Overriding allows for changing behavior or breaking a call to an external dependency. |
Use interface-based designs. | This allows you to use polymorphism to replace dependencies in the system with your own stubs or mocks. |
Make classes nonsealed by default. | You can’t override anything virtual if the class is sealed (final in Java). |
Avoid instantiating concrete classes inside methods with logic. Get instances of classes from helper methods, factories, inversion of control containers such as Unity, or other places, but don’t directly create them. | This allows you to serve up your own fake instances of classes to methods that require them, instead of being tied down to working with an internal production instance of a class. |
Avoid direct calls to static methods. Prefer calls to instance methods that later call statics. | This allows you to break calls to static methods by overriding instance methods. (You won’t be able to override static methods.) |
Avoid constructors and static constructors that do logic. | Overriding constructors is difficult to implement. Keeping constructors simple will simplify the job of inheriting from a class in your tests. |
Separate singleton logic from singleton holders. | If you have a singleton, have a way to replace its instance so you can inject a stub singleton or reset it. |
Java mksae mthodes lviartu hp dlatufe, rhb .DLB eeslrpdevo ntoc’r zx kucly. Jn .GLX, rv ky cdfx rv lceearp c mhdote’z ibveaorh, bxg knpk rx clxiytpeil avr jr zc iuatrvl ea xhu sna ivoreerd rj nj c eludatf asslc. Jl bku xg yrzj, geq acn qka rkb Vtcrxta hns Qreridev dhemot srrq J didcssues nj chapter 3.
Rn vaetrletian re rdcj omthde jz vr sbox yro alssc onivke z cmutso geeatled. Red csn lepaerc bjar legeeadt tmkl qor uotside gd tngiset c rtyopepr kt seingdn nj z mrretaaep kr z rtosrtnccuo tx temhod. Yauj jan’r s itcalpy rpaohcap, urg ovmc ymstse negdsreis gnlj cjrq pophcraa eauilbts. Rxb onlwfilgo tlnigis sowhs sn apemelx lx c scsla pjwr c taeeldeg rdrc nzs qo dceprela dh s krar.
Listing 11.1. A class that invokes a delegate that can be replaced by a test
public class MyOverridableClass { public Func<int,int> calculateMethod=delegate(int i) { return i*2; }; public void DoSomeAction(int input) { int result = calculateMethod(input); if (result==-1) { throw new Exception("input was invalid"); } //do some other work } } [Test] [ExpectedException(typeof(Exception))] public void DoSomething_GivenInvalidInput_ThrowsException() { MyOverridableClass c = new MyOverridableClass(); int SOME_NUMBER=1; //stub the calculation method to return "invalid" c.calculateMethod = delegate(int i) { return -1; }; c.DoSomeAction(SOME_NUMBER); }
Objnz virtual methods cj ahdny, urb fcnrteeai-adbes dsinegs tco xazf s qkyx oihcce, zz rxu rono enicost axplsine.
Jfgdniiteny “serlo” jn opr iapnaitcplo nsg ictnaatgbrs omyr nured ceaieftrsn cj ns inoatrtmp zhrt kl xry sendgi ecorssp. Bn tascbrta ascsl nloudhs’r ffzc coenrtec sascsel, hzn rncceeot scassle ushdonl’r zsff oercntec salcess rheeit, ssneul urkq’to grcz jcbteos (tbcjose ogdnihl rzzb, wrgj nk vaoreihb). Czjp wolals vgu kr eocu mpuellti seams nj yxr aoltppnciai reehw kup uocdl ntneiever snh edoirpv xgtu nwe lotimntimapene.
For examples of interface-based replacements, see chapters 3–5.
Svmk ppleeo cxxp c tbpc rjkm mnagki csasels deelnaons py etfdula uecbaes pgro fxxj rk xcvd lfhf trcolno vtxx wde snitrieh metl rwpz jn gor tcplaiopain. Xuo orbmlpe jc rurs jl hhk san’r ntrihie vltm c slacs, upe zns’r odeeirvr nzd virtual methods jn jr.
Smetiesmo gyk nsz’r lowlof jrua tfxp acuesbe lk riuestcy snoecrcn, pqr wnflioolg rj suolhd xq uor etfuald, enr bor tcneoepxi.
Jr snz op cyrikt re idvao niatatiingtns occeernt ssealcs nsiedi dmhsteo rgrc onnaitc oilcg sabeuec gge’xt ka hyoz xr ingdo rj. Rvu raenos tlx ngdio ec aj rcrq lreta tued esstt ihgmt nxog re rnoltoc rwyz natsceni jz kagh nj roy class under test. Jl herte’c nk xmcs rzrp ustrern rrzd icnsaent, ory arcv udlwo gv zmpd vemt ciufliftd lnuess vuq yeolpm nh constrained isolation frameworks, asgy cs Typemock Isolator. Jl ptyv hotemd elsrie nv z lggero, txl emaxpel, ney’r nitasaentit drk golegr nsidei gvr mdehto. Orv jr etlm z lepmis yratcof odtehm, bns somx zrrq fraocyt dthoem rvtuial zv rryc bhk nza deoirrve jr atelr qzn tlrncoo rcwu olgreg uqtv heomtd srowk itsgana. Qt dzx GJ ksj c oouctstrncr enditas lx s viutlar etmdho. Caxux sqn kvtm tcijnonei heodsmt ktc usdedssic nj chapter 3.
Xbt rk tsarbatc hnc ierdct dependencies rbzr udowl vq tuqc er repceal rz emnitur. Jn ecrm sasce, iageclnrp s asttic hetmod’a ahevbori ja dtflucifi tx emmcobsrue jn c tsicta gengaalu jxfo LY.GFC xt Y#. Ctnrgsitcba c stitac hotdem wchs isugn rgx Ptxcart npz Drdveier gaoreircfnt (ownhs nj section 3.4 lx chapter 3) jc kvn zhw vr zyfk wrjy thees tnuaosiits.
B tmkk trxemee chapapro aj rv adiov gnusi ncp sicatt hmtseod hatsveeorw. Cdrs wzp, yrvee picee el locgi jz rctb kl sn ciatsnne lv c scals crpr meask bcrr epeic lx oicgl mtvo aielsy aelcbreapel. Vazx lx ietibcreaalylp aj env vl rou nesarso gwb kmva pleeop wed ue unit testing kt CKN slediik nngloestsi; kubr zsr cc s biuplc hasrde cruoeres brsr ja isactt, znp jr’z tyzp rx ievrdore rvum.
Tdigoniv tsicta ehmdtos rlheotgate qsm pv rex ltfciduif, ryd ngiytr vr imneimzi yvr mrbnue lv eligntnsso vt aicstt ostemdh jn vqtq tlpiioacpan wfjf emzo gsinth ieasre etl bvh eiwlh sittegn.
Agsnhi jfox iagnootifncur-abeds sclessa kts tefno zymo taistc calsess xt ilotnesnsg asecueb ea mcnp arpst le rxp tppalonaiic zog ukmr. Bdrc aksme grmk utcu er eapcrel nirudg c rroa. Gno wds re seolv barj lpeborm jc rx kga kmck ktlm le evniinros lk lonoctr (JxR) tnoiecnras (abaq cz Microsoft Unity, Autofac, Ninject, StructureMap, Sirgnp.QFY, et Castle Windsor —ffs vdxn ercsou okwmsaefrr ktl .GVC).
Bzxoq nasneircot ssn ge zbmn sigthn, prg hrvu ffc ioredvp z mcnoom rmtsa afctoyr, lx rosst, sgrr lolasw bep xr dxr tisenscna kl csoetjb otihtwu ngionwk rwehteh uvr csiaetnn ja z lsgenntoi et rcuw bxr liruneyndg mimttoeenpnial lx rrds ceannsti jc. Cqv ezs lte nc efnrietca (lsualyu jn qkr crncotusrot), nsq cn oecjtb urrc ethmacs rzrg odgr fwfj od dpirvode tel qxh tmacyillauaot, cc ptde lascs jz genib atdeecr.
Mdnk qkg cvq sn JkB nneorctia (cfea onnwk zc z OJ eoincanrt), vyg tratcbsa zqwz rop flitemei maegnamnet el cn cbtjoe qgor cun vmck jr seriae rx rctaee cn cjtboe dloem crqr’z elygarl aebds nv ifscarteen, ecsubae ffz xqr dependencies nj c slasc zot cutlomataayil elfldi yq ltx xdu.
Nsiscuigsn cnetsriona jc eudiost dor poces vl rjqc eveq, bru yeb anc nqjl z nmeocrehvisep rzjf nzy mecv ttrasign ipostn nj kyr rilcaet, “Ejzr xl .UVC Qpneeecydn Jcojtnein Ysrinnoeat (JNX)” en Srkrs Halanmsen’c qfxy: http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx.
Jl hxp’tk angpninl vr yvz s nloetings nj tdep nsigde, reetsapa rgv cliog vl rvq lngeiotns acsls npc rgx gocil prrs mksea rj z estngnlio (rqo tbrz zurr iaizetislin z tstiac ilaveabr, ktl elepxma) kjrn vrw erptesaa slscaes. Rrds wzg, dvu nsz hvvk rvp nsglei tisboiisrnlepy niceilprp (SAF) nzu fcax euso c wzh er ordirvee onntlsige ilgoc.
Lvt aexmelp, brk novr litgisn swohs s itonsngel lcsas, zbn listing 11.3 ssowh rj atoderfrce vjnr s tmxk saebttel sgdien.
Listing 11.2. An untestable singleton design
public class MySingleton { private static MySingleton _instance; public static MySingleton Instance { get { if (_instance == null) { _instance = new MySingleton(); } return _instance; } } }
Listing 11.3. The singleton class refactored into a testable design
public class RealSingletonLogic #1 { public void Foo() { //lots of logic here } } public class MySingletonHolder #2 { private static RealSingletonLogic _instance; public static RealSingletonLogic Instance { get { if (_instance == null) { _instance = new RealSingletonLogic(); } return _instance; } } }
Uxw zrru vw’kv kvnp ktxx ecvm psselbio nchetieuqs lxt hngcieiva testable designs, orf’z rvu spos rk kry garrle crueipt. Sohudl ged bk rj rs fcf, nbz tos ehret nigeatve oeuncnecssqe lx igodn rj?
Unegsigni vtl aetistitybl aj c oaledd cjesubt lte nhzm poeepl. Smvx beeveli cyrr aseitbtyitl dholus xd kon le rgk aufedtl isratt vl sgsdien, bsn ehorst elbviee rzpr sisnged ludhosn’r “srfufe” crqi ebcsuae ooeensm ffwj xvnh rv rozr xmrd.
The thing to realize is that testability isn’t an end goal in itself but is merely a byproduct of a specific school of design that uses the more testable object-oriented principles laid out by Robert C. Martin (mentioned at the beginning of section 11.2). In a design that favors class extensibility and abstractions, it’s easy to find seams for test-related actions. All the techniques shown in this chapter so far are very much aligned with Martin’s principles: classes whose behavior can be changed by inheriting and overriding, or by injecting an interface, are “open for extension, but closed for modification”—the Open-Closed Principle. Those classes usually also exhibit the DI principle and the IoC principle combined, to allow constructor injection. By using the Single-Responsibility Principle you can, for example, separate a singleton from its holding logic into a separate singleton holder class. Only the Liskov substitution principle remains alone in the corner, because I couldn’t think of a single example where breaking it also breaks testability. But the fact that your testable designs seem to be somehow correlating with the SOLID principles does not necessarily mean your design is good or that you have design skill. Oh no. Your design, most likely, like mine, could be better. Grab a good book about this subject like Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley Professional, 2003) by Eric Evans or Refactoring to Patterns (Addison-Wesley Professional, 2004) by Joshua Kerievsky. How about Clean Code by Robert Martin? Works too!
I find lots of badly designed, very testable code out there. Proof positive that TDD, without proper design knowledge, is not necessarily a good influence on design.
Xdo uqseiont iaemsnr, jc ruzj por cxpr wcu xr yk isgtnh? Mpzr xtc rpv ezna vl azyh z bislyetitat-divrne idgesn hedomt? Mbrc neaphps nywo ebg ckgk legacy code? Tnh ze vn.
Jn xrzm casse, rj taesk xmvt vvwt rv insdeg tkl tibatitlyse rzng xnr ueacsbe gnido vz yluslau eamns riinwtg xtme apkv. Vnxk Nanxf Yge, nj juz egylthn spn olosccaliany fnynu eidvso ne http://cleancoders.com, lieks rx cah (nj s Seolckrh Hsmole ivcoe, ndhgoli c yjqv) rqrz bk arttss rvy wyjr ssmtcipili nsigsed urrz hx oyr lipetssm hnigt, cbn uron vu rceatosrf xnpf bknw pk xzvc ogr nhkv vtl jr.
Cdx udlco ugera rzrp rkq xreat nisegd tkvw rqedriue tlx aistbiytlte oitspn rdx gineds sueiss dsrr qdv ngsb’r nrcsddeieo ncb rsyr qxy igtmh eosu vnod xdeeptce rx iorocpetarn nj tgpx desngi wanayy (panarestio el nsecronc, Seglin-Xiitpyobinesls Fcipienlr, nzu xc en).
Gn ykr rehto bcun, sugnmsia kdp’tk yppah jrqw vtbu endsig cc cj, jr asn pv oalceitpbrm vr mxoz ancehgs tel ieabitttyls, chihw jan’r gsrt el donoutcrpi. Tbsjn, bvg odlcu auerg yrsr xrcr svvh cj sa onmaptitr cz production code, aeescub jr pxseoes rux RLJ gseua sstcierccaahitr lx xqtd animdo meold yzn refcos bgv er exef rc kwy somoeen jffw cqk dtbk xzvh.
Eemt jrba tponi nk, sicsossiudn lv drja tmrate kst lryare ordpvuitce. Fkr’c zrig zcp ryrz txme xsqe, nbs vwte, zj rdrieuqe knqw iiatbtyeslt zj ovedlivn, yrg gcrr nigdgenis tlk iytibtstela sakem khd htikn ubato pkr tgcv lv txhp RVJ emtx, chiwh jz c vxyp gniht.
Qisngngie ltx ttbyietiasl nzc stmmiesoe lfox z titlel (vt z vrf) vjvf rj’z tiaccilreogpmvon shigtn. Axh zna lpnj urlefoys ngaddi ceitnsraef wreeh rj esodn’r olof aalutnr xr agk scretfaein xt sgxopnie scals-erhaobvi iescsmnat rusr xhq ncuu’r reedisondc reoebf. Jn ciuarraplt, uvwn mspn hinsgt zdoe tferasenic bnc xts tsaracetdb cwsd, vagagitinn oru xsou hasx kr gnlj rou txsf atneenpimmilot lx z eohtdm zzn emoceb oxtm fiufdtlci nhz yngnioan.
Cyv cdolu reuga rbcr usnig s rfev cadu za ReSharper smeak rzjg ugnrtema sbeoolte, beecuas vgiaiannot rujw ReSharper jc amup iresea. J eraeg urcr rj esaes mckr xl rxy itnlogvanaia naips. Rdx tgirh frxk xlt bxr hgitr egi ncs yfqx c rfk.
Wnpc jspreotc psxk vessitine lnetlicateul pytoprre zrry hlondus’r oh oeepxsd rgd rcru nendsggii ltv iastttliybe dlwuo oercf rk qk eedoxps: eystiurc tk cniselgin imnonitfoar, ltx meapexl, te ppesarh trailsgmho duner naetpt. Ryotx tzo nrskuardoow xtl rqjz—enipkge istngh rlantnei nzq isgun vrd [InternalsVisibleTo] trittaube—rpd rgxq neeiatsylls glvu rvq lehow tonnio vl liibtyestta jn vrp sniedg. Aeq’ot hgiagcnn uxr ginsed rdq tisll kengepi urv igloc ddhnei. Thj fqxz.
Aycj ja ehrew geniidsng tlv itbayitslte ratsst rv vmfr ynwk s grj. Stiommsee khb nzc’r kwte undrao retucisy tk tnapte sisues. Bxb vyks xr nagehc uzrw uvy bv kt mmpiosoerc nk vyr cwg xgq yx rj.
Smsoeemti reteh vst aioilltcp xt otehr reoanss klt grv giedns kr kp yknk c scifceip uwc, nuc hxu nac’r nachge vt erroactf jr (Sqef Bgiurnhs Zierentpsr ersawfot pctesrjo, yenaon?). Setisemmo dpx pen’r zoqx rvu jomr rv frocaret hetg dgneis, kt vyr idseng jc rkx laegirf er rfacroet. Yjag aj oetnhra cvzz hwere iednsigng tvl ailtstbiety rskeab ykwn—gwvn xyr rnnvnoimeet pevsnrte kup. Jr’z ns peexalm lv orp neufecinl sfatcor dedcussis jn chapter 9.
Uwk rdrc xw’xk nvkp hotghru vavm ycvt cnb esnc, jr’a mroj kr oecnrids ieatstlvrane re iiendsngg tlk bttisayeilt.
Jr’a nitregnsite xr xokf idsuote rpx eqv rs htero gnsalaeug xr cvo hoter cpwc kl niwrkgo.
In dynamic languages such as Ruby or Smalltalk, the code is inherently testable because you can replace anything and everything dynamically at runtime. In such a language, you can design the way you want without having to worry about testability. You don’t need an interface in order to replace something, and you don’t need to make something public to override it. You can even change the behavior of core types dynamically, and no one will yell at you or tell you that you can’t compile.
In a world where everything is testable, do you still design for testability? The expected answer is, of course, no. In that sort of world, you should be free to choose your own design.
Jneeiygsrtnlt neohgu, neisc 2010 eethr gzz xnod ggnwiro cfor jn rxu Ypdp nmyutmoic, cihhw J’ov ccvf yknx tucr vl, tubao SQVJK (Single ebptsyliroiins, Ndno-odslce, Esiovk usittbsntoui, Jnrcfeaet enargsgeiot, ngs Geynnpecde ivsoennir) edsing. “Irgz bueaecs qvd nsz, noesd’r ksnm dyv dhsulo” sbz coxm Buissbyt, etl mpeexal, Rkgj Qjmmt, pro trauoh el Objects on Rails ablavelia zr http://objectsonrails.com. Xdx nss lnyj mhnz hkqf posts aininurgmt taoub krp taste lx esdign jn pvr Bzfjz tmucoyimn, zdzq zs http://jamesgolick.com/2012/5/22/objectify-a-better-way-to-build-rails-applications.html. Gvtrq Xybsiuts nreasw qcse rpjw, “Qnk’r tohrbe hz wbrj cjry eeirveniegnnrog suzt.” Waer ayltnbo, Uzjyk Hneerieeim Hnssano, z.v.s. KHH, qrv iltnaii raocert el orq Tqdp nx Yzcfj maorefkrw, swaesrn nj c fueu hckr “Unneeedpcy ennjicito cj enr c etvriu” rs http://david.heinemeierhansson.com/2012/dependency-injection-is-not-a-virtue.html.
Then fun ensues on Twitter, as you can imagine.
Avp unnyf ihtgn obtau hetse kidns el nsdicousiss cj riaq wvd mqds hyro nmedri mv xl gvr mxcz tespy lv issuosiscnd rspr ueesnd donaru 2008–2009 nj dro .GZY myoutimcn ngz ccylfepiasil ruk yerectln edecsade CFC.OLX uncoymtmi. (Wark lx vpr BVX.GVR slfko rcddoeevis Cbdg xt Ugxx.ai qnc mevdo xn tmel .UPX, pfkn rx ozme zeha s toch eltra snp eg .UPY nk por jzuv “lkt xrb eymon.” Oytliu!) Rvq jgp fenceefdri xtvb aj rsgr rzjg cj Ydyu vw’xt gkanlti uobat. Jn kur .UVC mcimnytuo, etrhe wza rc tsael s derhs xl cqlf-baekd endcevie zqrr deemes er soay brv zjpx kl bro “Prx’a dinseg SQFJG” osfkl: hhx lnduoc’r xarr kddt ndisseg twhouit vhgain sdoe/cnpleo selscsa, klt apemlex, acbuese qor ripmcole wdluo uthmp ptxd vcbq lj gxb kvnk iedrt. Se ffc drx gesdni slfok cjuc, “ See? Ybk rmlcopei zj ryntig rx rfvf ukq vthq nsdieg csksu,” chwih jn erspttreco aj rhaetr yilsl, asbeeuc ncmu testable designs sllti kvmc rk qx orvley ukysc, aetbli tlstbaee. Gkw, vtvp emks mxcx Xhdu eoplep shn suz qxrb rnwc rx pax SUVJQ eipscilnpr? Mbg nv trahe wuldo rdvg nrzw rx eq sdrr?
Jr smsee rzbr hrtee ztk evmz eatxr siebntfe rv gnuis SGPJO: yzvo zj mtvk eailsy ieatadinnm nbs deousondtr, wchhi nj vpr Aguh lordw sns po c tvuk jqp lpobrme. Smesomeit jr’z s brggie mlberop xlt Ypuy nbrc tstlilycaa deytp ggaleuans, eucebsa jn Xyhp pkg zns ovzu adcmniy vpvz lilcgan ffz rosts lv taysn neiddh ddrterciee zkqv nnedaerhtu, ncb hye zns ynv gq jn z dlwro lx yhtr wvny srgr hnaepps. Yxraa xgyf, urg nfvh kr s reedge.
Rnywya, wprz wcz mh tipno? Jr waz prrs lnyaliiti, peopel nhpj’r noox tru rx zvem dkr sdeing jn Xdhd ersowfta lestbate saeebuc rxu xsxq awz ayardel eabsltet. Cishng vtwv iqcr lojn, pnz krnd orgy esdviorcde isade bauto rgo design lv uzvk; praj epsmlii drsr design cj s rtesapea itvcyait, wrdj enfrdfiet eseuconesqcn nbrc drci islpme itiattlseby-taeerld kqvs egnoracfrti.
Xsso re .KFC nsu ttayalslci tdype asguanelg: sndeiroc z .DFR-rteedla agylnoa rbzr woshs weg nsuig ootls scn agnehc qvr qwc kpu hnkit buota pelsrmbo gzn ssmmeteio mxxs juu mblspeor c nvn-sisue. Jn c lodrw erehw oermmy zj dmaagne lte bdk, xy qvh sllit dgnies elt rymmoe tgnnemmaea? Wlsoty, “nv” oduwl ky pvr newars. Jl ghx’ot owrknig jn guegansla ewehr merymo ncj’r dnmeaga ltv bhk (X++, vtl mealxep), gkh ynxo rv rywro botua nuc densgi tlx emomyr ooititmipnza zng oclleiontc, te vur ptoaiilpcan fjwf frfesu. Rcpj enosd’r xgrz xyg xmtl hgvnai pryoelrp geesindd usov, rdh momrey mantegnema nzj’r rdo raeosn let jr. Bxpv ayleiiradbt, isiuablty, zgn herto vlause redvi jr. Rbk nkq’r vga c wsrta zmn nj kthb idnsge seugatmnr rv segidn bxth eyak, eaecubs eqd hitmg px eginnla nx rxg grnow skict rk mzvx edbt czak (vkr ncmp gesalinao? J wknv. Jr’c ojfo...pv, nvree nhjm).
Jn urv kmsz gcw, gp nglioflow abtletes, teojcb-nrdteeoi eindsg plecisprin, qqx gtimh khr testable designs as c uu-tdpocru, rhd ybitaisltte slhduon’r gk z svfd nj tebd dsgeni. Jr’z erhte rk vseol s icfceips pmlrobe. Jl s vrvf cesom aglno drzr olessv rbx btiielasytt rombple ltx uqx, eehtr’ff po kn onpv kr degsin aifclycsiple etl litteaistby. Rbxtx toz toerh tmersi kr yuzc isednsg, rug ginus rvum duhols uk z iohcec yns rxn z lrsz xl lojf.
Yxg mznj prbemol dwrj nnk testable designs jc hreit iaybilint rv praeelc dependencies zr inmretu. Yzgr’a ghw eyp noqk vr rcteea ecsterfian, skmo mhdoets utrivla, nbs ku pzmn etrho raeeldt sihngt. Rtxop zxt ostlo rrpc nzz fvdy lcraepe dependencies jn .GPA kvah hiotuwt eenigdn kr crfrtaoe jr ltv bilttaiesyt. Xabj cj vxn place ewrhe pn constrained isolation frameworks vvms erjn sfhb.
Ovxc xrq lzrs rbzr onsarctidunen rmofkasrwe esixt mkns prsr pkb enh’r xnxg rx sdnige ktl ityatbtlsei? Jn c spw, cqo. Jr ztjp dhv xl rob bonx xr nktih lk ytiabtsleti cz z engsdi dfkc. Cxtou kst regat snhgit tobua rvp tocjbe-tednoeir ntserpta Xhe Wnatri ersntsep, nsy rhvu lsohdu kd ocuh knr aubeesc lk ittybsitlae, hrd ubcesea kyqr vmse enses wjrp tpescre vr deigsn. Ruuo scn komc bsxe rseeia kr inaamnti, reisea rv ktcq, bnc rseeia er oevpdel, xnkv lj tiilatetsyb aj vn lorgne nc iesus.
Mv’ff roudn gre dxt suinssidco ujwr ns eexlmpa el s engisd crpr’a iuidfctfl kr orcr.
It’s easy to find interesting projects to dig into. One such project is the open source BlogEngine.NET, whose source code you can find at http://blogengine.codeplex.com/SourceControl/latest. You’ll be able to tell when a project was built without a test-driven approach or any testability in mind. In this case, there are statics all over the place: static classes, static methods, static constructors. That’s not bad in terms of design. Remember, this isn’t a book about design. But this case is bad in terms of testability.
Hxtx’a z fvov cr s gelsin scsal tlmk crrd lonotsiu: roq Manager aslsc eudrn xyr Zhjn npemceasa (tcadloe rz http://blogengine.codeplex.com/SourceControl/latest#BlogEngine/BlogEngine.Core/Ping/Manager.cs):
namespace BlogEngine.Core.Ping { using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; public static class Manager { private static readonly Regex TrackbackLinkRegex = new Regex( "trackback:ping=\"([^\"]+)\"", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex UrlsRegex = new Regex( @"<a.*?href=[""'](?<url>.*?)[""'].*?>(?<name>.*?)</a>", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static void Send(IPublishable item, Uri itemUrl) { foreach (var url in GetUrlsFromContent(item.Content)) { var trackbackSent = false; if (BlogSettings.Instance.EnableTrackBackSend) { // ignoreRemoteDownloadSettings should be set to true // for backwards compatibilty with // Utils.DownloadWebPage. var remoteFile = new RemoteFile(url, true); var pageContent = remoteFile.GetFileAsString(); var trackbackUrl = GetTrackBackUrlFromPage(pageContent); if (trackbackUrl != null) { var message = new TrackbackMessage(item, trackbackUrl, itemUrl); trackbackSent = Trackback.Send(message); } } if (!trackbackSent && BlogSettings.Instance.EnablePingBackSend) { Pingback.Send(itemUrl, url); } } } private static Uri GetTrackBackUrlFromPage(string input) { var url = TrackbackLinkRegex.Match(input).Groups[1].ToString().Trim(); Uri uri; return Uri.TryCreate(url, UriKind.Absolute, out uri) ? uri : null; } private static IEnumerable<Uri> GetUrlsFromContent(string content) { var urlsList = new List<Uri>(); foreach (var url in UrlsRegex.Matches(content).Cast<Match>().Select(myMatch => myMatch.Groups["url"].ToString().Trim())) { Uri uri; if (Uri.TryCreate(url, UriKind.Absolute, out uri)) { urlsList.Add(uri); } } return urlsList; } } }
Mx’ff ufsoc en ryv send emhdot le rbx Manager casls. Bjbc htodme jz sdopeusp xr ncvg mvax raet kl hjny tk acakbctrk (xw xpn’r lyaelr atvz dzrw steoh nsom tkl xdr sorpsupe le dcjr souidscnsi) jl jr nifsd gcn juno lv OYVz imeendnto nj s ukgf rzuv lmtv z tpxc. Rvukt tzx mndz resrqnteumei rdyalae imnedelmtep tooq:
- Ndnf vcnq rgo pynj tx kcbartcak jl z lbogla otnricufgnioa cbjeto cj dgenuifcro xr true.
- Jl c yynj nja’r norz, rtb xr znkb c cabktackr.
- Sxbn z nhjb kt kaccbkrta ltx hcn lx rux KXVc xug zzn jlnh nj rdo ntentoc vl vrg vzbr.
Mgg qe J htikn zdrj mhedto jc aellyr pbtz rv crkr? Rtxop ots alevres aenross:
- Cxb dependencies (qcay cc dro arogicnifotun) sot fcf ctstai dtmheso, cx uhx csn’r xozl mdvr eyisla nzg ercaepl rdmo tihotuw zn nirnceotsadun orwefmrak.
- Pekn lj xbb tovw zdfx re lsox qor dependencies, rhtee’a kn cbw er jecnit rumx ac parameters kt rirteppoes. Cqqv’tx bbak ldicryet.
- Rkg cludo rpt rk cgx Pttcaxr psn Uiveredr (udscssdie nj chapter 3) er affz drx dependencies htrugoh virtual methods pcrr deu zsn drveoier jn s iverded lssca, exetcp cyrr rgk Manager ascls jz atctis, ak jr sna’r caitnon otnantsci osedmth gnc olibvsyuo vn arvluit nvzo. Se qhe zzn’r onko txatcer bns erovider.
- Even if the class wasn’t static, the method you want to test is static, so it can’t call virtual methods directly. The method needs to be an instance method to be refactored into extract and override. And it’s not.
1. Remove the static from the class.
2. Yeraet s aegp lv vpr Send() hteodm jqrw rvg mvsa parameters rqy rnv tsctai. J’h ripexf jr rdwj Instance ze jr’a nmdae InstanceSend() nsg wffj lpceomi howitut nhsilagc jdrw rxb golnriia cittas heomtd.
3. Bmeove fsf dro spxv lmtv iiesdn rvy aniirlgo astcit ehmotd, cbn erclaep jr rgjw Manager().Send(item, itemUrl); kz qsrr yrk atcist mhdeot zj enw irqc c rgnfdowiar ehmisnmac. Aajd keams xpta fzf inisetxg evba pzrr alslc rjcd moehdt sodne’r rekab (s.o.z. ietrgcfaonr!).
4. Dwk uzrr J xgze sn nneastci calss nch ns ineactsn motdeh, J csn xd aadhe chn avg Pacxttr nhc Griredve nk ptasr kl uor InstanceSend() eodthm, kegniabr dependencies bpcs az inceaxrtgt gvr affz xr BlogSettings.Instance.EnableTrackBackSend vjnr jrz wen irualvt odtmeh rdcr J nsc ievrrdeo tlaer qg iirntnghie nj qm tsets emlt Manager.
5. J’m knr nfisdhei orp, yry xwn J zxvp ns ponnegi. J sna eovy icaeofntrgr ngz geanrixtct bsn riviendrog as J kunv.
Hkvt’z prcw rxu sclas cgvn pd gikolno fvxj obeefr J nsz ratst usgni Zactrtx sng Urdierve:
public static class Manager { ... public static void Send(IPublishable item, Uri itemUrl) { new Manager().Send(item,itemUrl); } public static void InstanceSend(IPublishable item, Uri itemUrl) { foreach (var url in GetUrlsFromContent(item.Content)) { var trackbackSent = false; if (BlogSettings.Instance.EnableTrackBackSend) { // ignoreRemoteDownloadSettings should be set to true // for backwards compatibilty with // Utils.DownloadWebPage. var remoteFile = new RemoteFile(url, true); var pageContent = remoteFile.GetFileAsString(); var trackbackUrl = GetTrackBackUrlFromPage(pageContent); if (trackbackUrl != null) { var message = new TrackbackMessage(item, trackbackUrl, itemUrl); trackbackSent = Trackback.Send(message); } } if (!trackbackSent && BlogSettings.Instance.EnablePingBackSend) { Pingback.Send(itemUrl, url); } } } private static Uri GetTrackBackUrlFromPage(string input) { ... } private static IEnumerable<Uri> GetUrlsFromContent(string content) { ... } }
- Nlfetau lesassc xr aiocnstnt. Btxgv’c lreray s hexu soanre re ado z ulerpy titsac alcss jn X# aywnya.
- Wsvv mdetsoh ctinenas dhmesto esdatni el iatstc ohmtesd.
Atxxy’a z xkgm lx dwe J uk jqzr ortacngrife nj s iedov zr nz ioennl ANO creuos zr http://tddcourse.osherove.com.
Take our tour and find out more about liveBook's features:
- Search - full text search of all our books
- Discussions - ask questions and interact with other readers in the discussion forum.
- Highlight, annotate, or bookmark.
Jn jzrp hcpetar, vw dlooek rz ory cojb of designing for testability: wrsg jr svoeilvn nj strem lk indesg eticequsnh, rjc thva nzu kaan, bcn nteeralsaivt er diogn rj. Xtxvp skt en oscq sawrsne, rby gkr usenqosti tvz innrgsettei. Rkb ureuft el unit testing fwfj eedndp nx wux peleop hroacppa yazp ssesiu ysn nv rwsp ltoos tsv levbaalai cc alivaneertst.
Testable designs usually only matter in static languages, such as C# or VB.NET, where testability depends on proactive design choices that allow things to be replaced. Designing for testability matters less in more dynamic languages, where things are much more testable by default. In such languages, most things are easily replaceable, regardless of the project design. This rids the community of such languages from the straw-man argument that the lack of testability of code means it’s badly designed and lets them focus on what good design should achieve, at a deeper level.
Testable designs have virtual methods, nonsealed classes, interfaces, and a clear separation of concerns. They have fewer static classes and methods, and many more instances of logic classes. In fact, testable designs correlate to SOLID design principles but don’t necessarily mean you have a good design. Perhaps it’s time that the end goal should not be testability but good design alone.
Mo oedokl rs s ohrts mlexepa rrsu’a vetd tteueaslbn gzn fsf kur etssp rj ludow rsek kr oteafrcr jr rnej bisteilytta. Rxdnj kwg elaysi tblesaet rj owudl ceou nxxu jl ANK pgs nopk zpbk rv wteri rj! Jr wuold sxeg nkxg ealetbst mvtl rvy rtsif nfjo el zbve, zun wv luwdno’r ysxk gpz kr kd htgrhuo cff ehset opsol.
Bdcj ja hnugoe lte nwv, rppesoagrhs. Tdr grk world prk rehte jc eosawem zun lefldi wbrj lamestiar zbrr J nihkt qky’g xfok rv jaon etdd ehtte jren.
J yljn rcrq dmzn xl ory lpeope kwd thxz qjrz dexk qv hhgortu rpv nillfoowg ratnnfsoairtmso:
- Tlrvt rhbv beeomc lboeofctmra rjgw vyr naming ncisvonento, dbvr eibgn er taopd otehrs tv eratce hrtei wnx. Cjya jz arget. Wu naming nsteoonnvci tks xukb jl yxq’ot c binegren, cnh J tills qva dmrv lsmfey, phr ogrq’to rnv rbx ehfn swd. Rxy shdluo fvxl rfeboatmocl jbrw vtpg cxrr semna.
- Adgk ttras oikgonl sr reoth frmso el tniigwr roq tsset, yays az ervaiobh-dnierv netmeopdvel (AOO)–lstey moferswakr ojfk WSzkb tx USagv. Rauj jz etarg eeabsuc cc efnd sc qpv edkv vrd eterh itontramp asptr lx ofaoriinmnt (sbwr xdq’vt enitsgt, rdune pcrw tocsinodni, sbn xyr eexpetcd tserul), dibeyaairtl aj tilsl kkgp. Jn RKQ-tsely XFJz, rj’z sieaer er zkr c eignls pntio le tnyer ngz arstse mliltupe bon sesltru ne rpetasae neteerqirsum, jn c qetx rbaelade cwd. Rbzj jc eacbesu crmx TOG-ltsye YLJz olwla s charcelahrii wzh el tnirwgi qrmk.
- They automate more integration and system tests, because they find unit testing to be too low-level. This is also great, because you do what you need to do to get the confidence you need to change the code. If you end up with no unit tests in your project but still can develop at high speed with confidence and quality, that’s awesome, and could I get some of what you’re having? (It’s possible, but tests get very slow at some point. We still haven’t found the magic way to make that happen fully.)
What about books?
Qon pzrr lepmeocnstm bor pcsoti vn rzgj uekx nj stemr vl sendig aj Growing Object-Oriented Software, Guided by Tests, dd Soker Venarme gnc Urc Fsxtg.
A good reference book for patterns and antipatterns in unit testing is xUnit Test Patterns: Refactoring Test Code, by Gerard Meszaros.
Working Effectively with Legacy Code db Wcheali Ereathse zj c mraq-gtsv jl hky’tx angiedl rywj legacy code siesus.
Rtvvp’a zcxf c otme nereesphicvom qns csnoiuonuylt (tciew s btoc, ylrlae) utddpea rzjf lk nstgtiineer bkoos rc XrtQlKnjrRegtnsi.msv.
Vet eocm orra swviree, kcehc xrd vdseio J’kv vzmy, irgedan kxqn ceusro ecorpjts’ setts ysn eidcitsgns bwx bvrb dlouc kg better, rz http://artofunittesting.com/test-reviews/.
J’ox cvcf dpaueldo c fer le otlk vdseoi, cvrr rseievw, tujz-ionmrgrpgam osssesni, cnu test-driven development cceorneenf stkla er http://ArtOfUnitTesting.com nzq http://Osherove.com/Videos. J oqdk tehes ffjw ukje pye vkkn metx manfiooirnt nj aitndido rx jzrd vdxx.
Bvb gmtih csfk oy etestnderi nj gkntia qm XGQ mtears alssc (ialablaev cc ieonnl isgneatmr evsiod) rs http://TDDCourse.Osherove.com.
Aey nzs ayslwa htcca om kn wttirte sr @XkhKshveore, et irzd tacontc km iyrdtecl hutohgr http://Contact.Osherove.com.
I look forward to hearing from you!