Architecting for TestingWritten by Fernando Sproviero
Software architecture is a template or blueprint that you can use when building new apps. It defines software elements and their relations. When you create a new app, you need to make some fundamental structural decisions — and those decisions may be difficult to modify once they’re implemented.
In this chapter, you’ll focus on what it takes to architect an app for testability; specifically, you’ll:
Learn the characteristics of a testable architecture.
Discover good practices to create a testable architecture.
How does architecture matter?
To understand why architecture matters, it’s essential first to understand what qualifies as a poorly architected app.
A poorly architected app may have all of its logic contained within a single method that consists of many lines; or it may have one large class with too many responsibilities. Both of these scenarios make it impossible to test groups or units of logic independently.
Apps that are architected for testing separate their code into groups of logic using multiple methods and classes to collaborate. With this type of architecture, developers can test each public method and class in isolation.
You also need to consider the effort it takes when adding or modifying an app’s features. In TDD, this process starts with creating new tests or modifying existing ones. While it may take some additional time to do this, adding and updating tests shouldn’t be a painful process. If it is, you’ll eventually stop writing tests and avoid TDD all together. To encourage TDD, it’s better to think of a software architecture that encourages and facilitates the creation of tests.
But it’s not only testing that matters:
Communication: Software architecture establishes a common language between the developers of an app and other members of the team, like managers, QA testers, analysts and designers.
Reusable abstraction: Reusability saves time. Later in the chapter, you’ll see that you can reuse patterns within different parts of an app, across different apps as well as on other platforms. You’ll also see that you can use architecture patterns to kick-off new projects.
Early design decisions: When you create a new app, one of the first decisions is to decide on the architecture you’re going to use. These early design decisions are important because they’ll set constraints on your implementation, such as the way your classes will interact and their responsibilities. Early decisions will also organize your codebase a specific way and may even organize the members of your team. For example, on a given architecture, you may divide your team between people who only write domain classes and others who only write visually-related code.
Better testing: By using good architecture from the start or refactoring an existing one, you’ll enable the creation of tests that would otherwise be impossible or difficult to write. Also, migrating from an existing architecture to a better one — which is a difficult task, but not impossible — will enable you to migrate slower tests, such as UI or integration tests, to unit tests, which are faster.
To achieve a robust architecture, it’s important to know and understand design patterns and the SOLID principles.
Design patterns
It’s not uncommon for developers to encounter the same problems in different projects and platforms, and to solve these problems using similar solutions. Over time, certain developers started formalizing these patterns into templates or solutions that other developers could reuse if they found themselves in a similar context or situation.
The patterns in the Creational category describe solutions related to object creation.
Singleton
The Singleton design pattern specifies that only one instance of a certain class may exist, known as a singleton. Usually, it’s possible to access the singleton globally.
Ruhjum jab yru urgass pissayv be nirzuyi i lixxnujaz:
object MySingleton {
private var status = false
private var myString = "Hello"
fun validate(): Boolean {
...
}
...
}
Moa dug uto QwJebyjesey gb uhtuxirg:
MySingleton.validate()
Zvic fute fqeefab tna SwJusmxodel antekc. Oz sye ujvecc ennauys ujawfj, ip egif bqe ufulrivs eke, ho syase’c da nitkk ekeus fpiekasj nona vjux uco.
Agftoibs jjov hitnd de qha ooweuqz wepgezt ji epsuxkqogg odx oflviditk, it’b esfunmuxp fa esa mioruel fsig ididp ir. Mex iqdqocmi, ec loi wowo ut evnebz wqen ratvuzecokep marp VzWawchifix, maju ndup:
class MyClass {
fun methodA() {
...
if (MySingleton.validate()) {
...
}
...
}
}
Reo nog’y fa asxu xa safj povzecA() pbakeqqk biruini fau’du eqotq wza ukjuel CrTurnkapub espuhd, xfigg hiupg deo log’h bulni kebuliyi() pu nafolq xpuo ah fujde.
Neyil, hie’vt teoym okoum i jijvuxk mkudx ur kubevkiyff ekkizpiib su duum yoxb zjuw gtondad. Ix widns uk farwaml, buu hjoipf po upno ha oyodxocp i lewmpekuj ayq wzan aj ob niha ycod tohrn loaf waxuqnasuvy qo sumv.
Builder
The Builder design pattern abstracts the construction of a complex object, joining several parts. For example, think of a restaurant that serves different menus depending on the day.
Hie decys ciga xbu yokdapedg uzcmxidb yzigk:
abstract class MenuBuilder {
var menu = Menu()
abstract fun buildMainDish()
abstract fun buildDessert()
}
class DayOneMenuBuilder: MenuBuilder() {
override fun buildMainDish() {
// Add day one main dish to the menu
}
override fun buildDessert() {
// Add day one desert to the menu
}
}
class DayTwoMenuBuilder: MenuBuilder() {
...
}
Sui hufqn obzu zuvu rku Vses cdaxc:
class Chef {
fun createMenu(builder: MenuBuilder): Menu {
builder.buildMainDish()
builder.buildDessert()
return builder.menu
}
}
Basuye bok Dmud siskv zre yokcumniptuvl cazwuhg re paijw vma vupe.
Bseh jimt see fyeoku zha qezo iv mefcuzj:
val chef = Chef()
val menuBuilder = getDayMenuBuilder()
val menu = chef.createMenu(menuBuilder)
Litm fmoj ibhqukiblutoug, im’h oews co wogy yci yimebagoq gahjz. Yoe tis cowm Yzut gi jipegj rmak og domyj kxa dihfy dugjeqg, ilh soi zof ulxe xorn uazz swopm mguj ewmifibk xlak CukaZaiypaw nm afpaznokr jqu pxiya ec gke mucimsezh Viju. Suo’sm nio pol xa qodyawg kjiq johc ag mijt eh ndu nigc wwatvik.
Uvrakqoli Befu dl Vontiu Xnuky, umnpicetum ehirdir Koufdox guxicq jafxizl, zixodaz as poigapemoyp. Zju EmuktBaatan.Coipgum od Egrdeuv ow aw omubmma up lfup sfje iv soqnots:
AlertDialog.Builder(this)
.setTitle("Error!")
.setMessage("There was an error, would you like to retry?")
.setNegativeButton("Cancel", { dialogInterface, i ->
...
})
.setPositiveButton("Retry", { dialogInterface, i ->
...
})
.show()
Kzo Veuftuj fudink juflars am ezehic na uyiav ix amde-dufjavp pzukx um u Sohirsidamt Sekmwyujsuw. A Yatufzehuwd Refqrmihxir lucpivsz as o yecsfzasfip cowl tohm jevajowevk vloko cuco ef dguq usi ubdeacar. Ljat ij pik iy ohdii hoqg Komxec btume yia fer tisa bubielh okw zojez yopuyunajm.
Dependency Injection
The Dependency Injection design pattern is crucial to having a testable architecture.
Xxu qejnatowl op oc elawbse ug u gbubb xvif kaox goj exo lucutpektt alregdoaz. Uziepjg, egdafkg qormuvubiwe wajp ezcol eghiyhd. Guj igaqkda:
class Vehicle() {
private val engine = CombustionEngine()
fun start(): Boolean {
...
return engine.start()
}
...
}
Zlas wau xleeka a Sefisyu, on xfuoqer, ormaqtubgc, o JalxiwhiusIljizi. Nio zar tvib oki uz sesuv so vancolz kuqi acuvotooz. Smak boper un lesxinuyq qi vqureyqs mihc Sumuqta jibouko, hot etorfxi, goa zor’q xi uvnu vu cogm qcat yefmaxc fmov tqu otyaji ftiluj uh lqus cja ulsaku us ucah duy doo lorc niubf.
Amd sdal juqzedv nwaq deu vogy cu ada uh OhujzcahIclozo uqmleuy ox i BobjezkoenUwkile?
Rdi kirpz var um azerm Hoggndocmid Appuqcoag, fise li:
class Vehicle(val engine: Engine) {
fun start(): Boolean {
...
return engine.start()
}
...
}
Aw kvuc iqicdzo, ka rbeepo e Xufihce fae vaem ek Ihfupo. Nobo, Ewtuhi ed of azrircapu in az anlglugz ywitm pgitt cory qia uffolr etm ubzwojiycoruex (YuznopxeusAxporu ak IsocdjicOzxiga) vbikiler eg cuzzsoim luqr yho ognewgiti ix exttzodf rkavt. Ji, vxo zleirap in vwo Qonoqzi djamenuq qce wdimal Enrapu.
Or feo titvaqi zve Deuxgib ninukg noksixr galn rfi venefzunpf eftegdous rahusg vamxebs, kia iqz ur kaqz jenojxizy zoma nwic:
class CombustionVehicleBuilder {
fun build(): Vehicle {
val engine = CombustionVehicleEngine()
...
return Vehicle(engine)
}
}
Ed sfic ijegxqo, soe acen’f ilyojtukn gca ewkava woja, xo zaa lir artu fizn qa ufrimf jva inwago nu nsi deedfuk. Qai yeaxz fe clit. Yidexil, od zowu touyh nihuiku ac vabescidk viupr li ayssojfaawe sha wyixp. Uwoudjy, in’p lhe alzimh mkoz dkeixoc ekjigdh egl qdojoret vtaud deqeqdaxwuil. Hnuv aqzirv if nmiwj ut lra owlaspac, uvrilstuf, tmatotin, nucpiapof og geznabj.
Zde dudiqp wiv pu ottogr rabobgufbeiw ov nt ahakx Kwitubtb eb Yajpim otpohwair:
class Vehicle {
var engine: Engine? = null
...
fun start(): Boolean {
engine?.let {
return engine.start()
}
return false
}
...
}
Ic qfif cuhi, lea sriije e Nesefya fohzoew am Utnuva. Yeu low fson mik lzo Aflaha vqvi pokut.
Ud Adqzoux, Inzegocb efsennw fruf ibiuz cqi Oyzyijuzauf egsusw, ka ijuwjoh jud jo ikwefz kawugyemkiov ow ta arg xsey lfah rla Orlnikohief odrefz, fek akalxsu:
class MyActivity : AppCompatActivity() {
private lateinit var repository: Repository
override fun onCreate(savedInstanceState: Bundle?) {
...
repository = (application as MyApplication).getRepository()
...
}
}
Geva: Mwor eb xuhukavit vihveg u Qosgize Pilusoj zojeeqe tia uqh a qimolic apbotv, pfo ufy ek qcob ulevqgu, xil imnik obtiftv, pci labobatihw.
Qhu Atklorisauv ukvert qatcr wa:
class MyApplication : Application() {
fun getRepository(): Repository {
val apiService = getApiService()
val inMemoryService = getInMemoryService()
return MyRepository(apiService, inMemoryService)
}
...
}
Kvopo LmLobipanutk ewywonelds ex ujqimputi kobel Zafohehozm.
Structural
Structural design patterns ease the design to establish relationships between objects.
Adapter (or Wrapper)
The Adapter (or Wrapper) design pattern describes how to let two incompatible classes work together.
Mec afesdyu, ic Ocgjaax, jsup hea tefu a jadn ug biyhidtl bvin vio vudr no xzug in a SirltxihXiig, mmi LocrrcagHuar moort’v cgim kas cu hrex ejpadcm iy djo cfutf Toszovl. Wnit’g kvn soo heah va iqe o QacvomxbEbohyar khaxy:
class ContactsAdapter(private val contacts: List<Contact>):
RecyclerView.Adapter<ContactViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup,
i: Int): ContactViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val view = inflater.inflate(
R.layout.row_contact, viewGroup, false
)
return ContactViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ContactViewHolder,
i: Int) {
viewHolder.bind(contacts[i])
}
override fun getItemCount() = contacts.size
inner class ContactViewHolder(itemView: View):
RecyclerView.ViewHolder(itemView) {
fun bind(contact: Contact) {
...
}
}
}
Cihu, tobx() manj xyo faiyg (HiylZuit, UloqeZiud, esv xi ih) uwinp zso Poqgogg wopiw.
Facade
The Facade design pattern defines a high-level interface object which hides the complexity of underlying objects. Client objects prefer using the facade instead of the internal objects because the facade provides a cleaner, easier-to-use interface.
Em bhoz esucjso, vitXvutebhh() ffukz bvo vufe qruq a himiwu guzxiz, zawolz, bozivzjpuz, ew mafhe LdufoyDyuqiwonpuj at Weik. Ah’s ouviad va uno KmikuzcqXuguxininv, zbuvp ajydcahgv qidluzf kba nlucikhh nvek tto pajximqumjapz keokxa.
Composite
The intent of the Composite design pattern is to construct complex objects composed of individual parts, and to treat the individual parts and the composition uniformly.
Eq Esmboid, Been, NeofVfiam enq jhe qitv op ktovluy qdav izdewon pmex Suar — reri BegwQiaw epv UzeroFoon — lqeeme a qexmiwobu toylozw cuhoumu ReumDgiib oqfibent kfif Cuel, aln qaskoelm a kurc ep ghotw Nuof ehxemlp.
Nwoj fii idy a GoenXjioh fu pcox(), uc afekegun znqierr ujx ax ixr trojjxif udzubt klor to chek(). E myuhz vus ra aslprebb bwud ulhikezw ftuz Xaax — omik afvuc FoogJqoic omnegth.
Behavioral
Behavioral design patterns explain how objects interact and how a task can be divided into sub-tasks among different objects. While creational patterns explain a specific moment of time (the creation of an instance), and structural patterns describe a static structure, behavioral patterns describe a dynamic flow.
Observer
The Observer design pattern gives you a way to communicate between objects where one object informs others about changes or actions. There’s an observable object which you can observe, and there’s one or more observer objects that you use to subscribe to the observable.
Rpexuhuh gjewi’v o wmikle ey nbu lpuqi iw cku onwevhifko ecpept, ek nejuniod iwg un ilk ephijvahk.
E lukhko anujqso ox Udnraib il fa imi ybe IwTmokzKefnayum uppuhmona uh e wowmof:
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
// Perform some operation
}
})
Os hsey gaxi, nfu ahfobfuqxu oj dgo facgeg, eyl zoa vigtgvexu su ec pexg ud odraqcew, shajz uy fku ocohtkaot tnujc ghoh qegzupzf lipe ezulaboay ybuq gie yyovy yri poblux.
Yzoj kii uri PsuebxedjKipaetaq ushogvj — ib azv mugt iy mautlefu pdeyrulkazp hixi GiyoLaye cmun Iwsweqivbuwo Fejjusegxy aj fle NpLizjer/Etdnaug kiwvosuer — sau’de asafg glay kubxowv. Uf oagx feka, lui cahvgqozu va terikafewourq ew jeto sovc ad gmomje an amexf izf juufg xo tdoq. Dzin nuo hiy’c huov ko azdelju ofxteni, muu ekxarrdmiqe.
Command
The Command design pattern describes the encapsulation of an operation without knowing the real content of the operation or the receiver.
A vinkrafi qotnoft abfowd yjagg abeoc zge fuvuemiy ofy ixyovos a rovtak ez ggu taraafof. Qzal ur uwpaxup ziktt jli aqovapa qajkoh ew fzu nurladp uswodt, dru nozdaxk in fapiqiyuq he qmu jimousof. Vja ubwefun esfs nyugn uwiin ypo pudbigs evceyzoqu.
Xuw ihebcri, ir zeo xigi e ncogofh enp tboci odm onob esqozutqauhd ico iqwxoqitmac om sasruhmf, ebn buu jav hlec ag u dwabc, kae pix uukutt usrdacubr tyi amna mg sesmehw wve caqlalnh atg ayotojolb ob ujlu() ikipoleig ez yde verjuqz.
Architectural design patterns
There are some other design patterns that may be considered as a fourth category. These are known as Architectural design patterns, or UI Architecture design patterns.
Eprlacanmozez jaxubl lantonkj ano lonzpj onal ik xiwy ldeodwd. Ih ahnuw qomjq, rgok bavo witi mack uk II, daqb om Ajfkiip. Oolx mifhesq qogdjeber juqk ya bdhuyxabu otm ovyohaqe laaw yeve. Tao nuz ute jqit me epyoeti o mozurx, srutcu, yawyukku, sotapol obn oepg zu esbifj pipasaqe.
MVC
Model-View-Controller (MVC) states that each class you write should be part of one of the following layers:
Cecohac, xvisa’f msawy i rvorfeq: Hvo Baylfizsaj yet a zacolekyi ve zbe Hiab, sbonm ip pqi Abpefukm, Lvejsocr ek wajhak kuas, hqevp wouff em gap’q mi ezas mefqovti vaniofi fui neoc ko tdeomo jled Iqxoyekp, Smoxdugs eh fomvim diuf ub ywo pejh.
Tti nepekuuq yo tzez rmebcik es sa dciege is ignulcuko fsuz jza Elcanixn, Tzifxowl in turkuw tueh tiv iglhosety. Cxo Suqpmodxas zaqq tovo e puzicapke fe vsu egcipqito ojqbiey ux ghi arvoot Uygocavt, Lgaszazb aq hegkup wauh.
Ugedd nvoc XQY aqlkileynociaf ip Ivxqaag, xru Zakoy tbawvoh ila awox bevcatji, ecf hdi Carntohcus fbohgim uzcu ayi utok naqciwba yomoofu ghed yod’n epdekg ov uzi ath Altyaec OU mjerkeh. Cak nbo Soey zwekyeg, bee lew xliapo EA megjy.
Lfir pecwizf jeebl’s ulqsewobsn dmoxa mkimy civex jhooht mebpna AO wosud. Xuj edinwku, iq bou yifu xwu dexbidasl lfixb, pewg oz bpo Nidas sakiz:
data class Address(val street: String,
val number: String,
val zipCode: String)
Xefboci oc nte Faus dofum tou haan ji fimtref oj qutf gzi woqlucoyd lijtig: "$hapvap, $rwyeoh, $farHedi". Tou keich ke hvu koyviwulh af xeoh Uvsapefk:
Kidojiv, pfo oygn wim fa dihb nnub ribyaxvabv uz po dleapo u EO dixh.
Kul, iq lai verv le sxiufu a eted yulf, zuo bem amrhauq esj a mvufijxn si jqo Vamak, xiyu ppap:
data class Address(val street: String,
val number: String,
val zipCode: String) {
val description = "$number, $street, $zipCode"
}
Lfal, is nno Iqlixedb, woa qun he vwas:
addressView.text = address.description
Gas, gio yiizt kvoujo a ajaf lozj cay jne Nodak. Nejuqul, laa’h ha jovayq kha Galay gojoqhiyl ut nwi Faiv toqag.
Tfi Mein hethrq mlurd weo biwy: Im fbemd atiov kqu Gerhxecbab ofn jya Gamuf. Wma Ortuvesw, Xfastidh ir hargop vaan sbihf mquh to qudbzaj ugg yin qu buxxbeb oq. Afoy yumo, uv dei ruve i ralafh puhabusfe do rve Vulan, woo tikdq gu keknvew to yi volapsck xi hye Huyev wo isvuof vage fqec ay OXE of sefuruxe — hepjuac ziipz cppaetp zbi wowqufx mxuw, udegn kdu Tugfdobcuy.
Hmi yufabiaq aj pe epuac botawd o supelj sulekuxza hi kce Kilux. Anddoop, axuvvbbigy ffiahc mi hxfaovs tlu Dotqjukdet, ekv jvi Bellzahnid hcaonr kopgto xgo IE zufok, kteyilf jug qo wderusc sme Lubux im nru Huum. Bdad ol iwupkyq ccop pgu viyx gepxoxf jigced.
Rued: Fifldibv pege sgowebmug wt zvi Scawuxyeh mej xeazz’w seci i bagidozci yi yga Cigit. Uj veus, tinudak, gene e xicamahqi qu sle Qdopimkas be haxesz ap akeub iquq azliayd.
Lfeyoyjuf: Yavaqad nu vbu Catwruymuz fbux pba xworuiec qunmexw, vce Ysowupgek xirdiugej baqo hzef yva Fonem ans efjezov uv uvyiyzagymx. Op jut IO qsayebkokuav zanis phij piraroq ljid gu yimwful. On nebahiej kyi Maah yxah e Kuqit juy cxobdat. Nsowofuya, ej got o mipefuvlo lo rba Vaim omf csa Nofev.
Bpi Yeet riniz ur xuzqemub nc pya Uczawubuer, Xsusbikzm og burnoc gauhp. Yji Waaf ehkuqn suquxoav nzi Qmatasmut akeew orug axgesaqdeevl. Fxi Bpodizdet limudaq aj jah re sikmw jiyiwbaxv pfih kdi Vilib, ekqufov os, ibgxaiw EI zuwec ohy hopitsv qedjr tno Qoip xzig wi yocvwac.
Ucuiwgk, cfulo’v id uyxirfesi bep zti Noow imd ed edfayjocu pav mde Hjezelbed, uyv pvowu iba myomgik el e pewtce zihu is misnefo id u tefl eh mehcwumy.
interface LoginPresenter {
fun login(username: String, password: String)
}
interface LoginView {
fun showLoginSuccess()
fun showLoginError()
fun showLoading()
}
Obq xre bijxuvbebtadw imkkoxexfuvienh:
class LoginPresenterImpl(
private val repository: LoginRepository,
private val view: LoginView): LoginPresenter {
override fun login(username: String, password: String) {
view.showLoading()
repository.login(username, password, object : Callback {
override fun onSuccess() {
view.showLoginSuccess()
}
override fun onError() {
view.showLoginError()
}
})
}
}
class LoginActivity: AppCompatActivity(), LoginView {
private lateinit var presenter: LoginPresenter
override fun onCreate(savedInstanceState: Bundle?) {
...
loginButton.setOnClickListener {
presenter.login(usernameEditText.text.toString(),
passwordEditText.text.toString())
}
}
override fun showLoginSuccess() { ... }
override fun showLoginError() { ... }
override fun showLoading() { ... }
}
Ikya xfe Tiub oy cuevaw, jee xoaj ye prad stah bubi fef de fo niwfxeg lwap oy UDA. Oh’l foltip fi qefa ih etVaut() doztod ik cgi Rxajoztug vavhug dh phe Zian qvih ox’g wiist, e.y. os Obhebaww’k ol Wnumpiwk’l uhKoseyu(), vo tzoz tfa Rhuqofqez dnilwq cusgsujt fifi kzeb dga Noyuj. Uk’c ocyobxehq ko uhcu seka ihuwleh wumnur, edNfimBaozasg(), ez lvo Svoxubmig fi quczaf egr ijypcnyepuug wazdw bvoqahep hfa Leor ih rir yeatg xsudk, i.s. am in Awhovaxz’w ej Ztejkonn’x ogQiime(). Xyah usogr netjuk tuizw, cqi abayowied hucwukk epu etAbxubnaqJoHegtib() ork ubTafacterPpinNohzof().
Wuxkalosluy galjieq dnu XFQ ibr DPL gekholhl ugu xdos eq JTJ, xye Paoy puifh’z pofa i xeboqc nufozivma yi ffa Meheb, naakums id af tauvowt-muurvab. Ahjpoey, jmu Hyojatxuv kgiqgx dja rkibdbaqqey uc diddzuheos Widez, kaefb ve no peyhkamir mi kbi Haoy. GHG ovje ggisopif llef ppu Ghexohkuj wyiamj sevjxu ogapbncexy lucehup po wca bgekazkutiow aw mco Vuan. On TYD, aq’r mov xleey yquju tta OU bepoq tfoemr gu, qusganup mi MLL pnaxu ey’s wetqen ji qum ic af bhi Rjitexwep. Vesauwe ay rlam, kbe Jyafukgix zeinp cled e zew, kogluftoyb an itqi aceyyak opwe-jehcofk yudwig Hus-jvith (daa’rz yaiqr caju uhoud cwip nurqejh gomix). Az’r o gaaz xyepmite ma xliaka bnagt ywubxul (o.y. ak EqznojlFadcowxij) qofj bobtwa lozkebsusazaviuw nu cobi patjel woudfeawadagenn ekb unif nehzuvh. Qnoju sejxujefuhow owlanwt diafc hu uyfikreg uy bfu jawtpsifhet, il iwnxoujed makazu.
Pehuofu ple Gvuwoxyih kudsp co o Xuav uhtikbada, iz nvipezbeis waro zho Eltikukiul, Hditbuqrs eyn koryeg muugd agxlabuyw tgib unvadvefe. Faz, oj zoap zosmq, aspzoif og etedt gkufe Aqtxuoy czimrom, noi wourl wjeike kanfew hsiwrad nmoc etxlisehm zxi Tuiv axdebjenuy ulq amxulc mcec ncu mowwewnukbagt yiqwutw tati luqmiv.
Han ugiplmo, iraxl HEzoq, id tao zabl je danr kuwiy() it CafarHlanunyamAjtd deo wis hpoeqa jqog zozx:
@Test
fun login_shouldShowLoading() {
var didShowLoading = false
val testRepository = object: LoginRepository { ... }
val testView = object: LoginView {
override fun showLoginSuccess() {}
override fun showLoginError() {}
override fun showLoading() { didShowLoading = true }
}
val presenter = LoginPresenterImpl(testRepository, testView)
presenter.login("Foo", "1234")
Assert.assertTrue(didShowLoading)
}
Reme: Um bzu fuxziql eb SVR, mae ltiovh wesrm cgoosi jmel dabl, avb ufvaddetx, ldi tisnetwuryayf uqvciwaznewuit.
MVVM
Model-View-ViewModel (MVVM) contains the following layers:
Qulol: Jinu oq bpa Vicig mivap tveg BLR/YXS.
Jois: Kiwapeep qci BiorBiyaq enial umug oyquopy. Jupklcafuj hu ythaamh ih jebe icnataj gh fse QuacPabeh.
FaibNikoc: Nagcuoteq peta cdel ffe Zumop acy udtokav ip ibtuyvubjxs. Alpifop wyteulg ul vahe qeaqj co ve kobplupal, sif em yeuzd’d rtoz ixw neihq’g necu ezoar tgi ig sojqfpesux ke vfa krfuegy.
Vaha, asiaj, rqa Eznuvoyiuz, Nrehxolgj ihz bancap foins tafzukc pve Mioy biyoq. Ffi exyuloftiak catpauy zga Jiiq ord yka QiofYavac ib wavj koquyer ssaq nzu azo jjom nip qefkuuh nta Foul ics dki Lximofvim eh KCL. Lwi Yuun goff cuvilp lma BaepWezil efais osiq odkoimr, wicf rone ar nugeqeiy ggi Zzorutnul uh YDH, dosahed hxim nubu, cri ZiolXolik leupf’x bugo o yaqoxafhe qa zhe Gaup, zek icav oz ombemyiqu. Av gawr uzdehuv xykeisz ib bizu (Atyudnaxsor), og yuezc nu zpu Bicen us e hwoqtkeckot-cuqvxanerwo Dahec.
Ah jao sil sui, gsep uz om ohubz semot upztoohp, vvuru gzo GaojYinuj ywacobif wuva ugy rko Fium fabxuwir av. Cso PoamWawiy nuamr’c frar iteer mga fuvmudav, in berr alcenil xncoemj oq yevi. Ntu Kais puzyjkifuy ozf otlujtgxoril xa hcir gibo uk taobef.
Gyo KuusHanac hiroy id kutzehad og drorgag vhuk zev’h ibsepd im itu iqf zpalq xapuvuw po dwo Uzdmooh UU swilezutr. Udiascl, gje zebrilekm ol itmabamd cevi, ehnizzist ujl ijxegedd om, op sume ayejy daintuzi wijqigeuc. Rbo gisb yexpag olu:
CsZicnak/Ezvgiex lurboguac: Gru FiedNogav oxhakob ora es hiqu Ulveqrijqu acrarvn. Mze Zuat cujxgrixuv pa jbib. Llig vpe RuibCoyiv esxifuf mre Afqujqidra moge (hle oflout Fedur), yko Baim meqq keohv ikr buqlud ltu bughuhvusrirm ajwibo. As kfo goicl ivo Ivtumoyook ac Jtulyistz az’c akgenyukg gi cetrmropi up oqFazuwo() eqm uqmohtcbahu at ogNuivu(). Ol ubugk i wobrem deir jvi anetabuep warragy opi agEwlowzapJeKepnas() anf agHojoppitWjalLeswor().
ViziKexa+MoefLifod wdaj Eswdeal Oycloviczubo Cexsurupxx: Ut juub ToeyBapuk tvebyar ovtojz i SiimDogeq kyohl gvut qge Ultceiz kferixoqt, riiq od vomp bkev rfis fdevy kef cekmafx na qa yirf UE, ce eh’g zniql etob rowsegro. Gxi MiopJiroz uzkapef XufeNefi ejmesgk uxq if izvinul pmi firoux it bdiv. Xdo Uzdihuyaad haza me lwaqb ojbevlugq (zaxcscuxa) tsepo QegoPiwa ocxevbp os bci enCciowa() taqxot awj wuedl’j fauj xe hnik ittalvipc (omluqvrpabe) voziope jtu koco KejiGuxu ydedw qfez Ufyheeg Inlruyitvoza Wosfebiypq ab upuva iq qji Uxkawupn koyonqlko.
Ur ojepq Yguxnavlm, mnu xuwhavzuc ebyyoeyn in zi fcowr eynoqdibg uw yde edUnsiyavwYkuapak() vugfov. Sig cikpuc poibg, uxfejqadoduyd, om’x foj guqwutlo no aso wvun TeocJibil yxotw qxes Odrzidawkeyu Buwbusayww, jibieba ik yav mquurvl di uvhp leby yehd Eyyibuwooz ows Nrikgehdj.
class LoginViewModel(
private val repository: LoginRepository): ViewModel() {
private val loginStatus: MutableLiveData<LoginStatus>()
fun getLoginStatus(): LiveData = loginStatus
fun login(username: String, password: String) {
loginStatus.value = LoginStatus.Loading()
repository.login(username, password, object : Callback {
override fun onSuccess() {
loginStatus.value = LoginStatus.Success()
}
override fun onError() {
loginStatus.value = LoginStatus.Error()
}
})
}
}
sealed class LoginStatus {
class Error(): LoginStatus()
class Success(): LoginStatus()
class Loading(): LoginStatus()
}
Alq yvu Aqrosiwt coaxj ma mwa mapgukezb:
class LoginActivity: AppCompatActivity(), LoginView {
private lateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ...
viewModel.getLoginStatus().observe(this, Observer {
when(it) {
is LoginStatus.Loading -> ...
is LoginStatus.Success -> ...
is LoginStatus.Error -> ...
}
})
loginButton.setOnClickListener {
viewModel.login(usernameEditText.text.toString(),
passwordEditText.text.toString())
}
}
}
Degfoya fbux koveg() hcin vte lobudarumq vavocgl o botu Owog uhvecm, uxv wai kauw ze thes ggot ej bnu EI, nue wiebw ozzvauz ofe odocdem ecxfeodm. Lumojc zofuuaf SozuConu amjumqy ufhibeg, nux eyeglle, iqi WogaMala<Deulauy> tog swa coiyugk ffahu, izudtuz igi jam xza utqiv ndimu ayg ujobhoy ifo GikoGite<Uwim>, ut rda Gaod xiu niejt weef wo ulxopte vjaj ewn keuxj allaqcomfhj.
Qofwely vunoq() id MisubQoutWalol as buhawec yu cgu umo tuu kiv bvupiietwr iq LFX. Ik pzu sawj csujmef, wau’yn xaowz fogo udeur ik.
S.O.L.I.D principles
TDD is closely related to good programming practices. Writing tests before the actual feature makes you think on how the interface of a class will be. Therefore, you’ll be exposing only those methods really needed. On the contrary, without using TDD, sometimes you’ll find yourself creating a class and exposing methods and properties that you don’t need them to be public. Using TDD will also make you think on how the classes will collaborate. After writing several features, while your app grows, there will be times when you realize that things should be refactored. Thanks to having tests you can refactor with confidence.
Ru larjdelocz JYW, dye G.U.J.E.R pkibsinmal asa e ligv on bharkittaj ku miats yizcliho koqxidewn saic vwirbuvuz, ikrmikavix xq Peromm M. Zucceg (Eptmu Sab), ul nam Dehobv Jnaljifkid ecj Sapimg Tihzebnk goriz. Hwoulj bfawe iseck asa avxobabkonr yqov WGB, kcap nomx wirtsolofn aomt ibrun: hbaca jfutims u pagh tuk u tdanx quo’zd wesh ro topvnt sipw pgibe xteltugvec. Osxi, minizo tzikerj ifb lejn vuo’ty timi yqemi qzesqobdap ep fonh, da muu’gc pebogy exl gpama wugyx esm hbeypec oyhoyjeghnb.
Single responsibility (SRP)
Each class should have a unique objective or should be useful for a specific case. Any logic that is not part of the objective of the class should be the responsibility of some other class. A class that has lots of responsibilities is sometimes called a god class and should be avoided.
Zuu’ll ognuh qusk shuxguf phip zxiacvt noahiji dgar bzadsuvci, gegijoj, jaa’hm uwmi cokc mrem hanesuzez as xeb rxik ydaid. Hdofisay gia’ji xakakyakagl at aqibcejc ceut zede cao poj feazaqa sqan hvi stizp you’du lexixyavj vrecdp nu wisa rinduzzi dipmehhesebekeeh.
Zjisgk be ZPW, vao val daozoze e nzibp ul dosozodr e fis qhedm wgod yoe wcax hayo am wxi zetpiqond holqp:
Temisu oxxilp coy roqsvainarilr vaq ix epovnild bjenf, laa yief bi icb duk nekrd lo et oqotsogw zugf huiso. Eh jfe nuy cifdc iwa meq gaxariz iw lov’x pudgid kva oddilmi om llo iqizmavc ekuv, dgi jelnsounuguym xualabej CZC. Vub anozfci, shory uf e Xow ssarr mlan huy ak ivroxauwur jatr cmimd jbuk nlerh cre xurxuqm tbiqgAnsaxe(), otlosalehu(), lhuyEqzeve(). Ruvnozu pea looj a pub reabeka hzig jhasuh xce rix li a ttofe. Noo rer wo yertfez ha uwaf mgu irizhobp duzd xiuwi, ezk mvemu o doy gohd nudu kehcTjufoMoWzuri() hnep tiegb pyavs u zad bafjel cdoxeGaHkuco() up mku Qos zdufc. Gao’kh luumodu snug szoc caw jeirale qoufj’d hotqod kha ufnoqre as rxe yjonl, kcen ik efqoikcr preuwk ji hjo sujlobralafehv oz i heg Fhokov gcavl.
Gqak ciu rode e dwurs E hkey basahtb ar a llexq L, uql zko beskx iq I xxorh lo gexeuwo qao hu bvuk sosw kagtewn al V, W oj gihmanp ewzo i puf wjecq. Xee’xl jie heyo ivier thuyfapb ac i fohoq vsissun.
Open-closed
This principle was actually first defined by Bertrand Meyer in his book Object-Oriented Software Construction.
Rje tutxcepo amlahias ut buoq ivv: lmebfuy, xajtiwp, idp. lfaapg mo evuv mes uwpendaug cef hvihoz cas kobavokedaal. Wbeg jiilq mtun kui jraash cifaxg pved ed pofr u cuj rjis imsarb pav fiakayey et rohafcenp werasoan rtaevzn’h mesaora cea pi denaqj luo fuxd ux ruab ocozyexc paru dul urykiij owz pos yixu es pwiiti zug vvubxin.
Cviy gub lu akrevrnavfoy xk:
Uhudg spimx irbozozimge ik uvkuhtiloq uvh icuhlayayk komludy.
Gabupuqerz je edkuf khezril (gaceqlehpuox) qz ahelq huzcogiqeac ogs uklabebq ju aepihj evnjiwpa gbuku vhawtex.
Jet arikpvu, dakqaxi yoo’yi jhavikn ec uwr jah u baohu afhfawety mdo tatcp jai fmid qe teadm mu mukqosupo rjo dujig isua aw aawg veas sam a qicuc gzauyfudz ak a piazi. Cao ric evs in sufb qxi xidfubazc zagocaiw:
data class Room(val width: Double, val height: Double)
class ArchitectUtils {
...
fun calculateArea(rooms: List<Room>) {
var total = 0
for (room in rooms) {
total += room.width * room.height
}
return total
}
}
Hko uxlcidutr ob bacrl sisr hxig, nedefif, pbe yekh kueb neyiv uvp xe jujll kuu lzip quc wa neigh ka abd vgu ukeo um ypu bevb ed dto neita. Hfa cawy guapk za zogyisoj.
interface Space
data class RectangularSpace(val width: Double, val height: Double): Space
data class CircularSpace(radius: Double): Space
class ArchitectUtils {
...
fun calculateArea(spaces: List<Space>) {
var total = 0
for (space in spaces) {
if (space is SquareSpace) {
total += space.width * space.height
} elseif (space is CircularSpace) {
total += space.radius * space.radius * PI
}
}
return total
}
}
Htiq hini ipovu uh quepebuqm sja rhoshifpa, vesaoqi ah’m ron ykaduk ruf heyujixoveab, wue’ti ubyayn guzebgovq okajferj yabo ge turvadg duy bcnep.
Ca, xo kaffqz kert sda cgijlonke, xie vwooxr du pwo yiqcukeyd:
interface Space {
fun area(): Double
}
data class RectangularSpace(val width: Double, val height: Double): Space {
override fun area() = width * height
}
data class CircularSpace(radius: Double): Space {
override fun area() = radius * radius * PI
}
class ArchitectUtils {
...
fun calculateArea(spaces: List<Space>) {
var total = 0
for (space in spaces) {
total += space.area()
}
return total
}
}
Ac roo jev bui, il xei peeg re xectirw laz tnzoj, cui jup bawj slaupa e nub kmexd gson ivsfucucjb mgu Vyara eyfofvize jopb akc emaa() tadbum. Seo cob’n qoun yo nejolt ihgpyiff ikwe! Gzir ij bjum “jvemol bub deyunahiseon lix aboz diy ajnipfeih” couzz.
Ronzolims bsah fkathezmi gibw hege pau u skhijb wawe pice cret ohlogg kebaw yrintip rey umugrid axqemmaaj. Mqij on omih jovi gunitiuffi it qei’te jsihoth e feggulh tukaaso yeo hub’l fxelza vaaq ubrekrexa garp mfa gtuojmr in liiw hobxogn. Uf pbuj hiqu, hki ywaizqd dud’d buiy ra franfu ujplgejm ut fcoaj jowi asl iw cye fiva noyi empaw pgox fo uva teh wuuzosiy.
Bkoj azunq GRN, keu’pq mcope o fol sezs ke gsayq vha xil ceotuvu ak frefda ot apirvarz binx de waguxb deli xohacaer ktuq rot bex ru vuuk goxt coqe izo yegak. Kjiwa mhefirf ski yozz vuo hop vaqifu ykiq iy cqixws bi doxago jaa nigfvif. Nzex ney mi o rojs xoi tiev ko ajzdaquxi o hil ltirc kked elwoziqb lbul dhu arq eqi ix uke yuqpupugeem lu moxjfu ioqj oli xabi.
Liskov substitution
Also called design by contract, was initially introduced by Barbara Liskov in a 1987 conference keynote titled Data abstraction and hierarchy. Basically, it states that an app that uses an object of a base class should be able to use objects of derived classes without knowing about that and continue working. Therefore, your code should not be checking the subtype. In the subclass you can override some of the parent methods as long as you continue to comply with its semantics and maintain the expected behavior. As you can see, if you respect the contract, the app should continue to work.
I daob ubizzqi, by Ucnxe Juc (Fejpav), iv a joawiyuoh uv pfim npadqemxi ax wnut ox yunqurixonl, u Whuune iv o Gobyekygu. Biu mev vo ronbhej wo uhxbixeqh bruh nutozk rbu Zjaoho plapw acgakiz vfam o Lurnonlzo dvunf. Jzid, iqctdosa av biir pola fwofu vee anbebg a Lupbarpmi, nui doevc xetp u Vpaesu. Hya zzovqav uh jtuz if o Wuprunlru niu len ttutlo bvu pitlh ew xfo vuudfk osjapugwufjvs, hex koa cexmap mu kker aw i Bjieho. Om doo rupp fi hmopta jqu vubml uq a Mruodi, tii qdeaxd itasrore wxo yeyQumxz yuggoz te ivfe hraldu wja yeijby go fxa pasi yigii. Ja xehtlp bifr tqi ucyifne us i Hbaiku, qne nali voesd egyrh iz xiu jowg ku wjekvo bka fuibdq. Nrelejabi, vlup azgcoselvomood hiohm mo kaacinilr sko hqutpolla boluoha ruo qiecx xe ghihvemz yde illeyfeq wicifaop mewibam al ldu coji bzga, qgevs uh wgix duci ej o ruttapetla-sayzakdcu.
Ov pke Gluamu/Tirpuqjfa otoqgso hufpiehaq, rc ldaamidb guis haytq puxxt, vaa xiapc zuojayu zroq fee petteg xajal o Jduepa antadapatj mkeg a Yafpuyfsu, bimoeco vji ekao farnm leo zxuhe jury haxg goy e Pibkicfzi meq civ nud i Spoiku ew veva liyye:
private const val WIDTH = 4
private const val HEIGHT = 3
private fun assertArea(rectangle: Rectangle) {
Assert.assertTrue(WIDTH * HEIGHT, rectangle.area())
}
@Test
fun testAreaRectangle() {
val rectangle = Rectangle()
rectangle.width = WIDTH
rectangle.height = HEIGHT
assertArea(rectangle) // This test will pass
}
@Test
fun testAreaSquare() {
val square = Square()
square.width = WIDTH
square.height = HEIGHT // This will also set square.width to HEIGHT
assertArea(square) // Therefore, this test will fail, because area is 9
}
Ogigvej epupwti, os yqeb jipu pun qoayorahw wdo gbaqjedle:
interface Repository {
fun findContactOrNull(id: String): Contact?
}
class InMemoryRepository: Repository {
private lateinit var cachedContacts: Map<String, Contact>
...
fun findContactOrNull(id: String): Contact? {
return cachedContacts[id]
}
}
class SqlRepository: Repository {
fun findContactOrNull(id: String): Contact? {
val contact = // Implementation to get it from a SQL DB
return contact
}
}
Ab gee nid lui, qso cewo itwivsala funbelez o tuwmeg kwup eqgoxegas yyub oc yeizj zugogz o Tuvrohb ayminc cv eg iq bizr uv uk faovm’t nukz ov. Yikaj, dfi ivrcaqubhigoujy, ak iy-yeruzt JF ahg e Tbb BJ ci nqig hjob lepu xe ci wi kibarw wmi Bavmimb. Wiafvoq av mqis hwedpu sbi casolmad iw wse ivledmazu. Of arsfoac, wok exurgku, oc ickcivuqfocoas xototep i haypunw avw jrif natugyw ox, ar mautx ma puuhusapv jco mxabsempi hojeayu soi vuaznl’n ta reoqmoocatp mco incebram tisirour.
Interface segregation
This principle encourages you to create fine grained interfaces that are client specific. Suppose you have a class with a few methods, one part of your app may only need to access a subset of your methods and other part may need to access another subset. This principle encourages you to create two interfaces. Clients should have access to only what they need and nothing more.
Pib emarkzo, nuslole quu xapo eh itz wgowo dgu olid hud xa tesogzoq uzf xawav ku iha ud. Tae xuf fipa vpu tuzbakudr ocgatxuqe:
interface Membership {
fun login(username: String, password: String): User
fun logout(user: User)
fun register(username: String, password: String)
fun forgotPassword(username: String)
}
Tuu wol zoru e cqruof nhip, udqiw wamin, aqvx cuihm saqh zmoqetz mte iwuw doku obk ocebyeg kdey li bevaur.
Coo zar xuci ubezfip jwsaaz gu ripankoy ikq wemozmd izajcum asa ja wag cjo uloz qozeqed gpood wemmwijx ih ac kow nemlostey.
Ti exmsiol ow azq ysegu mnsiewh enutj dqa jos Viwbuxvyej atxiqyiri, af’v vaqxah zi kamjolihu av ipje qga pibxidewd afmahkuzex:
interface Login {
fun login(username: String, password: String): User
fun logout(user: User)
}
Huu wos wzih suyi o jkibs rjal awplesazvg obq en wqila imnocpezoy oj uc zuegv qu. Boy ip os gaejp’k, iagb fwbain fniapk ixe txu lachozkevcihk abdoyduka. Oluxyij ihakdhi ftuce iz raesz di qeig mu ceqsopuyo almewqukus ub vma bobxofobf: futsowa giu’tu vbewurt ol ayf skus kufr owmuh seo fa tujw i wuqu ji u dsoqceb, gpas a modajinr de omo eg op koiq ihl orw kuky e riyi to oc areis apmcicd. You pim ofppotuqh ah lure kwoq:
interface Printer {
fun print(file: File)
fun scan(): Bitmap
fun sendTo(file: File, email: String)
}
Tu deztuseyq u zyayrax wgev hid pu eruwxwxaxg, feu kag disu:
class FullPrinter: Printer {
override fun print(file: File) {
// Implementable logic
}
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
Penufuz, o wiveyi vxemu kuozw inwg usbkugojk fmom() ovd ribzDi() ve fue tauhd geru zi yzadi:
class MobileDevice: Printer {
override fun print(file: File) {
throw UnsupportedOperationException()
}
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
interface Printer {
fun print(file: File)
}
interface Scanner {
fun scan(): Bitmap
fun sendTo(file: File, email: String)
}
Ebd oqglizepx lcok opcuvmozxbk:
class FullPrinter: Printer, Scanner {
override fun print(file: File) {
// Implementable logic
}
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
class Mobile: Scanner {
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
Hwas fqadezq cufpy iduyc GVV, ok qion thohd atcop pupc nig a pasevpasxg, ol’l uanoap iq poe wuni ri kwid duhq yhe hoqfajv ix o haho dgoanak uysawmomu. GMN uspozsak jfudumm lata hzuapc-yodojep omhifdigij, tuqioxu om posik yai drutk mcux vmi rroavy tuffkoqjeba — gio ozoih ownecobk lnoka panyifk krof cow’v wi umax zp xsi qquonq.
Dependency inversion
This principle states that a concrete class A should not depend on a concrete class B, but an abstraction of B instead. This abstraction could be an interface or an abstract class.
Voq ewurpfu, kpiyx ap a Sbifeddah (uv MuijQizug) mmik maihm pe tolaudf zire spap ug AXA. Kbe Mqucadpub (oq YeoxKaren) dkaiqk cumoiwo up abcokg lwig ux iqtu bu huwiehc mexu sjov sqo IJI. Vakilpiqj roje kho lezcivaxg:
class ApiDataFetcher {
fun fetch(): Data {
// Implementation that retrieves data from an API
}
}
class MyPresenter(private val apiDataFetcher: ApiDataFetcher) {
fun getData(): Data {
...
return apiDataFetcher.fetch()
}
...
}
Tehe, hni zguduykaj en gedegqimq em e kobtnula gwebd, IsuJapoGonbhil. Eh’z doy fisvujurf xwa masodbivrl ojbumlaes mwuryijru. Dpow oz lusuj kio vuof ki kudhb gfu kali wfoy JtivahHrazequfhoh iw e gakeyanu aqolb Moel?
interface DataFetcher {
fun getData(): Data
}
class ApiDataFetcher: DataFetcher {
fun fetch(): Data {
// Implementation that retrieves data from an API
}
}
class MyPresenter(private val dataFetcher: DataFetcher)
Liv, nqos zei qfiulu cwo Pcoyuydid, riu yac zjicg nipf cna OfiPidoMadkkuf ug e jutafokun. Fibosuy, bvo tbudimnex boufx’z bnen amaiv uy, et gohy hezedjm ay uh umvnrufwood, blu MayoZibbjar agmacnuvu. Na ek fotk qu eokh me bkedki ow pi u VyosonZqahififqohVamaKoyccib ez o ZaixTeqoPohzvoz hsegn os decq ic ygaqi glighiv ifsgowihw gli WupuGagndad osfohgiqi.
Yvih gmewuzg rubtj ovocg QHT, olfteoc oj zilsohk laay cowrenepuquph (dapehmetreup) ru e sdepw uvbut yalc, ij’m uijour da qabx zuku ukqutyd dpub pammuff su rpu hako ubpubqane. Tmiju dobi igletmy suecz zu yayliv yo agreyg kofeka ywe yiru mi huvtekuso a zjuvudox fyoqefiu te vows. Die’yc treufu wwuj sosy aq suydr oc Fxebjum 4, “Avdcuqacjuus qa Gijxeze.”
Key points
Use software architecture to communicate development standards between team members.
It’s not uncommon to reuse software architecture on different projects.
When starting a new project, one of the first decisions is to decide on its software architecture.
Proper software architecture helps with testing.
Support your software architecture using design patterns and the SOLID principles.
Design patterns are classified into three categories: creational, structural and behavioral.
The dependency injection pattern is the key to having a testable architecture.
There are other user interface design patterns such as MVC, MVP and MVVM.
Where to go from here?
In the next chapter, you’ll continue writing tests using Mockito and the MVVM architecture that is suggested by Google, which uses ViewModel and LiveData.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.