2013年10月24日木曜日

Behavior Driven Development

Өмнөх бичлэгүүдэд TDD-н талаар хэд хэдэн удаа дурдсан билээ. Энэ удаа Behavior Driven Development (BDD)-н талаар цухас дурдъя.

BDD нь TDD-тэй зарчмын хувьд адилхан зүйл бөгөөд TDD-г хэрэгжүүлэх нэг арга зүй юм. TDD гэсэн нэг олонлог байна гэж үзвэл BDD нь түүний доторх нэг дэд олонлог мэт. Өөрөөр хэлбэл BDD нь TDD-н нэг тухайн тохиолдол болой. 

TDD нь хийх гэж буй программыг хөгжүүлэгчийн өнцгөөс (low level: software requirement) хардаг бол BDD бизнес хэрэгцээ талаас (high level: user requirement) нь хардаг. Ийм учраас BDD нь системийн шинжээч (эсвэл хэрэглэгч) болон хөгжүүлэгч хоёрын хоорондох ойлголцлын зөрүүг арилгах, нийтлэг нэг хэлээр (ubiquitous language) ярих боломжийг өгдөг*. 


* Программ хөгжүүлэлтийн нэг том асуудал бол төсөлд оролцогч талуудын ойлголцлын зөрүү байдаг. Оролцогч талууд нэгдсэн нэг ойлголттой болох, ойлголцлын зөрүүг арилгах үүднээс нийтлэг хэллэг (ubiquitous language)-тэй болох нь чухал. Энэ талаар Domain Driven Design гэдэг аргачлал буй. BDD-д Domain Driven Design-ы нийтлэг хэллэг гэдэг ойлголтыг оруулсанаар том дэвшил гарсан гэж би хувьдаа үздэг.

BDD нь
  • TDD-г хэрхэн зөв хэрэгжүүлэх вэ?
  • TDD-н талаарх буруу ойлголтыг яаж залруулах вэ?
  • TDD-г хэрхэн хялбархан ойлгуулах вэ? 
гэдэг эрэл хайгуулын үр дүнд буй болсон аргачлал юм.

Туршлага багатай хүмүүст TDD-г хэрэгжүүлэх явцад 
дараах бэрхшээлүүд гардаг байна:
  • Юуг тестлэх ёстой юм бэ? 
          Чухам алийг нь тестлээд, алийг нь теслтэх шаардлагагүй вэ? 
  • Тестийг хаанаас эхлэх вэ?
  • Тестийг хэр хэмжээгээр хийх вэ?
          Хэдийд тестийг дууссан гэж үзэх вэ?
  • Тест методыг юу гэж нэрлэвэл зохимжтой вэ?
  • Тест амжилтгүй болсон шалтгааныг хэрхэн олох вэ?

Юуг тестлэх ёстой юм бэ?

BDD-д шалтгаан үр дагаварын холбоосыг чадамгай ашиглаж шийдэх асуудлыг хялбархан томъёолдог. Одоо байгаа төлөвөөс дараагийн төлөвт ямар шалтгааны улмаас шилжиж болох вэ? Өөрөөр хэлбэл бодлогын даалгаврыг (программын спекийгшалтгаан үр дагаварын холбоос байдлаар бичих, эсвэл одоо байгаа спекийг энэ хэлбэрт хялбархан хувиргасанаар программын спек өөрөө тест спек болж хувирч чадна. Ингэснээр юуг тестлэх вэ гэдэг асуудал тодорхой болж байгаа юм.

Яг л бодлого бодож буй мэт. Ялгаатай нь хариуг нь урьдаас мэдэх агаад түүнийгээ системийн гаралт (behavior) ийм байх ёстой гэсэн байдлаар томъёолдог.

Given: Анхны буюу одоогийн төлөвт байхад (өгөгдсөн нь:)
When Оролт. Ямар нэгэн үзэгдэл өрнөж, тодорхой нөхцөл биелэхэд (гаднаас нөлөөлөхөд)
Then: Гаралт. Ямар хариу үйлдэл (behavior) хийх вэ?

Тодорхой жишээ авъя.
    Scenario: ATM-с бэлэн мөнгө гаргах
         Given: Дансны үлдэгдэл хүрэлцээтэй, 
         And: карт хүчинтэй байгаад,
         And: ATM-д хангалттай бэлэн мөнгө байгаа бол
         When: Хэрэглэгч бэлэн мөнгө авах хүсэлт гаргавал
        Then: дансны үлдэгдлээс [хүссэн мөнгөний хэмжээгээр] хасалт хийж чаддаг байх,
        And: бэлэн мөнгө АТМ-ийн мөнгөний амнаас гарч ирж байх,
        And: залгиураас карт буцаж гарч ирдэг байх ёстой.

Дээрх спекийг тест код болгож хөрвүүлбэл иймэрхүү болох ажээ.

public class ATMFunctionalTest {
    private ATM atm;

    @Given("Дансны үлдэгдэл (@remain) хүрэлцээтэй,")
    @And("карт хүчинтэй байгааd (@isValid),")
    @And("ATM-д хангалттай бэлэн мөнгө (@amountOfMoneyATMHas) байгаа бол")
    public void anATM(double remain, boolean isValid, double amountOfMoneyATMHas) {
        atm = new ATM(remain, isValid, amountOfMoneyATMHas);
    }

    @When("Хэрэглэгч бэлэн мөнгө (@requestedCashAmount) авах хүсэлт гаргавал ")
    public void cashRequested(double requestedCashAmount) {
        assertThat(atm.getAmountOfMoneyATMHas(),                      
                        equalOrGreaterThan(requestedCashAmount));
        assertThat(atm.getCashRemain(), equalOrGreaterThan(requestedCashAmount));
        assertThat(atm.isCardValid(), equalTo(true));
        atm.withdraw(requestedCashAmount);
    }

    @Then("дансны үлдэгдлээс хасалт хийж чаддаг байх (@cashRemain), ")
    @And("бэлэн мөнгө АТМ-ийн мөнгөний амнаас гарч ирж байх (@isCashDispensed),")
    @And("залгиураас карт буцаж гарч ирдэг (@isCardReturned) байх ёстой.")
    public void theCashRemainIs(double cashRemainboolean isCashDispensed, 
                                      boolean isCardReturned) {
        assertThat(atm.getCashRemain(), equalTo(cashRemain));
        assertThat(atm.isCashDispensed(), equalTo(isCashDispensed));
        assertThat(!atm.isCardInserted(), equalTo(isCardReturned));
    }
}


Тестийг хаанаас эхлэх вэ?

Хэрэглэгчийн хувьд хамгийн хэрэгцээтэй байгаа behavior (зан үйл: системийн ажиллагаа, гадны нөлөөлөлд үзүүлэх хариу үйлдэл) аль вэ? гэдэгт анхаарлаа тавьбал хандах зүг өөрөө тогтно (kkkk). 

Agile хөгжүүлэгчид ямагт хийх ажлаа эрэмбэлээд хэрэглэгчид хамгийн чухал, эсвэл хамгийн эрсдэлтэй гэсэн зүйлээс эхлэн гүйцэтгэхийг эрмэлздэг.



Тестийг хэр хэмжээгээр хийх вэ?

Программын спекээс (behavior or story*) ургаж гарсан сценарь (scenario) болгоныг тестлэж дуусаад, кодын давхцал байхгүй болсон бол дууссан гэж үзэж болох юм. Энгийнээр хэлбэл behavior-г дэлгэрүүлээд scenario (тухайн тохиолдол)-уудад задлаад тухайн scenario бүрийг тестлээд дуусахад л тест дуусна гэсэн үг юм.

* Agile хөгжүүлэгчид нүсэр том шинжилгээний бичиг баримтын оронд User Story хэмээх хялбар картыг (хэдхэн (1-3) өгүүлбэрт багтах ердөө л нэг хуудас) ашигладаг. Программын шаардлагыг жижиг story (behavior)-нуудад  хуваагаад хамгийн чухлаас нь эхлээд тухайн story дээр дурдагдсан нөхцөл шаардлагыг хангах хүртэл гүйцэтгэл хийнэ.


Тест методыг юу гэж нэрлэвэл зохимжтой вэ?

Тест методын нэрийг оновчтой зөв өгсөнөөр (бүтэн өгүүлбэрээр) яг тестлэх гэж буй зүйлдээ анхаарлаа төвлөрүүлэхээс гадна тест амжилтгүй болсон үед шалтгааныг хурдан мэдэх боломжтой болдог.

JUnit* мэтийн тест framework-г ашиглах үед тест методын нэр тест гэсэн үгээр эхлэдэг. Үүнээс болоод тестийн тайлан энгийн хүнд ойлгомж муутай болдог тал бий. Үүнийг agiledox гэдэг tool-ээр илүү хүний хэлэнд ойртуулж өгсөнийг анзаарсан Dan North (BDD-г үндэслэгч) методын нэрийг test-р биш should-р эхлэх ёстой гэсэн санаа гаргажээ. Нээрээ хэрэглэгчийн өнцгөөс харвал should-ингэх ёстой, ийм байх ёстой гэсэн спекийг шалгаж байгаа гэдгээ бүрэн ухамсарласнаар илүү зөв тест хийгдэх ажээ. Тестийн тайлан ч жирийн хүнд ч ойлгомжтой болж ирдэг байна.

*Cүүлийн хувилбаруудад annotation(@Test) ашиглаж болдог болсон тул тестийн нэр дурын байж болно.


Тест амжилтгүй болсон шалтгааныг хэрхэн олох вэ?

Dan North тест амжилтгүй болсон шалтгааныг хурдан олж чадахгүй байгаа нь хүний хэлэнд ойр, жирийн хүнд ч ойлгогдох тайлан байхгүйгээс болж байна гэж үзжээ.

Тестийн үр дүн :

Given Дансны үлдэгдэл (100) хүрэлцээтэй,
And карт хүчинтэй байгааd (true),
And ATM-д хангалттай бэлэн мөнгө (10000000) байгаа бол
When Хэрэглэгч бэлэн мөнгө (10) авах хүсэлт гаргавал 
Then дансны үлдэгдлээс хасалт хийж чаддаг байх (90),
And бэлэн мөнгө АТМ-ийн мөнгөний амнаас гарч ирж байх (true),
And залгиураас карт буцаж гарч ирдэг (true) байх ёстой.

Амжилттай болсон тест ногооноор амжилтгүй болсон тест улаанаар гарна.


Төгсгөлд нь

BDD нь TDD-н гүйцээлт юм шиг санагдлаа. TDD-д тестийг эхлэж бичээд, дараа нь гүйцэтгэнэ (жинхэнэ кодыг бичнэ) гэдэг ерөнхий зарчмыг зааж өгсөнөөс хэрхэн гэдгийг жаахан бүрхэг орхигдуулжээ. Харин BDD нь TDD-г хэрхэн амжилттай хэрэгжүүлэх вэ гэдэг талаас нь авч үзсэнээр туршлагагүй программчлагчдыг ойлгомжгүй байдлаас гаргасан юм болов уу?

Энэ арга нь хэрэглэгчийн хүсэлт хөгжүүлэлтийн шат бүрт гээгдэж, гуйвж ирэх, программын спекийг буруу ойлгохоос сэргийлэх сайн арга юм. Ялангуяа та "шинжилгээ-зохиомж-гүйцэтгэл-тест" гэсэн дарааллаар хөгжүүлж буй бол шинжилгээ-гүйцэтгэл хоёрын хооронд ямар нэгэн байдлаар зөрүү гарахтай бишгүй таарсан буй за.

Программмчлагч хэрхэн гоё код бичих вэ? Мэдээллийн сангийн тэр хүснэгтээс өгөгдлийг ингэж аваад тэгж дэлгэцэнд гаргаад гээд хэт системийн далд хэсгийг бодсоор (явцуурсаар) хэрэглэгчийн үндсэн хэрэгцээг мартах, дутуу ойлгох, жинхэнэ асуудлаас хөндийрч холдох явдал гардаг. Харин хэрэглэгчид гадны өдөөлтөнд систем ямар хариу үзүүлэх вэ гэдэг л чухал байдаг. Энэ 2 өөр ертөнцийн хүмүүсийг холбож байгаа бас нэгэн гүүр нь  BDD юм. Энэ утгаар BDD-г Spec Driven Development гэж бас нэрлэдэг.

Ингэж хэрэглэгчийн хэрэгцээнээс (system specification) шууд тест код гаргаж авч хөгжүүлсэнээр дээрх асуудлыг шийдэхээс гадна тест код их ойлгомтой болдог ажээ.

Дашрамд хэлэхэд одоо хөгжүүлж буй системд Rails ашиглаж буй бөгөөд rails-н built-in testing framework (minitest) ашиглаж буй. Өнөөдрөөс эхлээд RSpec-Capybara-Selenium ашиглаж илүү хялбархан тест хийх аргыг манай нэг инженер судлаж эхлэлээ (Тамир аа. Чи шүү!). Энд дурдсан RSpec бол BDD хөгжүүлэлтэд зориулсан testing framework юм.

Та BDD-н талаар илүү ихийг мэдэхийг хүсвэл:
  • http://dannorth.net/introducing-bdd/
  • http://d.hatena.ne.jp/digitalsoul/20090819/1250686015
  • http://blog.mattwynne.net/2012/11/20/tdd-vs-bdd/
  • http://ericlefevre.net/wordpress/2008/06/28/behavior-driven-development-vs-test-driven-requirements/
  • http://en.wikipedia.org/wiki/Behavior-driven_development
  • http://ja.wikipedia.org/wiki/%E3%83%93%E3%83%98%E3%82%A4%E3%83%93%E3%82%A2%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA

2013年10月15日火曜日

Pair Programming: How Implement It?

Өмнө нь TDDPP-тэй хослуулан хэрэглэвэл маш үр дүнтэй гэж дурдсан билээ. Энэ удаа хэрхэн хослуулан хэрэглэх талаар жишээгээр тайлбарлая гэж бодов. 

Нэлээд хэдэн жилийн өмнө нэг сайхан жишээ олж харсан юм. Түүнээс давж бичиж чадахгүй болов уу гэж эмээж байгаа тул тэр жишээг монгол хэлнээ хөрвүүллээ.

Эндээс TDD болон PP-н талаар хангалттай ойлголт авах болов уу гэж найдаж байна. Энд дурдагдах зарим нэр томъёог шигтгээ оруулж (* Орч) тайлбарлахыг хичээлээ. Эргээд харсан чинь нэлээд урт бичлэг болжээ. Эцсийг нь хүртэл уншаад ойлгоогүй, дутуу дулимаг зүйл байвал сэтгэгдэл үлдээнэ үү!



Галт тэргэн доторх TDD

0. Эхлэл

Сайн байна уу? Амьдралдаа тохиолдсон хамгийн содон туршлагыг та бүхэнтэй хуваалцья. Энэ бол PP болон TDD-г хөдлөж буй галт тэргэн дотор
хэрэгжүүлсэн тухай яриа билээ.

PP бол хосоороо нэг программыг хөгжүүлэх арга бөгөөд алдаа багатай, уншигдац сайн, хялбар код бичих боломжоор хангадаг арга юм.

Харин TDD бол Test First Programming гэж [бас] хэлэгддэг эхлэж тест код бичээд, дараад нь жинхэнэ кодыг бичдэг арга юм.

Энэ 2 арга нь хоорондоо сайн таардаг, хоёул ихэд дэлгэрч буй XP-н туршлага билээ.

Жинхэнэ сэдэв рүү орохын өмнө TDD-н талаар бага зэрэг тайлбарлая. Өнөөг хүртэл "зохиомж" => "код" => "тест" гэсэн дараалалтайгаар программыг хөгжүүлэх арга зонхилж байсан билээ. Харин TDD-д "тест" => "код" => "зохиомж" гэсэн эсрэг дарааллаар программыг хөгжүүлдэг. Энд дурдсан "зохиомж" бол "кодлох" үйл явцын үр дүнд бий болсон дахин зохиомжлох ажиллагаа буюу refactoring юм.



Дээрх процесс ("тест" => "код" => "зохиомж")-т нэгжийн тест хийхэд зориулсан JUnit гэх мэт tool-г  хөгжүүлэлтийн чиг баримжаа болгодог.

JUnit-нь тестийн үр дүнг амжилтгүй бол улаан, амжилттай бол ногоон өнгөөр харуулах ба хөгжүүлэлт гурвалсан буюу "тест" => "код" => "зохиомж" хэлбэрээр өрнөнө.

TDD-г үндсэндээ дараах дараалалтайгаар хэрэгжүүлнэ.

Алхам Зорилго JUnit дэх өнгө
1
Тест бичих Улаан
2
Кодлох Ногоон
3
Refactoring Ногоон

Энэхүү документаар Китано (北野), Хиранабэ(平鍋) 2-н, Stack*-г сэдэв болгон галт тэрэг дотор явуулсан TDD (бодит явдал*)-н талаар өгүүлэх болно. 
* Энд дурдсан Stack нь C++-н standard library-д багтах stack<int>-н спек (spec)-г үндэс болгов. pop() утга буцаахгүй гэдгийг анхаарна уу!            
*2003.04 сард Хамамацүд зохиогдсон XP-н семинараас буцах замд болсон явдал. 
Дашрамд энэ үйл явдал Майбара(米原)-Хүкүи(福井) өртөөний хооронд галт тэрэг дотор өрнөсөн билээ.
            
             米原 ==== 長浜 ======== 敦賀 ======= 鯖江 ===== 福井



1.Майбара өртөө 
~Бүх үйл явдал эндээс эхлэсэн юм.~ 

Хиранабэ: Китано! Notebook чинь цэнэгтэй юу?
Китано: Яасан? Mэил шалгах гэсэн юм уу?
Хиранабэ: Үгүй ээ, үгүй. Өнөөдрийн семинар дээр хийсэн Stack-г бодитоор хийж үзмээр санагдаад.
Китано: Нээрээ, тийм шүү. Түр хүлээгээрэй. Асаагаадахъя.

(Цүнхнээсээ notebook гаргаж ирээд асаав)
Китано: За, Eclipse нээчихлээ.
Хиранабэ: За, TDD-р Stack хийхээс өмнө стекийн спек ямар байдгийг жагсаая.

  • isEmpty(): Стек хоосон бол true, үгүй бол false буцаана.                                         boolean isEmpty() 
  • size():  Стекийн хэмжээг буцаана.                                                                           int size() 
  • push(value): value-г  Стекийн оройд хийнэ.                                                             void push(int value) 
  • pop(): Стекийн оройн утгыг авч хаяна. Стек хоосон                                           бол java.util.EmptyStackException-г чулуудна.                                               void pop()                                                                     
  • top(): Стекийн оройн утгыг буцаана. Стек                                                         хоосон бол java.util.EmptyStackException-г чулуудна.                                   int top()
Китано: Классын нэр Stack байх нь ээ?
Хиранабэ: Тийм. Эхлээд би driver* болъё.
PP-д код бичиж буй талыг driver, хажуунаас дэмжигч талыг navigator гэнэ.  
Китано: Ойлголоо. Тэгвэл, эхлээд прожект үүсгэе.
Хиранабэ: Прожектын нэрийг Stack гэчих үү?
Китано: Тэгье. Прожект үүсгэчихээрээ, JUnit нэмчээрэй*. 

* Орч: Java прожектын classpath-д JUnit-н library (jar file)-г нэмэх
   classpath: Java class-г хаанаас олж болохыг заасан зам
   library: Логик холбоо бүхий хэсэг бүлэг class-н багц
   jar file: Java-н library file-г jar өргөтгөлтэй file-д хадгалдаг.
  
Хиранабэ: Прожект үүсгээд, JUnit нэмчихлээ. Test case үүсгэх үү?
Китано: Аанхаан. Stack-н тест юм чинь классыг StackTest гэж нэрлэе.
Хиранабэ: За. StackTest-н template-г үүсгэчихлээ.


   import junit.framework.TestCase;

  public class StackTest extends TestCase { 
  } 

2. Нагахама (長浜)Өртөө
~Assert First~

Хиранабэ: Эхлээд ямар тест бичих үү?
Китано: Харин ээ. Stack-г дөнгөж үүсгэсэний дараа хоосон, ө.х isEmpty()-н буцаах утга true байгаа эсэхийг шалгадаг ч юм уу? 
Хиранабэ: OK. Харгалзах тест кодыг бичье. testCreate() гэчихвэл болж байна уу?
Китано: Аанхаан болж байна.
    import junit.framework.TestCase; 
  
   public class StackTest extends TestCase { 
       public void testCreate() { 
           assertTrue(stack.isEmpty()); 
       } 
   } 

Хиранабэ: Энэ болж байна уу? 
Китано: Болж байна. Гэхдээ assertTrue-c өөр юм бичээгүй болохоор ...
Хиранабэ: Мэдээж, алдаа гарна. TDD-г хэрэгжүүлэх явцад, түрүүлээд assert метод бичдэг хэвшилтэй  юм л даа. Энэ нь юуг шалгахаа эхлээд тодорхой болгочоод, дараагаар энэ шалгалтыг хийх явцад шаардлагатай кодыг бичих учиртай.
Китано:  Аан, ойлголоо. Тийм учиртай байсан юм уу?
Хиранабэ: За, шаардлагатай кодыг бичье. Энэ болж байна уу? 

import junit.framework.TestCase; 

pulibc class StackTest extends TestCase { 
  public void testCreate() { 
   Stack stack = new Stack(); 
   assertTrue(stack.isEmpty()); 
  } 


Хиранабэ: Complile хийж үзэх үү? Eclipse дээр яаж хийдэг гэл ээ?
Китано: Хадгалангуут Compliler ажиллаж эхлэнэ. Би emacs-тай key binding хийсэн болохоор Ctrl-x s-р хадгална.
Хиранабэ: За хадгаллаа. Compile error гарчлаа.
Китано: Тийм байна. Stack класс байхгүй байна гэнэ. Үүсгэчие.
Хиранабэ: Eclipse дээр амархан үүсгэдэг гэж сонссон юм байна.
Китано: За байз. Compile error гарсан газар x гэснийг click 
хийвэл Stack классыг үүсгэх гэдэг менью гарч ирнэ*. 
* Eclipse-н энэ функцыг Quick Fix гэдэг.
Хиранабэ: Хо. Үнэхээр ашиглахад хялбар юм гээ ч.
Китано: Stack классын template бэлэн болчлоо.

 public class Stack {  
 

Хиранабэ: Дахиад compile error гарчлаа. За байз, compiler isEmpty методыг үүсгэ гээд байна. Ойлголоо, ойлголоо (дурамжхан). Ёсоор болгоё. Энд compile error арилгахад л хангалттай хамгийн бага код бичих ёстой болохоор boolean төрлийн default утга false-г буцаачъя*.

* Яг үнэндээ бол эхний тестийг давуулахгүйн тулд зориуд false буцааж байгаа юм.

Китано: Compiler ч гэсэн программчлагч болж оролцож байгаа юм шиг санагдчихлаа. PP(2уулаа) биш triple (3уулаа) болчлоо (инээв)!! Тоглоом хийх ч яахав.  TDD гэдэг чинь compiler-н тусламжтай хөгжүүлдэг арга юм аа даа.
Хиранабэ: Тийм шүү. Compiler-н хэлж буйг сайн сонсох юм бол дараачийн хийх ёстой алхамыг зааж өгдөг юм. Бас Eclipse мэтийн IDE байвал хүрэлцэхгүй байгаа метод, классыг үүсгэ гэж шавдуулдаг болохоор найдаж болох эд шүү (орч: eclipse-г хэлж байна).  
public class Stack { 
 public boolean isEmpty(){ 
   return false; 
 } 
}

3. Нагахама(長浜)-Цүрүга(敦賀) өртөөний хооронд 
~Эхлээд улаан, дараа нь ногоон, тэгээд refactoring!!~

Хиранабэ: Ингээд compile error алга болчлоо. Одоо тестлэе.
Китано: Улаан* болчлоо.
* JUnit ашиглан тест явуулж байгаа тохиолдолд энэ нь тест амжилтгүй болсоныг илтгэнэ.
Хиранабэ: Яг төлөвлөсний дагуу улаан боллоо. Ингээд нэгдүгээр алхам дууслаа. Иймэрхүү маягаар TDD-д эхлээд улаан болгоод дараа нь ногоон* болгох замаар нэг нэгээр нь шалгаад явдаг юм.  
* JUnit ашиглан тест явуулж байгаа тохиолдолд энэ нь тест амжиллтай болсоныг илтгэнэ.
Китано: Аан. Ингэж улаанаас ногоон болгож байгаа нь тест cпек (test specification document) дээр тест давсан эсэхийг check-лээд явахдаа төстэй юм байна.
Хиранабэ: Үргэлж ногоон байгаад байвал үнэхээр тест зөв хийгдэж байгааг мэдэхгүй, аягүй бол андуураад өөр тест хийж байгаа юм биш байгаа гэсэн түгшүүр төрнө.
Китано: Тэгвэл TDD-г улаан ногоон сөөлжихөөр хийх ёстой юм биз дээ. Temp алдахгүй зүгээр юм байна.
Хиранабэ: Яг зөв. Гол үндэс нь "улаан", "ногоон", "refactoring" гэсэн 3 тулгуур ойлголт. "Refactoring" яаж хийх талаар хожим үзүүлэмз.
Хиранабэ: Үндсэн сэдэв рүүгээ эргэж ороод, тест давах хамгийн бага кодыг бичих үү дээ?. 
Китано: Ммм... stack хоосон болохоор ... size гэх мэтийн хувьсагч ашиглаад ...
Хиранабэ: Юу? Тэрээ хүртлээ бодоод байгаа юм уу? Зүгээр л true буцаачихвал болоо биш үү?
Китано: A, тэгж болох юм уу?

public class Stack { 
 public boolean isEmpty(){ 
   return true; 
 }
}

Хиранабэ: Тестийг давах нь л одоогийн зорилго болохоор. Ирээдүйн талаар бодох хэрэггүй.
Хиранабэ: Ингэж энгийнээр кодлох ажиллагааг TDD-д оромдох-аргалах (fake it) гэдэг. 
Китано: Аан.
Хиранабэ: Тестлээд үзье.
Китано: Ногоон болчлоо. Ингээд 2-р алхам болчив уу? Гуравдугаар алхам refactoring хийх үү?
Хиранабэ: Үгүй. Одоохондоо давхардсан код байхгүй болохоор refactoring хэрэггүй. Бусад спекийг кодлочоод, шаардлага гарвал isEmpty()-г refactoring хийе.
Китано: За, ойлголоо. Тэгвэл push()-н тестийг хийж үзье тэгэх үү?
Хиранабэ: OK. push()-н тестийг top()-г ашиглаад хийж үзэх үү? Дахиад 1-р алхамаас эхлээд улаан төлвийг үүсгэе.
Китано: Методын нэрийг testPushAndTop() гэе.
Хиранабэ: Ойлголоо. assertEquals-г эхлээд бичье ...

import junit.framework.TestCase; 

pulibc class StackTest extends TestCase {
 public void testCreate() { 
   Stack stack = new Stack(); 
   assertTrue(stack.isEmpty()); 
 }
 public void testPushAndTop() { 
   Stack stack = new Stack(); 
   assertEquals(1, stack.top()); 
 }



Китано: Stack-н instance (хувь: санах ойд үүссэн объект) үүсгэж буй хэсгийн код давхардаж байна.
Хиранабэ: setup() метод руу оруулчих уу?. setup() бүх testXXX()-н өмнө дуудагддаг юм. ОК биз?
Китано: Тэгвэл testPushAndTop-г тайлбар болгоод, testCreate-г setUp методыг ашиглаад тестлээдэх үү?

 Stack stack; 
 public void setUp() { 
   stack = new Stack(); 
 } 
 public void testCreate() { 
   assertTrue(stack.isEmpty()); 
 } 
/* 
 public void testPushAndTop() { 
   Stack stack = new Stack(); 
   assertEquals(1, stack.top()); 
 } 

*/

Хиранабэ: Болж байна уу?
Китано: Болж байна. Тестлэе.
Хиранабэ: Ногоон хэвэндээ байна. testPushAndTop-г ч гэсэн тэгчих үү?
Китано: Тайлбараас гаргаад, а, push-г мартсан байна.
Хиранабэ: Өө, тийм байна. Хүлээж буй утга (expected value) маань 1 болохоор 1-г push хийх нь.

public void testPushAndTop() { 
  Stack stack = new Stack(); 
  stack.push(1) 
  assertEquals(1, stack.top()); 

}

Китано: Ингээд тест код бэлэн болчихлоо. Энэ чигээрээ бол compile error гарах болохоор, юуны өмнө compile error гарахааргүй болгочъё.
Хиранабэ: Эхлээд, push метод. Буцаах утга нь void болохоор юу ч бичилгүй орхичихъё. Дараачийнх нь top метод. return 0 гээд демо* хийчье.

* Oрч: программын бүрэн төгс болоогүй анхны хувилбар

 public void push(int value) { 
 } 

 public int top() { 
   return 0; 
 }

Китано: Ингээд compile error гарахааргүй боллоо. Энэ хэвээр нь ажиллуулбал улаарна. Тестлэж үзэх үү?  
Хиранабэ: Тестээ ажилууллаа. Улаан боллоо. Хүссэн ёсоор тест амжилгүй болов. Нэгдүгээр алхам дууслаа.
Китано: Тэгвэл, тест амжилттай болтол нь засъя. Одоо top методын буцаах утгыг 1 болгоно гэсэн үг үү?
Хиранабэ: Яг зөв. Fake хийнэ.

  public int top() { 
    return 1; 
  } 

Хиранабэ: Тестээ ажиллуулъя. Ногоорлоо. Төлөвлөсөн ёсоор тест амжиллтай болов. Хоёрдугаар алхам дууслаа. 
Китано: За, refactoring хийх үү?
Хиранабэ: Тэгье. push методоор оруулсан утгыг top методоор буцаахаар өөрчилье.

  private int value;

  public void push(int value) { 
    this.value = value; 
  } 
  public int top() { 
    return value; 
  }

Хиранабэ: Ингэчвэл яаж байна?
Китано: Болж байна. Тестлэж үзье. Ногоорох ёстой доо.
Хиранабэ: Тестээ ажилууллаа. Ногоон боллоо. Refactoring амжилттай. Гуравдугаар алхам болчлоо. Одоохондоо үүнээс илүү refactoring хийх боломжгүй тул дараачийн ажилдаа оръё.
Китано: Одоо size метод-г хийвэл яаж байна? 
Хиранабэ: OK. Push хийвэл size өсөх нь үү гэдэг тест бичье.
Китано: testPushAndSize() гэж нэрлэвэл зүгээр.
Хиранабэ: За. assertEquals-г эхлэж бичээд ... Энэ болж байна уу?

 public void testPushAndSize() { 
   s.push(1); 
   assertEquals(1, s.size()); 
 }

Китано: OK. Compile error гарахааргүй болгочих уу? size методыг return 0 болгочихвол болох нь ээ?
Хиранабэ: Тийм байна.

 public int size() { 
   return 0; 
 } 

Китано: Тестлээд үзье.
Хиранабэ: Run Test! Улаанаар эргэчихлээ. size()тест давсангүй гэсэн үг байх нь ээ.
Китано: Тэгвэл тест давж чадахаар болгоё.
Хиранабэ: size()-н буцаах утгыг нэг болгоё. Тэгээд тест... Ногоорчлоо.

 public int size() { 
   return 1; 
 } 

Хиранабэ: TDD-н 3 алхамыг эргээд нэг саная. Энэ 3 алхамыг үргэлж санаж явах юм шүү!


АлхамЗорилгоJUnit дэх өнгө
1
Тест бичихУлаан
2
КодлохНогоон
3
RefactoringНогоон



4.Цүрүга өртөө (敦賀駅) 
~ Triangulation-Гурвалжлах??~ 

Хиранабэ: Удалгүй Цүрүга өртөөнд хүрэх нь ээ.

Хиранабэ: Одоо жаахан аргаа өөрчлөөд дахиад нэг тест нэмье testPushAndSize()-д .
Китано: Яана гэсэн үг үү?
Хиранабэ: Ингэнэ гэсэн үг.

public void testPushAndSize() {
  s.push(1);
  assertEquals(1, s.size());
  s.push(2);
  assertEquals(2, s.size());
}

Хиранабэ: 2 удаа push хийхээр size нь 2 болно байх аа? Үүнийг шалгах тестийг нэмээд энэ тест, өмнөх тестийн аль алиныг нь давах хамгийн хялбар кодыг бодох учиртай юм. Дахиад улаан төлөвт оруулж байгаад, үргэлжлүүлдэг (код нэмэх, засах) нь TDD-н алтан дүрэм билээ. 

Китано: Тийм үү? Тэгвэл илүү зөв кодыг гаргаж авч чадах нь ээ. Ер анзаардаггүй явсанаас биш, би үргэлж иймэрхий маягаар кодлодог юм шиг санагдаад явчихлаа.
Хиранабэ: За, эхлээд энэ тестийг улаанаар эргэх нь үү шалгая.
Китано: За. Улайчлаа.
Хиранабэ: За байз, яаж энэ 2 тестийг 2ууланг нь зэрэг давах вэ? Китано чи байсан бол яаж хийх үү?
Китано: Size-н утгыг хадгалах instance хувьсагч Stack class-д нэмээд push()-г дуудах болгонд нэгээр нэмэгдүүлнэ. size() тэр утгыг буцаахаар засвал болно гэж бодож байна.
Хиранабэ: Санал нэг байна. Чи driver болох уу?
Китано: За.

public class Stack { 
  private int size; 

  public void push(int value) { 
     this.value = value; 
     ++size; 
  }

  public int size() {
    return size; 
  }
}

Китано: Энэ ямар уу?
Хиранабэ: За, compile хийгээд, тестлэе.
Китано: Run test! Ногоон болчлоо.
Хиранабэ: Амжиллттай боллоо. Энэ аргыг гурвалжлах (Triangulation) гэдэг. Хэд хэдэн test case бэлдээд, бүгдийг нь давах код бичсэнээр ерөнхий тохиолдлын шийдийг мөрдөж гаргах арга юм. Нэг тест (тухайн тохиолдол) бол fake хийгээд болчино. Үүнийг шинэ тест нэмж fake хийх (залилах) боломжгүй болгох замаар зөв хариуг мөрдөж гаргадаг. Гурвалжин хэмжилтийн арга шиг [нэг шулуун дээр орших] 2 цэгээс шулуун татаж тэдгээрийн огтлолцлоор ерөнхий шийд олдог TDD-н нэг pattern (загвар) юм.


Хиранабэ: За, энэ аргыг хэрэглээд одоо болтол fake хийсэн чигээрээ байгаа хэсэгт тест нэмээд явах уу? Анхаарал татсан газар байна уу?
Китано: Байна. isEmpty()-н [үргэлж] return true хийж байгаа нь анхаарал татаад байна. Яаж ийж байгаад засчмаар байна. Тийм учраас testPushAndTop() дотор push хийсний дараа isEmpty() false буцаана гэдгийг шалгамаар байна.

public void testPushAndTop() { 
  stack.push(1); 
  assertFalse(stack.isEmpty()); 
  assertEquals(1, stack.top()); 

Китано: Ийм болно.
Хиранабэ: Хо, сайн байна. Энэ ч гэсэн triangulation байна шүү дээ.
Китано: Тестээ ажиллуулаад улаан болж байгаа эсэхийг шалгаад яаж засахаа бодъё.
Хиранабэ: Яаж хялбар болгох вэ?

public boolean isEmpty() {
 return size == 0;
}

Китано: Ийм байх болов уу. size 0 утгатай бол true буцаах. 
Хиранабэ: ОК. Тестлээд үзье.
Китано:  Run Test! Ногоорчлоо.



5. Хокүрикү (北陸)-гийн хонгил ! Иччораи*! 
~Exception тест~ 


Китано: Дараачийн шинэ тестийг нэмэх үү?
Хиранабэ: Юуг тестлэх үү?
Китано: Pop-н тестийг хиймээр байна. Юуны өмнө огт push хийгээгүй үеийн pop-н тестийг нэмье.
Хиранабэ: Аа, тэр тохиололд EmptyStackException чулуудагдах нь уу гэдгийг шалгахгүй бол болохгүй нь ээ. Энээ хүртэлхээс жаахан өөр маягийн тест болох нь байна.
Китано: Тийм байна. Сонирхолтой тест болохоор хийж үзмээр байсан юм. Иймэрхүү байвал ямар уу? 

public void testEmptyPop() { 

  try { 
    stack.pop(); 
    fail(); 
  } catch (EmptyStackException exception) { 
  } 

* Орч: Иччорай - Хамгийн сайн зүйл  гэсэн утгатай үг. Иччора (一張羅) гэдэг үгний Фүкүи муж хавийн нутгийн аялга ажээ. Сонирхуулахад Фүкүи хавийн шинэ үеийн ардын дуу "Дэлгэр цаг" (イッチョライ節) дууны эхний мөрөнд Фүкүи мужын бахархал болсон газруудын нэг болох Хокурикүгийн хонгил гэж гардаг байна. Уг дууг 1937 онд энэ хонгилын нээлтэнд зориулж зохиосон гэдэг.

Хиранабэ: Аанхаан. pop()-н дараа fail() дуудагдвал (exception үүсэхгүй бол) тест амжилтгүй болно. ОК. Тэгээд pop()-дээр
Exception чулуудагдах болохоор [чулуудагдна гэж найдаж буй Exception-гcatch хийж (барьж авч) буй. Ммм.
Китано: Яасан?
Хиранабэ: Catch хийж буй Exception-ий хувьсагчийн нэр exception биш expected (найдаж буй) байвал илуу ойлгомжтой юм биш үү? 
Китано: Аа, тийм байна. Ойлголоо.
Китано: За, compile хийгээдэхье. Compile error гарчлаа. pop() байхгүй байна гэж байгаа болохоор pop()-г нэмлээ.
Хиранабэ: За, одоохондоо зөвхөн compile error-г арилгая.

public void pop() { 
}

Китано: Ийм болох нь ээ. Тестлээд үзье. Улаан болох ёстой доо.
Хиранабэ: За, улаалаа. pop()-н дараа fail() дуудагдаад байгаа болохоор тэр.
Китано: Тест давах хамгийн бага код бичих гээд үзье.
Китаноpop() дотроос Exception чулуудвал болох нь ээ?

public void pop() { 
 throw new EmptyStackException(); 
}

Хиранабэ: За, болж байна. Ногоон болох нь уу шалгая.
Китано: Ногоорчлоо (алгаа ташив).
Хиранабэ: Энэ бол Exception чулуудаж буй хэсгийг тестлэх арга. 
Китано: Энэ хэсгийг бас гурвалжлах аргаар шалгая.
Хиранабэ: Нэг удаа push-лэсний хийсний дараа pop-лоход Exception чулуудагдахгүй гэдэг тестийг хийж үзье. Та яаж хийхийг мэдэх үү?
Хиранабэ: Мэднэ. Ийм [тест] болно.

public void testPushAndPop() { 
 stack.push(1); 
 stack.pop(); 
 assertEquals(0, stack.size()); 

Хиранабэ: Шалгая. Улайх ёстой.
Китано: Аанхаан. Pop үргэлж Exception чулуудаж байгаа болохоор, Exception үүсч амжилтгүй болно. Run Test! Улаан боллоо.
Хиранабэ: Тэгвэл pop-г ингээд өөрчилчье.

public void pop() { 
 if (isEmpty()) 
   throw new EmptyStackException(); 
}

Хиранабэ: isEmpty() true, ө.х size 0 бол EmptyStackException-г чулуудна. 
Китано: За. OK, болж байна. size == 0 гэх юм болов уу гэсэн чинь, isEmpty()-ээр болох юм байна (инээмсэглэв).
Хиранабэ: Тестлээд үзье. Ногоон болчлоо (алга ташив).
Китано: Байз? top ч гэсэн EmptyStackException-г чулуудаж байгаа юм байна.
Хиранабэ: Тийм бил үү? Нээрээ тийм. За top-н exception-г тестлэе.

public void testEmptyTop() { 
 try { 
   stack.top();
   fail(); 
 } catch (EmptyStackException exception) { 
 } 
}

Хиранабэ: Энэ зөв үү?
Китано: Аанхаан. Тестлэе.  top()-д exception чулуудах код байхгүй болохоор fail() дуудагдаад, тест амжилтгүй болох ёстой.
Хиранабэ: ОК. Run Test! Улаан аслаа.
Китано: Тэгвэл pop()-той адилханаар засчихъя.   
Хиранабэ: Тэгье.

public int top() {
 if (isEmpty())
   throw new EmptyStackException();
 return value;
}

* top(), pop() 2 дээр хийж буй шалгалтыг ерөнхийлөх (тусад нь гаргах) хэрэгтэй гэдгийг анзаарсан ч бай яаран засах хэрэггүй. Юуны өмнө ногоон болгоод, дараад нь refactoring хийх нь зөв хандлага (TDD) юм.

Хиранабэ: Нэг ажиллуулаад шалгачия. Ногоон аслаа.
Китано: Гэхдээ top(), pop() 2-н EmptyStackException чулуудаж буй хэсэг адилхан логиктой болчлоо. Refactoring хийе.
Хиранабэ: Энэ удаагийн refactoring-д ExtractMethod* ашиглая.

public int top() {
  emptyCheck(); 
  return value; 

public void pop() { 
  emptyCheck(); 

private void emptyCheck() {
  if (isEmpty())
    throw new EmptyStackException();
}

* Орч: Ерөнхий кодыг тусад нь метод болгож гаргахыг хэлж буй бололтой.

Хиранабэ: Үүгээр ногоон болчвол refactoring амжиллтай болжээ гэсэн үг.


6. Хонгилийн үзүүрт?!

~TDD Оргил хэсэг (Last Spurt) ~

Хиранабэ: Ингээд TDD зөв голидролдоо орж эхэллээ шүү! Аль хэдий нь Хокүрикүгийн хонгил ард хоцорчээ. Одоо яах ёстой билээ.
Китано: Pop-лохоор оройн элементийг устгах.
Хиранабэ: Тийм. Хийгээгүй үлдсэн байна. Тест бичээдэхье.
Хиранабэ: Push-лээд pop-лоход size нь 0 болно, энэ тест.

public void testPushAndPop() {
  s.push(1);
  s.pop();
  assertEquals(0, s.size());
}

Китано: Тестээ ажиллуулъя.
Хиранабэ: Улаан болох ёстой доо. Ажиллуулаад үзье. Улаанаар эргэчихлээ. 
Китано: pop хийчээд байхад size-н утга өөрчлөгдөхгүй болохоор тэр дээ.
Хиранабэ: За, энэ тестийг амжилттай болгох код бичье.

public void pop() { 
 emptyCheck(); 
 size--; 

Китано: pop дуудагдахад size-н утгыг нэгээр хорогдуулна. Ингээд болоо шив дээ.
Хиранабэ: За, ажиллуулъя.
Китано: Ногоон боллоо.
Хиранабэ: Одоо яах билээ?
Китано: За байз, стекийн гол онцлог болох түрүүлж орсоныг сүүлд нь гаргадаг савыг хийгээгүй байгаа гэж бодож байна ... Одоогоор нэг л төвшинтэй стек байгаа болохоор ...
Хиранабэ: За, энэ тестийг бичээд үзье.

public void testPushPushPopTop() { 
  s.push(1); 
  s.push(2); 
  assertEquals(2, stack.size()); 
  s.pop(); 
  assertEquals(1, stack.top()); 

Китано: 2 удаа push, 1 удаа поп хийсний дараа top хийвэл эхний удаагаар push хийсэн утга  буцна гэсэн тест болох нь.
Хиранабэ: За, яг энэ байдалтайгаар ямар болох нь уу нэг шалгая. Run Test!
Китано: Улаан болчлоо. Түрүүн хэлсэнээр 1 төвшинтэй болохоор тэр үү?
Хиранабэ: Тэгвэл, олон төвшинтэй болгое. Яаж хийх үү?
Китано: Харин ээ, int төрлийн массив ашиглая.
Хиранабэ: You Have!*
Китано: I Have!*

* You Have! - Чамд даатгалаа шүү! 
   Онгоцны нисгэгч туслах нисгэгчид жолоо шилжүүлэхэд хэрэглэдэг хэллэг. 
* I have! - Ойлголоо! 
  "You Have!"-н хариу.

private int[] value = new int[10]; 

public void push(int value) { 
 this.value[size++] = value; 

public int top() { 
 emptyCheck(); 
 return value[size - 1];
}

Китано: size хувьсагчийг массивын индекс болгож ашиглана. Болж байна уу?
Хиранабэ: OK.
Китано: Run test!
Хиранабэ: Ногоон боллоо.
Китано: Ингээд бүх ToDo жагсаалт хоосон боллоо*. Аль хэдий нь Сабаэг өнгөрчиж, удалгүй Фүкүи. 50 мин хэртээ болов уу? энэ TDD-н дадлага.
Хиранабэ: Тийм. Гоё боллоо. Refactoring дахин хийх шаардлагагүй ч боллоо. Гоё цэвэрхэн код (clean code) ч гаргаж авч чадлаа.
Китано: Бүгдийг тэмдэглэж аваад бусдадаа дамжуулмаар юм байна.
Хиранабэ: Тийм шүү.

* Орч: Driver программ бичиж байх хооронд navigator дараагийн алхамуудыг төлөвлөх ба түүнийг driver-т хэлж ToDo жагсаалтанд нэмүүлдэг. Хийж буй зүйлээ дуусангуут дараачийн алхамын талаар зөвлөлдөөд, жагсаалтаас дараачийн ажлыг сонгоно. Гүйцэтгэж дууссан алхамыг жагсаалтаас хасах замаар кодлох нь PP-н бас нэгэн сонгодог арга юм.

//////////// Stack.java ///////////////
import java.util.EmptyStackException;

/**
 * @author koji,hiranabe
 */
public class Stack {
private int[] value = new int[10];
private int size;

public boolean isEmpty() {
return size == 0;
}

public int top() {
emptyCheck();
return value[size - 1];
}

public void push(int value) {
this.value[size++] = value;
}

public int size() {
return size;
}

public void pop() {
emptyCheck();
--size;
}

private void emptyCheck() {
if (isEmpty())
throw new EmptyStackException();
}
}

/////////////// StackTest.java /////////////
import java.util.EmptyStackException;
import junit.framework.TestCase;

/**
 * @author koji
 */
public class StackTest extends TestCase {

private Stack stack;

protected void setUp() {
stack = new Stack();
}

public void testCreate() {
assertTrue(stack.isEmpty());
}

public void testPushAndTop() {
stack.push(1);
assertFalse(stack.isEmpty());
assertEquals(1, stack.top());
stack.push(2);
assertEquals(2, stack.top());
}

public void testPushAndSize() {
stack.push(1);
assertEquals(1, stack.size());
stack.push(2);
assertEquals(2, stack.size());
}

public void testEmptyPop() {
try {
stack.pop();
fail();
} catch (EmptyStackException expected) {
}
}

public void testPushAndPop() {
stack.push(1);
stack.pop();
assertEquals(0, stack.size());
}

public void testPushPushPopTop() {
stack.push(1);
stack.push(2);
assertEquals(2, stack.size());
stack.pop();
assertEquals(1, stack.top());
}

public void testEmptyTop() {
try {
stack.top();
fail();
} catch (EmptyStackException expected) {
}
}
}


Дүгнэлт

Китано: Сонирхолтой байлаа.
Хиранабэ: Юу нь таалагдав?
Китано: Код нэмэх, өөрчлөх болгонд тест код нэмэгдэж байгаа нь.
Хиранабэ: Аанхаан. Тэгсэнээр бэлэн болсон код тэр чигээрээ тестлэгдсэн
төлөвт орж байгаа юм. 
Китано: Бичсэн кодондоо итгэлтэй болох юм билээ. Бас кодлох үед агуулга гэхээсээ илүү интерфейст анхааралаа хандуулах явдал.
Хиранабэ: Яг зөв. Ихэнхи тохиолдол зохиомжилчоод код бичдэг болохоор ихэнхи хүмүүс классын интерфейсийг хэрэгжүүлэгч (дотор=доторхи алгоритм) талаас хараад байдаг. Тест эхлэж бичсэнээр тэр классыг ашиглагчийн байр суурьнаас (гадна талаас нь) харж эхлэдэг зуршилтай болдог. Өөр?
Китано: ?
Хиранабэ:  Хамгийн чухал нь классын cпек тестлэх боломжтой спек болж хувирах явдал. Заримдаа тестлэх боломжгүй класс зохиох явдал тохиолддог. Харин XP-д энэ нь байж боломгүй үзэгдэл юм. Заавал тестлэгдэхгүй бол болохгүй. TDD-г хэрэгжүүлбэл тестлэгдэлт (testability-тестлэх боломж) -г үргэлж хангаж чадна.
Китано: Нээрээ тийм байна. Бас "улаан>ногоон>refactoring" нь хөгжүүлэлтийг ритмтэй болгоод, ямар ч байсан зугаатай юм.
Хиранабэ: Good Point. За, нэг дүгнээдэхье.

TDD-н өгөөж:
  • Бүх код тестлэгднэ.
  • Хэрэгжүүлэлт (алгоритм)-с илүү хэрэглэгч талын интерфейсийг чухалчилсан зохиомжтой болно.
  • Тестлэх боломж өндөртэй зохиомж бий болно.
  • Хөгжүүлэлт ритмтэй, зовлонгүй болно.
Китано: Хэрэгжүүлж үзээд жаахан залхуутай санагдлаа. Үргэлж fake хийгээд зүгшрүүлээд явах юм уу?
Хиранабэ: Тухайн нөхцөл байдлаас болно. Жишээ нь нэг мөрөөр бичиж болох кодыг fake хийх шаардлага байна уу? гэвэл хийсэн ч болно, хийхгүй байсан ч болно. Шууд эцсийн шийдийг бичих тохиолдол байна. Үүнийг тодорхой кодлолт (Obvious Implementation) гэнэ. Өөртэй итгэлтэй байгаа тохиолдолд тест код бичээд шууд эцсийн шийдийг бичих гэсэн зөвхөн улаан-ногооны хослолоор хийж болно. Харин өөртөө итгэлгүй байгаа бол оромдоод (fake), гурвалжлаад (triangulation), засалт (refactoring) хийгээд зүгшрүүлээд явах хэрэгтэй.
Энэ 2 аргыг солбиж хэрэглэх тухайд, хамтрагчдын өөртөө итгэх итгэл, чадвар, тухайн үеийн нөхцөл байдлаас хамаарна. Нойр дутуу ч юм уу толгой сэргэг биш үед бага багаар оромдож хийх нь ритмтэй болгох сайн тал бий.
Хиранабэ: Дахиад дээрх 3 аргыг нэгтгээд дүгнэе. Ж нь: int add(int a, int b) гэсэн метод бичмээр байлаа гэж саная. Зөв хариу нь return a + b; байг. Тэгвэл 3 арга байна. Эхлээд assertEquals(3, add(1, 2)); гэж тест код бичээд улаан болгоно. Эндээс 3 янзын аргаар цааш нь үргэлжлүүлж болно.
  1. Fake=>Refactoring                                                            Эхлээд return 3; гэж ногоон болгочоод, return a+b; 
  2. Fake=>Triangulation=>Refactoring                                   Эхлээд return 3; гэж кодлоод ногоон болгоно. Дараа нь assertTrue(4, add(3, 1)); гэж тестлээд, улаан болгоно. Хамгийн сүүлд нь return a + b;
  3. Тодорхой кодлолт ()                                                                         Шууд л return a + b;
Зургаар үзүүлбэл нэг иймэрхүү:

Хиранабэ: Бас нэг зүйл байна. Энд нэг өгөгдлийг танилцуулъя. TDD-г хэрэгжүүлсэн, хэрэгжүүлээгүй үеийн кодны чанарын ялгааны талаар ийм нэгэн өгөгдөл байна.
Хиранабэ: Энэ графикийг хар даа.
Хиранабэ: Энэ бол TDD болон ердийн хөгжүүлэлтийн харьцуулалт. Laurie Williams, Body George 2-н Хойд Каролинагийн их сургууль дээр явуулсан харьцуулсан туршилт. Боулингийн оноо боддог программыг 150 оюутнаар, хосоор программчлах аргаар хийлгэжээ. Программыг хөгжүүлэх хугацаа ердөө 75-хан минут байсан байна.
Китано: Энэ графикийг яаж унших ёстой вэ?
Хиранабэ: Уг туршилтаар 7 хүлээн авах тест* (acceptance test) бэлтгэсэн байсан бөгөөд эдгээр тестээс хэдийг нь амжилттай давсан эсэхийг хэвтээ тэнхлэгээр, түүнд харгалзах хувийг босоо тэнхлэгээр харуулжээ. 
Китано: Олон тестийг амжилттай давсан багийн дотроос TDD-р хөгжүүлэлт хийсэн багийн эзлэх хувь илт давуу байна шүү.
Хиранабэ: Тийм. Эндээс TDD-н ашигтай, сайн талыг ойлгосон байх гэж найдаж байна.
Хиранабэ: Ингэсгээд харихгүй бол ...
Китано: За тэгье. Баяртай.
Хиранабэ: Баяртай.

*Acceptance test: Захиалагч тал программыг хүлээн авах үед хийдэг тест

Жинхэнэ эхийг эндээс уншина уу! 
http://objectclub.jp/technicaldoc/testing/stack_tdd.pdf


Орчуулсан: Сарту Рэнцэн Батзоригт