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

0 件のコメント:

コメントを投稿