Mocks Aren’t Stubs (орчуулга)

Агуулга:

  • Ердийн тестүүд
  • Дууриамал объектуудтай тестүүд
    • EasyMock ашиглах
  • Дууриамал объектууд (Mock) болон (Stubs) хэлтэрхий объектуудын ялгаа
  • Сонгодог болон дууриамал тестийн загвар
  • Ялгаануудын дундаас сонгох
    • TDD -р хөтлөгдөх
    • Тестийн өгөгдлийг тодорхойлох
    • Тестийн тусгаарлалт
    • Тестээ хэрэгжүүлэлтэйгээ холбох
    • Загварчлалын төрөл
  • Тэгэхээр би сонгодог уу эсвэл дууриамал аргаар тестлэгч үү
  • Төгсгөлийн бодлууд

 

“Дууриамал объект” гэж жинхэнэ объектуудыг тестлэхийн тулд ашигладаг тусгай оъектуудыг нэрлэдэг бөгөөд нилээдгүй дэлгэрсэн нэршил юм. Ихэнх програмчлалын хэлний орчингууд одоо дууриамал объектуудыг хялбар үүсгэж болох өөр өөрсдийн гэсэн тогтолцоотой болцгоосон. Гэв ихэвчлэн хангалттай ойлгомжтой байдаггүй нэг зүйл нь юу вэ гэхлээр хэдийгээр дууриамал объект нь тестлэх зорилгоор ашиглагддаг нэг хэлбэрийн тусгай объект боловч, мөн нөгөөтэйгүүр тестлэх өөр нэг загварчлалын аргыг бий болгосон байдаг. Энэхүү нийтлэлээр би та бүхэнд дууриамал объект хэрхэн ашиглагддаг, үйлдэл шалгах явцыг (behavior verification) хэрхэн дэмждэг болон мөн хөгжүүлж, дэмжиж буй бүлгэмийн залуус хэрхэн өөр нэг тестлэх загварыг хөгжүүлж байгаа талаар тайлбарлах болно.

 

Би энэхүү “дууриамал объект” гэх нэршлийг анх хэдэн жилийн өмнө Extreme Programming (XP) бүлгэмийн нөхдүүдээс дуулж билээ. Түүнээс хойш энэхүү дууриамал объектууд миний сонирхлыг улам ихээр татах болсон. Нэг шалтгаан юу гэхлээр дууриамал объектуудын хөгжүүлэлтийг авч явж байсан нилээдгүй хөгжүүлэгчид надтай хамт ThoughtWorks-д хамт ажиллаж байсантай холбоотой байх. Нөгөө нэг шалтгаан нь юу гэвэл би энэхүү ойлголтыг XP -ын нөлөөлөлтэй тестлэх аргад тулгуурласан зохиол бүтээлүүдээс их харж байсантай ч холбоо байх.

Гэвч би энэхүү дууриамал объектуудын талаар муу тайлбарласан зүйлүүдтэй олонтаа таардаг. Ялангуяа хүмүүс эдгээр дууримал объектуудыг хэлтэрхий (stub) тай хольж хутгаад байдгыг би мэднэ. Яагаад хольж хутгаад байдгыг нь ч би сайн мэднэ – учир нь би өөрөө ч нэг хэсэг мөн тэгж харж байлаа. Харин тэдгээр дууриамал объектын хөгжүүлэгч нартай хийсэн ярилцлагууд намайг өчүүхэн ойлголтоос минь бүр цөм рүү нэвтрэн орж, нарийн мэдэхэд минь их тус болсон.

>Тайлбар:
Stub – тестэнд объектын зөвхөн зарим методын үйлдлийг дууриалгах зорилгоор ашиглагддаг тул хэлтэрхий объект гэж нэрлэсэн

Энэ ялгаа нь үнэндээ бол хоёр тусдаа ялгаатай зүйлүүд байгаа юм. Нэг талдаа тестийн үр дүнг хэрхэн батлах аргуудад ялгаа бий: объектын төлвийг батлах (state verification) болон объект хоорондын харьцаж буй үйлдлийг батлах (behavior verification). Нөгөөтэйгүүр тестлэх, загварчлах хоёрын хоорондоо хэрхэн нөлөөлж байгаа арга барил философи -д нь ялгаа бий. Энэ ялгааг би доор тайлбарлахдаа Тестээр хөтлөгдөх хөгжүүлэлт (Test Driven Development) -ын сонгодог болон дууриамал аргууд гэж нэрлэсэн болно.

 

Ердийн тестүүд

Хоёр төрлийн аргыг энгийн жишээгээр тайлбарлаж эхэлье. (Жишээнүүд Жава хэл дээр бичигдсэн болохоос зарчмууд нь бусад бүх объект хандалтад програмчлалын хэл дээр ялгаагүй боломжтой.) Бидэнд Захиалга (Order) объект байгаа бөгөөд түүнийг Агуулах (Warehouse) объектоос дүүргэлт хийгддэг гэж үзье. Захиалга (Order) объект маш энгийн энгийн зөвхөн бараа (product) болон бүтээгдэхүүний тоо (quantity) хоёроос бүрддэг. Харин Агуулах (Warehouse) объект олон төрлийн бараануудын тооллого бүртгэлийг (inventory) агуулна. Бид Захиалга объектыг Агуулах -с өөрийгөө дүүргэ гэж хэлэх юм бол, бидэнд хоёр төрлийн хариулт өгөх боломжтой. Хэрэв агуулахад (Warehouse) байгаа бараа захиалга (Order) хийгдэхэд хангалттай бол, захиалга хийгдэж, агуулахын барааны дүн захиалсан барааны тоогоор буурна. Хэрэв агуулахад хангалттай бараа байхгүй бол, захиалга хийгдэхгүй ба агуулахад ямар ч өөрчлөлт орохгүй.

Эдгээр хоёр үйлдлүүд нилээдгүй хэдэн тестүүд дээр хийгдэх ба эдгээр нь доорх ердийн JUnit тестүүдтэй төстэй байх болов уу.


public class OrderStateTester extends TestCase {
  private static String TALISKER = "Talisker";
  private static String HIGHLAND_PARK = "Highland Park";
  private Warehouse warehouse = new WarehouseImpl();

  protected void setUp() throws Exception {
    warehouse.add(TALISKER, 50);
    warehouse.add(HIGHLAND_PARK, 25);
  }
  public void testOrderIsFilledIfEnoughInWarehouse() {
    Order order = new Order(TALISKER, 50);
    order.fill(warehouse);
    assertTrue(order.isFilled());
    assertEquals(0, warehouse.getInventory(TALISKER));
  }
  public void testOrderDoesNotRemoveIfNotEnough() {
    Order order = new Order(TALISKER, 51);
    order.fill(warehouse);
    assertFalse(order.isFilled());
    assertEquals(50, warehouse.getInventory(TALISKER));
  }

xUnit тестүүд дараах дөрвөн төрлийн үе шатыг (phase) хийж гүйцэтгэдэг: setup, exercise, verify, teardown. Дээрх кодонд setup үе шат нь хагас нь setUp метод-д (Агуулах – warehouse –  объектыг байгуулахад), үлдсэн тал нь тест метод дотор (захиалга – order –  объектыг байгуулахад) хийгдсэн байна. order.fill гэж дуудаж байгаа нь exercise үе шат. Энэ үед бидний теслэх гээд хүсээд байгаа зүйлсийг маань объект гүйцэтгэх ёстой. assert (нотлох) шалгалтын зааврууд бол verification үе шат. Энд хэрэгжүүлсэн (exercised) метод шаардлагатай даалгаруудыг зөв гүйцэтгэсэн эсэхийг шалгана. Дээрх жишээн дээр tearDown үе шат яг тодорхой илэрхийлэгдээгүй ба garbage collector тэдгээрийг бидний өмнөөс нүдэнд харагдахгүй байдлаар хийж гүйцэтгэж байгаа.

setup үед бид 2 төрлийн объектыг үүсгэж байгаа. Order бол бидний тестлэж буй класс харин Order.fill -ыг ажиллуулахын тулд бидэнд Warehouse объект мөн хэрэг болж байгаа. Дээрхд дахин тодруулах нэг зүйл гэвэл: Order объект бол бидний тестлэх гэж анхаарлаа хандуулаад байгаа объект юм. Тестээр дагнаж мэргэшсэн хүмүүс үүнийг object-under-test, system-under-test гэх мэтээр нэрлэдэг. Хоёул хэлэхэд төвөгтэй, онцгүй нэрнүүд, хэдийгээр тийм ч олонд энэ чигээрээ илүү танигдсан нэршлүүд тул би мөн энэ чигээр нь ашиглаад явъя. Meszaros дагуу би System Under Test юмуу эсвэл бүүр товчлоод SUT гэх байдлаар тэмдэглээд явна.

Дээрх тестэнд надад нэг SUT (Order), нэг хамтрагч – collaborator – (warehouse) хэрэг болно. Хоёр шалтгааны улмаас надаа warehouse хэрэг болж байгаа: эхнийх нь тестлэгдэж буй бүхий л үйлдлүүдыг ямар ч асуудалгүйгээр ажиллуулахын тулд (Order.fill метод warehouse-ын методуудыг дуудаж буйгаа тул), хоёр дугаарт төлвийн баталгааг хийхийн тулд (Order.fill –с гарах аль нэг үр дүн warehouse -ын төлвийг өөрчлөх боломжтой учираас). Бид цаашид энэ сэдвийн талаар илүү судлаад, SUT болон хамтрагчуудын (collaborators) хоорондын олон зөрүүтэй ялгаа гаргаад ирэхээр та илүү олгох болно. (Өмнө нь би SUT -ыг “гол объект”, хамтрагчуудыг “хоёрдогч объект” гэх мэтээр нэрлэдэг байсан)

Энэхүү тестийн загвар нь төлөв батлах (state verification) арга юм. Юу гэсэн үг вэ гэхээр метод дуудагдаж, гүйцэтгэгдсэний дараа SUT болон хамтрагч нарын төлвийг шалгаж тэдгээр шалгагдаж буй методууд зөв ажилласан эсэхийг тодорхойлно. Дууриамал (Mock) объект шалгалтын өөр нэг боломжийг олгодогыг бид удахгүй харна болно.

Дууриамал (Mock) объектуудтай тестүүд

Одоо би дээрхтэй ижил үйлдлүүдтэй боловч дууриамал объекттой тест бичье. Энэ кодонд би jMock санг ашиглан дууриамал объектуудыг тодорхойлно. jMock бол жава хэлний дууриамал объект-ын сан. Хэдийгээр өөр нилээдгүй хэдэн дууриамал объектын сангууд байдаг хэдий ч, энэ сан нь тэрхүү техникийг үүсгэгчдийн өөсрдийх нь бичсэн сан тул анхлан ашиглахад тохиромжтой.

public class OrderInteractionTester extends MockObjectTestCase {
 private static String TALISKER = “Talisker”;

 public void testFillingRemovesInventoryIfInStock() {
   //setup – data
   Order order = new Order(TALISKER, 50);
   Mock warehouseMock = new Mock(Warehouse.class);
   
   //setup – expectations
   warehouseMock.expects(once()).method(“hasInventory”)
     .with(eq(TALISKER),eq(50))
     .will(returnValue(true));
   warehouseMock.expects(once()).method(“remove”)
     .with(eq(TALISKER), eq(50))
     .after(“hasInventory”);

   //exercise
   order.fill((Warehouse) warehouseMock.proxy());
   
   //verify
   warehouseMock.verify();
   assertTrue(order.isFilled());
 }

 public void testFillingDoesNotRemoveIfNotEnoughInStock() {
   Order order = new Order(TALISKER, 51);    
   Mock warehouse = mock(Warehouse.class);
     
   warehouse.expects(once()).method(“hasInventory”)
     .withAnyArguments()
     .will(returnValue(false));

   order.fill((Warehouse) warehouse.proxy());

   assertFalse(order.isFilled());
 }

 

Доор гарах кодыг би нилээн хөнгөвчилсөн тул одоо цөмөөрөө testFillingRemovesInventoryIfInStock метод руу анхаарлаа хандуулцгаая.

Эхлэл хэсэгт, setup үе шат нь шал өөр байна. Эхний объектыг бүрдүүлэгч хэсэг маань 2 үндсэн хэсэгт хуваагдсан байна: өгөгдлөл (data) болон объектоос хүлээх амлалт(expectations). Өгөгдлийн (data) хэсэг бидний тестлэх гээд сонирхоод байгаа объектуудыг үүсгэж байна (уламжлалт аргатай ялгаагүй адилханаар). Ялгаа нь үүссэн объектууд нь байгаа. SUT нь ялгаагүй – захиалга (order) объект. Харин хамтрагч нь агуулах (warehouse) объект биш байгаа, түүний оронд дууриамал агуулах (warehouse) объект байна – яг тодорхой авч үзвэл Mock классын объект байгаа.

setup -ын хоёр дахь хэсэгт, дууриамал объект-с хүлээх амлалт (expectations) хийгдэж байна. Хүлээх амлалт нь SUT-г гүйцэтгэх үед дууриамал объектын аль метод нь дуудагдах ёстойг илэрхийлж байгаа.

Бүх хүлээх амлалтууд тодорхой болсны дараа би SUT-ыг ажилуулж, гүйцэтгэнэ. Гүйцэтгэл хийгдсэний дараа би хоёр төрлийн ойлголттой батлах үе шатыг хийж гүйцэтгэнэ: Assert-уудыг SUT -тэй жишиж шалгана (өмнөхтэй яг ижил). Мөн би дууриамал объектуудыг хүлээх амлалтуудынхаа дагуу дуудагдсан эхийг шалгана.

Хамгийн гол ялгаа нь бидний хэрхэн захиалга (order) объект агуулах (warehouse) объекттой харилцахдаа зөв үйлдлүүд хийж байгааг шалгахад л оршиж байгаа юм. Төлвөөр батлах аргаар бол бид агуулах объектын байгаа төлөвтэй жишиж шалгана. Дууриамал объект үйлдлээр батлах (behavior verification) аргаар буюу захиалга (order) объект агуулах (warehouse) объект руу хандахдаа зөв дуудсан эсэхийг шалгадаг: Ингэж шалгахын тулд бид дууриамал объектод юу хийгдэхийг нь setup үед хэлж, шалгах үед нь тэрхүү амлалтууд нь хийгдсэн эхийг нь асууж шалгадаг. Захиалга (order) объект л зөвхөн assert-уудтай жишигдэж шалгагдах ба хэрэв метод объектын төлвийг өөрчилдөггүй бол бидэнд ямар ч assert хэрэг болохгүй.

Хоёр дахь тестэнд би хэд хэдэн зүйлүүдийг өөрөөр хийсэн. Эхлээд би дууриамал объектыг өөрөөр үүсгэсэн, үүсгэгчийн (constructor) MockObjectTestCase-ын mock методыг ашигласан. Энэ метод нь jMock сан дахь хялбарчилсан метод бөгөөд надаа заавал тестийн сүүлд verify -ыг дуудаж шалгах шаардлагагүйгээр, автоматаар өөрөө шалгагдчихна. Би эхний тестэнд мөн ингэж шалгаж болох байсан ч дууриамал объектын шалгалт хэрхэн явагддагыг харуулах үүднээс зориудаар ашигласан болно.
Хоёр дахь тэстэн дэх дараагын нэг өөр зүйл юу гэвэл би хүлээх амлалт дахь (expectation) хязгаарлалтыг (constraints) бууруулж withAnyArguments -ыг ашигласан. Ингэсний шалгаан нь гэвэл эхний тестэнд агуулах (warehouse) -руу дамжуулагдах тоог шалгацан учираас үүнийг хоёр дахь тестэнд дахин давтаад байх шаардлагагүй учираас юм. Хэрэв логикийн дараалал нь өөрчлөгдөх болбол, зөвхөн нэг л тест амжилтгүй болно. Энэ тестүүдийн шилжилт өөрчлөлтүүдийг хялбар болгодог. Сан нь анхдагч байдлаараа withAnyArguments -ыг тодорхойлцон байдаг болохоор би энэ хэсгийг тэр чигээр нь аваад хаясан ч болно.

Mocks болон Stubs объектуудын ялгаа

Анх эдгээр ойлголтууд гарч ирэхэд, олон хүмүүс дууриамал объектыг энгийн тестийн ухагдахуун болох stubs-тай хольж хутган, төөрөлдөж байсан. Түүнээс хойш хүмүүс тэдгээрийн ялгааг илүү ойлгосон байх. Хэдийгээр тиймч, хүмүүсийн хэрхэн mock -ыг ашигладгийг мэдэхийн тулд моск болон бусад тест doubles-уудыг мэдэх нь чухал (“doubles” ? хэрэв энэ таньд шинэ ойлголт бол санаа зоволтгүй, хэдхэн мөрийн дараа бүгд ойлгомжтой болно).

Програмын зөвхөн нэг элемент дээр тухайн агшинд санаагаа сайн төвлөрүүлэн та тест хийж байгаа бол энэ бол олны хэлдгээр unit testing юм. Асуудал нь юу вэ гэхлээр таньд ганц нэгжийг (unit) -ыг тестлэхийн тулд ихэвчлэн бусад нэгжүүд шаардлагатай болдог – бидний дээрх жишээн дээр warehouse хэрэг болсон шиг.

Дээрх миний харуулсан хоёр янзын тестын аргад, эхний нь жинхэнэ агуулах (warehouse) объектыг ашиглаж, харин хоёр дахь нь дууриамал агуулах (warehouse) объектыг (мэдээж энэ бол жинхэнэ объект биш) тус тус ашигласан. Хэдийгээр дууриамал объектыг ашиглах нь жинхэнэ агуулах объектыг ашиглахгүй байх нэг хэлбэр боловч мөн өөр төрлийн жинхэнэ биш объектуудыг иймэрхүү тестэд ашиглах боломжтой.

Эдгээр зүйлүүдийг тайлбарлахад хэрэглэгдэх нэршлүүд нилээд ярвигтай болж ирж байна – stub, mock, fake, dummy гэх зэрэг нэр томъёонууд гарч ирлээ. Энэхүү нийтлэлд би Gerard Meszaros -ын номонд дурьдсан нэршлүүдийг ашиглах болно. Хэдийгээр эдгээр нэр томъёонуудыг бүгд хэрэглэдэггүй ч гэсэн миний бодлоор эдгээр нэршлүүд бол нилээд сайн, мөн энэ миний эссэ учраас би аль нэршлийг сонгохоо өөрөө шийднэ.

Meszaros Test Double гэсэн нэрэн дор тестэнд ашиглагдах, жинхэнэ объектын оронд төлөөлөн ашиглагдаж байгаа аливаа объектуудыг ерөнхийлөн ингэж нэрлэсэн. Энэ нэршил нь киноны орлон тоглогч (Stunt Double) -с үүдэлтэй (Түүний ингэс ашигласан нэг шалгаан  нь өмнө хэдийн олон танигдсан нэршлээс зайлсхийх байсан). Meszaros дөрвөн төрлийн double -ыг тодорхойлсон:

  • Dummy шаардагдах параметерүүдийн тоог бөглөхөд л ашиглагдах объект бөгөөд хэзээ ч ашиглагдахгүй
  • Fake ерөнхий бүх зүйл нь асуудалгүй ажилладаг авч зарим нэг хялбарчилсан зүйлүүдээс болоод Production орчинд ашиглагдах боломжгүй объектууд (жишээ нь in memory өгөгдлийн бааз)
  • Stubs тестийн үед хийгдэх дуудлагуудад зориулж бэлдсэн хариултуудууд өгдөг, тестэнд программчлагдсан зүйлүүд нь уг тестээс гаднах ямар ч зүйлд хариулдаггүй. Stubs мөн аливаа дуудлагуудын мэдээллийг хадгалж болно. Жишээ нь email gateway stub магадгүй илгээсэн имэйлүүдээ юмуу аль эсвэл зөвхөн илгээсэн имэйлын тоог санадаг байж болно.
  • Mocks бидний энэхүү илтгэлд яригдаж байгаа сэдэв: Объектын дуудагдах байдал нь урьдчилан програмчлагдсан ба тэр нь тестийн үед заавал дуудагдах шаардлага болдог

Эдгээр double-уудаас зөвхөн mock л үйлдлээр батлах (behavior verification)-ыг шаарддаг. Бусад double-ууд ихэвчлэн төлвөөр батлах (state verification) -ыг хэрэглэдэг. Дууриамал объект (Mocks) нь exercise – үед SUT-ыг жинхэнэ хамрагч объектуудтай харьцаж байгаа гэж итгүүлэхийн тулд хийдэг үйлдлээрээ бусад double-уудтай яг ижил үйлдэл хийдэг. Хэдий тийм ч дууриамал объект setup болон verification үеүүдэд бусдаасаа ялгаатай.

Test double-уудын талаар илүү дэлгэрэнгүй судлах үүднээс бид жишээгээ өргөжүүлэх шаардлагатай болж байна. Олон хүмүүс test double-уудыг жинхэнэ объектуудтай ажиллахад ядаргаатай болсон үедээ ашиглаад байдаг. Test double-ыг ашиглах нэгэн түгээмэл жишээ нь: хэрэв захиалгын үед алдаа гарсан бол бид хэрэглэгч рүү имайлээр мэдээллэх шаардлагатай гэж үзье. Асуудал нь юу гэхлээр бид тестлэх үедээ жинхэнэ хэрэглэгчид рүү имэйлээр мэдээлэл явуулахыг хүсэхгүй. Тиймээс бид өөрсдөө удирдаж өөрчлөх боломжтой имэйлын системийн test double -ыг үүсгэе.

Одоо л бид дууриамал (mocks) болон stubs -уудын ялгааг таниж эхэлж байна. Хэрэв бид mailing -ны үйлдлийг тетслэж байсан бол бид энгийн stub -г дараах байдлаар бичих байсан байх.

public interface MailService {
 public void send (Message msg);
}

 

public class MailServiceStub implements MailService {
 private List<Message> messages = new ArrayList<Message>();
 public void send (Message msg) {
   messages.add(msg);
 }
 public int numberSent() {
   return messages.size();
 }
}

Дээрх stub-д бид төлвөөр батлах (state verification) -г дараах байдлаар ашиглаж болно.

class OrderStateTester

public void testOrderSendsMailIfUnfilled() {
   Order order = new Order(TALISKER, 51);
   MailServiceStub mailer = new MailServiceStub();
   order.setMailer(mailer);
   order.fill(warehouse);
   assertEquals(1, mailer.numberSent());
 }

Мэдээж энэ бол маш энгийн тест – зөвхөн л мэдээлэл илгээсэн эсэхийг шалгаж байна. Бид имэйл яг зөвхөн хүндээ илгээгдсэн эсвэл яг зөв агуулга илгээгдсэн талаар тест хийгээгүй, хэдий тийм ч гол санаа нь харагдсан болов уу.

Дууриамал объектуудыг (mocks) ашиглавал дээрх тест нилээд өөр харагдана.

class OrderInteractionTester

public void testOrderSendsMailIfUnfilled() {
   Order order = new Order(TALISKER, 51);
   Mock warehouse = mock(Warehouse.class);
   Mock mailer = mock(MailService.class);
   order.setMailer((MailService) mailer.proxy());

   mailer.expects(once()).method(“send&#8221;);
   warehouse.expects(once()).method(“hasInventory&#8221;)
     .withAnyArguments()
     .will(returnValue(false));

   order.fill((Warehouse) warehouse.proxy());
 }
}

Дээрх хоёр тест хоёуланд нь би жинхэнэ mail service -ын оронд test double -ыг ашиглаж байна. Ялгаа нь stub ашигласан нь төлвөөр батлах (state verification) -г ашиглаж, харин дууриамал объект (mock) ашигласан нь үйлдлээр батлах (behavior verification)-ыг ашигласан байна.

Stub төлвөөр батлах (state verification) -г ашиглахын тулд надад зарим нэмэлт методуудыг stub дээр нэмж өгөх шаардлагатай болж байна – тэдгээр нь хариуг шалгахад ашиглагдах болно. Үүний үр дүнд stub нь MailService -г implement хийсэн боловч мөн нэмэлт тестын методуудаар нэмэгдсэн байна.
Дууриамал (mock) объектууд үргэлж үйлдлээр батлах (behavior verification)-ыг ашигладаг, харин stub альнаар нь ч байж болно. Meszaros үйлдлээр батлах (behavior verification)-ыг ашигладаг stub -уудыг Test Spy гэж нэрлэсэн. Ялгаа нь тухайн double хэрхэн гүйцэтгэгдэж, хэрхэн үр дүнгээ шалгаж байгаад оршиж байгаа. Үүнийг би таньд үлдээлээ, та цааш судлаж ялгааг нь олоорой.

Сонгодог болон дууриамал тестийн арга

Одоо би хоёр дахь ялгаа болох сонгодог болон дууриамал TDD -ийн ялгааны талаар судлах боломжтой боллоо. Гол асуудал маань яг хэзээ дууримал объект (бусад Double)  -уудыг хэрэглэх вэ юм.

Сонгодог TDD -ын загвараар бол болж өгвөл аль болох жинхэнэ объектуудыг ашиглаж харин жинхэнийг ашиглахад төвөгтэй үед double -ыг ашиглахыг зөвлөдөг. Тиймээс сонгодог TDD аргаар хөгжүүлэгчид жинхэнэ агуулах (warehouse) объектыг ашиглаж харин mail service -ын оронд double-ыг ашиглах байсан байх. Харин ямар төрлийн double нь тийм ч  чухал зүйл биш.

Дууриамал TDD загвараар хөгжүүлэгчид харин үргэлж дууриамал объектыг бүх төрлийн элдэвийн үйлдлүүдтэй объектын оронд ашигладаг. Дээрх жишээнд агуулах (warehouse) болон mail service хоёрын хоёулангых нь оронд дууриамал объектыг ашиглах байсан.

Хэдийгээр дууримал тестийн фраймворкууд дууримал тестийн загварт зориулж хийгдсэн байдаг хэдий ч олон сонгодог загвараар хөгжүүлэгчид тэдгээрийг double-уудыг үүсгэж, ашигладах хялбар гэж үздэг.

Дууриамал загварын нэгэн гол урсгал нь Behavior Driven Development(BDD) юм. BDD -ыг анхлан надтай хамт ажилладаг Dan North хөгжүүлсэн ба  хүмүүст Test Driven Development аргаар хөгжүүлж сурахад нь туслах ингэхдээ TDD хэрхэн загварчлалын нэгэн техник арга мэт ажилладаг гэдгийг онцлож өгөхийг зорьсон. Энэ нь цаашлан TDD хөгжүүлэлтийн арга нь объект яг юу хийх ёстой талаар бодоход тусладаг болохоор түүнийг сайтар судалж ойлгохын үүднээс тестүүдийг илүү үйлдлүүдээр нь тодорхойлж нэрлэхэд хүргэсэн. BDD дууриамал объектуудын аргаар замнасан хэдий боловч нэршүүлэх мөн энэ аргад анализыг нэвтрүүлэх зэрэг нь түүнийг цаашид улам тэлж өгсөн. Би үүний талаар илүү нарийн зүйлийг энд өгүүлэхгүй, учир нь энэ нийтлэлд зөвхөн хамаатай зүйл нь BDD бол дууриамал загварын аргаар илүү хөтлөгдсөн TDD-ын нэг хувилбар гэдгийг хэлэх гэсэн юм. Илүү дэлгэрэнгүй мэдээллийг та дээрх холбоосоор орон мэдэх боложтой тул үүнийг би таньд үлдээлээ.

Та заримдаа “Детройт” загвар нь “сонгодог”-т харин “Лондон” нь “дууримал аргаар” хөгжүүлэгчидэд гэсэн байхыг хардаг байх. Энэ нь XP нь анх Детройт-д C3 төслөөс үүсэн хөгжүүлэгдсэн бол харин дууриамал загварчлалын арга нь анх Лондонгийн  XP -г нутагшуулсан хүмүүс хөгжүүлсэн гэсэн баримтаас үүдэлтэй юм. Би мөн нэг зүйлийг хэлэхгүй өнгөрч болохгүй нь, юу гэвэл олон дууриамал аргаар хөгжүүлдэг хөгжүүлэгчид “mockist” гэсэн нэршил болон сонгодог болон дууриамал аргуудыг тусд нь ялгасан ямар нэгэн техникийн хэллэгүүдэд дургүй байдаг. Тэд хоёр загварчлалын аргыг ялгаж үзэх ямар нэгэн шаардлага хэрэгцээ байгаа гэж үздэггүй.

Ялгаануудаас сонгох

Энэ нийтлэлд би дараах хос ялгаануудын талаар тайлбарласан: төлвөөр (state) эсвэл үйлдлээр батлах / сонгодог эсвэл дууриамал тестийн аргаар хөгжүүлэх ТDD. Дээрх аргуудын нэгийг сонгохдоо ямар зарчмуудыг баримтлах хэрэгтэй вэ ? Эхлээд үйлдлээр батлахын эсрэг төлвөөр батлах аргыг авч үзье.

Эхний авч үзэх шаардлагатай зүйл нь аргуулга (context) юм. Бидний ажиллаж объект хоорондын харилцаа (collaboration) *захиалга* (order) болон *агуулах* (warehouse) шиг хялбархан байна уу эсвэл *захиалга* (order) болон *mail service* шиг төвөгтэй байна уу ?

Хэрэв хоорондын харилцаа нь хялбархан бол сонголт хийхэд амархан. Хэрэв би сонгодог TDD аргаар хөгжүүлэгч бол би ямар нэгэн *дууриамал объект* (*mock*), *stub*, *double*-уудыг ашиглахгүй. Би жинхэнэ объектууд болон төлвөөр батлах аргыг ашиглана. Хэрэв би дууриамал TDD аргаар хөгжүүлэгч бол дууриамал объектууд болон үйлдлээр батлах аргуудыг ашиглана. Ямар ч шийдвэр гаргах шаардлагүй.

Хэрэв хоорондын харилцаа нь төвөгтэй бөгөөд би дууриамал TDD аргаар хөгжүүлэгч бол ямар ч асуудалгүй би дууриамал объектууд болон үйлдлээр батлах аргыг ашиглана. Харин би сонгодог аргааг хөгжүүлэгч бол надаа сонголт бий. Хэдий тийм ч альнийг нь сонгох нь тийм ч чухал биш. Ихэвчлэн сонгодог аргаар хөгжүүлэгчид тухайн байдлаасаа болоод аль амрыг нь сонгодог.

Бид төлвөөр эсвэл үйлдлээр батлах сонголтыг хийхэд тийм ч хүндрэлтэй биш гэдгийг харлаа. Гол асуудал нь сонгодог TDD юу эсвэл дууриамал TDD юу гэсэн сонголт юм. Төлвөөр батлах болон үйлдлээр батлах аргуудын зарчмууд энэхүү хэлэлцүүлэгт нөлөөлөх нь зайлшгүй бөгөөд үүнд би онцгой анхаарах болно.

Эхлэхээсээ өмнө би нэг хязгаарын нөхцөлийг дурьдахгүй бол болохгүй нь. Зарим үед объект хооронд харилцаа хялбар байсан ч төлвөөр батлах аргыг ашиглахад нилээд төвөгтэй байдаг. Үүний нэг сонгодог жишээ нь *cache* юм. Үүний нэг гол зүйл нь *cache* -ын тухайн төлвөөс та *cache hit* болсон уу *cache missed* болсныг хэлж чадахгүй. Энэ нөхцөлд хэдийгээр та үнэнч сонгодог TDD аргыг баримтлагч байсан ч үйлдлээр батлах аргыг сонгосон нь дээр болохыг ойлгоно. Үүнтэй ижил мөн өөр төрлийн хязгаарын нөхцлүүд аль ч аргад нь байгаа гэдэгт би эргэлзэхгүй байна.

Сонгодог уу, эсвэл дууриамал аргаар хөгжүүлэгч үү гэсэн сонголтыг хийхэд бид нилээд хэдэн хүчин зүйлүүдийг авч үзэх шаардлагатай болохоор би дараах байдлаар тойм хэдэн бүлгүүдэд хуваасан болно.

TDD -р хөтлөгдөх

Дууриамал объект бол XP -ийн бүлгээс гаралтай ба XP-ын гол онцлогуудын нэг нь Тестээр Хөтлөгдөх Хөгжүүлэлт (Test Driven Development) -ийн аргыг чухалчилдаг билээ. Үүнд системийн загварын хувьсал, өөрчлөлтүүд нь бичигдэх тестээр хөтлөгдөн давталттайгаар хийгддэг.

Тиймээс дууриамал тестийн аргаар хөгжүүлэгчид тухайн арга системийн загварт хэрхэн нөлөөлдөг талаар тусгайлан авч үздгийг гайхах зүйлгүй юм. Ялангуяа тэд Need-driven development (шаардлагаас хөтлөгдөх хөгжүүлэлт) гэх аргыг дэмждэг. Энэ аргаар бол та user story (хэрэглэгчийн шаардлага, хэрэглэгдэх байдал) -ыг хөгжүүлэхдээ эхлээд системийн хамгийн гадна талаас нь тестлэх байдлаар эхэлж, SUT -ынхаа интерфайсыг тодорхойлдог. Хамтрагч объектуудын хийгдэх үйлдлүүдийг нь олохдоо, SUT болон түүний хөрш объектуудыг хоорондын харилцааг судлан SUT-н outbound interface (хөрш объекттой харилцах гаралтын интерфэйс) -ыг тодорхойлдог.

Хамгийн эхний тест тань ажиллаж байгаа үед дууриамал объект дээр тодорхойлогдсон expectations (тэгнэ гэсэн хүлээлт)-үүд таны дараагын алхам дахь шаардлага болон хувчирч, тестлэх эхний алхам болдог. Ингэж expectation (тэгнэ гэсэн хүлээлт) бүрийг хамтрагч объектын тест болон хувиргах үйлдлийг систем рүү нэвтрэх давхрага SUT бүрт давтана. Энэ загварын аргыг мөн *outside-in* гэж нэрлэдэг ба энэ нь нилээд оновчтой тодорхойлсон нэр. Энэ загварчлалын арга layered system (давхарга систем) -д нилээд сайн таарч ажилладаг. Та эхлээд UI (хэрэглэгчийн интерфэйс)-ыг програмчилах ба түүний доор давхаргуудыг дууриамилаар тодорхойлно. Ингэсний дараа та доорх давхарга нэг бүрт тест бичин аажмаар системийн давхарга бүрт нэвтрэн доошлоно. Энэ загварын арга бол маш зохион байгуулалтайгаар удирдагдах арга тул олон хүмүүс ООП (OOP) болон TDD -г анхлагч суралцагчдад ихээхэн хэрэгтэй гэж итгэдэг.

Сонгодог TDD арга үүнтэй зааварчилгаа өгдөггүй. Хэдийгээр тийм ч та mock-ын оронд stub-ыг ашиглан ижил төрлийн алхамаар загварчлах боломжтой. Ингэхийн тулд, SUT-г ажиллуулах тестэнд хамтрагч объектоос ямар нэгэн зүйл хэрэг болох бүртээ уг утгыг hard-code байдлаар бичнэ. Ингээд та тест ногоон болсон үедээ hard-coded утгаа зөв кодоор сольно.

Сонгодог тестийн арга өөр бас онцлогтой. Нэг арга нь *middle-out*. Энэ аргад та шинээр хийгдэх feature-аа сонгоод, тухайн feature ажиллахад домайн-с юу хэрэг болох талаар бодож, шийдвэр гаргана. Ингээд домайн объектууд юу хийгдэх ёстойг загварчилсныхаа дараа
UI давхаргыг дээрээс нь тавьж ажиллуулна. Ингэж ажиллахад таньд магадгүй ямар ч дууриамал зүйл хэрэг болохгүй байх. Маш олон хүмүүс энэ аргад дуртай байдаг учир нь энэ арга хүний анхаарлыг эхлээд домайны загварчлал (domain model) -д төвлөрүүлдэг ба энэ нь домайны логикууд UI-аас хамаарахгүй байх боломжыг ойлгодог.

Миний нэг онцолж хэлэх зүйл гэвэл сонгодог болон дууриамал загварчлалын аргаар хөгжүүлэгч бүр тухайн нэг зүйлийг тухайн цагт хийдэг (do one at a time).
Энэ нь программын давхрага бүрийг нэг нэгээр нь дараалан гүйцэтгэх зарчим ба нэг давхаргыг дуусгахаасаа өмнө хэзээ ч нөгөө нь эхлүүлэхгүй гэсэн бүр *school of thought* (цэгцтэй, мөрдөж дагадаг зарчмууд) байдаг. Сонгодог болон дууриамал загварчлалаар хөгжүүлэгчид хоёул *Agile* аргачлал дээр суурьтай байдаг тул нарийн тусгаарлагдсан давталтайгаар (fine-grained iterations) хөгжүүлэхийг илүүд үздэг. Тиймээс тэд давхрага давхрагаар хөгжүүлэхээс илүүтэйгээр feature-үүдийг нэг нэгээр (feature by feature) нь авч ажиллахыг илүүд үздэг.

Тестийн өгөгдлийг тодорхойлох

Сонгодог аргаар бол та SUT-ыг тестлэхийн тулд түүнийг хамтрагчуудыг мөн адил үүсгэж өгөх хэрэгтэй болно. Хэдийгээр дээрх жишээнд хэдхэн цөөхөн объектууд ашиглагдаж байгаа боловч жинхэнэ тестэнд олон тооны хамтрагч дэд объектууд шаардлагатай болдог. Ихэвчлэн эдгээр объектууд тест тус бүрт дээр шинээр үүсэж, мөн устгагдаг.

Харин дууриамал тестийн аргаар бол зөвхөн SUT л үүсдэг ба бусад хөрш, хамтрагч объектуудых нь оронд дууриамал объектуудыг ашигладаг. Ингэж илүү нүсэр тестийн өгөгдөл үүсгэхээс зайлхийж болдог. (Ямартай ч ингэж тодорхойлдог. Би нилээд төвөгтэй үүсгэгддэг дууриамал объектын бүтэц, байдалтай таарч байсан ч энэ маань магадгүй tool-үүдийг буруу ашигласнаас байж ч болох юм.)

Бодит байдал дээр сонгодог аргаар хөгжүүлэгчид цогц тестийн өгөгдлүүдийг дахин ашиглахыг илүү эрмэлздэг. Хамгийн энгийнээр бол та тодорхойлох өгөгдлүүдийг xUnit-ын *setup* метод-д хийж өгнө. Харин олон классуудад ашиглагдах илүү цогц өгөгдлүүдлийн хувьд тусгайлан өгөгдлүүдийг үүсгэхээр зориулагдсан класс үүсгэж өгнө. Би ихэвчлэн эдгээр класуудыг ThoughtWorks -ын анхны XP төслүүдэд нэрлэж байсанчлан [Object Mother](http://martinfowler.com/bliki/ObjectMother.html)-ууд гэж нэрлэдэг. Том хэмжээний сонгодог тестүүдэд mother-уудыг ашиглах нь зайлшгүй боловч эдгээр mother-ууд нь илүүдэл тордолт, арчилгаа шаарддаг ба тэдгээрт хийгдэх өөрчлөлт бүр тестүүдэд хэд хэдэн давалгаалж нөлөөлдөг. Өгөгдлүүдийг тодорхойлох нь тестийн ажиллах хурданд мөн нөлөөлдөг боловч би үүнийг хэрэв зөв хийсэн тохиолдолд асуудал болсон гэж сонсоогүй л юмдаг. Зарим нэг онцгой давхар үүсгэгддэг объектуудаас бусад тохилдолд ихэнхдээ өгөдлүүдийн объектууд үүсгэхэд хялбархан байдаг.

Хачирхайлтай нь хоёр арга хоёулаа бие биенээ маш их ажиллагаатай гэж дүгнэдэг. Дууриамал тестийн аргаар хөгжүүлэгчид “тестийн өгөгдлийг үүсэх нь маш их ажиллагаатай&#8221; гэдэг бол харин сонгодогууд “эдгээр нь дахин ашиглагддаг харин та нар тест бүрт дууриамал объектууд үүсгэдэг&#8221; гэдэг.

Тестийн тусгаарлалт

Хэрэв та дууриамал тесттэй хийгдсэн системд ямар нэгэн алдаа хийвэл, зөвхөн алдаатай SUT-ыг агуулсан тестүүд л ажиллахгүй болно. Харин сонгодог аргаар хийгдсэн системд алдаа хийвэл алдаатай объектыг агуулсан бүх тестүүд ажиллахгүй ба үүнд алдаатай объект бусад тестэнд хамтрагчаар орж ашиглагдаж байгаа тестүүд ч мөн орно. Үүнээс үүдээд олон дахин ашиглагдаж буй объектод гарах нэг алдаа нийт системийг хамарсан маш олон алдааг дагуулдаг.

Дууриамал аргаар хөгжүүлэгчид үүнийг маш том дутагдалтай тал гэж үздэг; учир нь алдааны үндсэн шалтгааныг олж, засахын тулд маш олон тооны debug-ын хийх хэрэгтэй болдог. Харин сонгодог аргаар хөгжүүлэгчид үүнийг асуудал гэж үздэггүй. Ихэвчлэн алдаатай кодыг илрүүлэх нь нилээд хялбархан байдаг ба хөгжүүлэгч ажиллахгүй тестийг хараад л аль алдаа хаанаас үүдэлтэйг хэлж чаддаг. Мөн түүнчлэн хэрэв та байнга тестлэдэг бол (хэрэв үгүй бол, та тэгэх хэрэгтэй), гарсан алдаа тань таны хамгийн сүүлд өөрчилсөн кодоос үүсэлтэйг та мэддэг учираас алдааг олоход тийм ч хүндрэлтэй байдаггүй.

Тестүүдийн бүтэц бүрэлдэхүүн, тогтоц (granularity) нь үүнд мөн нэгэн чухал хүчин нөхцөл болдог. Сонгодог аргаар хөгжүүлэгдсэн тестүүд олон тооны жинхэнэ объектуудыг ажиллуулдаг тул ихэвчлэн нэг тест гэдэг бол нэг объектыг тестлэх тест гэхээс илүүтэйгээр олон тооны бүл объектуудыг тестлэх нэг бүлэг тест болдог. Хэрэв бүл олон тооны объектуудыг агуулдаг бол алдааны үндэс эх шалтгааныг олоход ихээхэн хүндрэл учирдаг. Энэ нь юу гэсэн үг вэ гэхлээр тэдгээр тестүүд нилээд бүдүүн бадаг, нарийн тодорхойлогдоогүй бүтэцтэй (coarse grained) байна гэсэн үг.

Дууриамал аргаар хөгжүүлэгдсэн тестүүд дээрх хүндрэлтэй тулгарах нь нилээд ховор. Учир нь тухайн аргад  үндсэн тестлэгдэж буй объектоос бусад бүх объектуудыг дууриамлаар ашиглагддаг тул хамтрагч объектуудад нарийн тодорхойлогдсон (fine grained) тестүүд шаардлагатай болдог. Яг үндсэндээ бол бүдүүн бадаг, нарийн тодорхойлогдоогүй бүтэцтэй (coarse grained) тестүүд нь сонгодог аргын дутагталтай тал биш.  Харин үүнээс илүүтэйгээр сонгодог аргыг буруу ашигласны үр дүн гэж хэлж болно. Нэг сайн баримталбал зохих зарчим бол та класс бүхнээ яг нарийн тодорхойлогдсон тестээр тусгаарласан эхэсээ шалгаж байх хэрэгтэй. Хэдийгээр заримдаа бүл объектууд тестэнд хамрагдах үе байдаг ч тооны хувьд цөөхөн байх учиртай – 5,6 -с хэтрэхгүй байвал зүгээр. Хэрэв та бүдүүн бадаг, нарийн тодорхойлогдоогүй бүтэцтэй (coarse grained) тестүүдэд алдаа олох (debuging хийх) болбол, та нарийн тодорхойлогдсон бүтэцтэй (fine grained) тестүүдийг алхам бүрдээ үүсгэн, тестээр хөтлөгдөх аргаар цааш үргэлжлүүлэх хэрэгтэй.

Сонгодог XUnit тестүүд нь яг үндсэндээ нэгжийг тестлэх (unit) тестүүдээс гадна мөн бага хэмжээний integration тестүүд юм. Ийм учираас үндсэн тестэнд илрүүлэгдээгүй алдаанууд дэд тестүүдэд заримдаа илэрдэг тул – ялангуяа классууд хоорондоо нарийн холбогдон харилцах үед – олон хүмүүс үүнд дуртай байдаг. Дууриамал аргаар хөгжүүлэгдсэн тестүүдэд энэ байдаггүй. Мөн түүнчлэн дууриамал тест дахь *тэгэх байх гэсэн нөхцөлүүд* (expectations-үүд) заримдаа буруу байх боломжтой тул хэдийгээр тестүүд зөв ажиллаж ногоон болох боловч алдааг нуудаг.

Одоо миний онцлох хэрэгтэй нэг зүйл бол хэдийгээр аль ч тестийн аргыг та ашигласан бай та үүнийгээ системийг бүхлээр нь хамрах нарийн тодорхойлогдооогүй (coarse grained) acceptance тестүүдтэй холбож өгөх хэрэгтэй. Би acceptance тестүүдийг төслийнхөө нилээд сүүлийн шатанд авч ашигласан ба үүндээ харамсдаг нилээдгүй олон тооны төслүүдтэй таарч байсныг хэлэх хэрэгтэй байх.

Тестээ хэрэгжүүлэлтэйгээ холбох

 

Дууриамал тестийн аргаар тест бичихдээ SUT өөрийн хамтрагч объектуудтай зөв харилцаж байгаа эсэхийг нь батлахын тулд түүний *outbound calls* (хөрш объекттой харилцах гаралтын хандалтуудыг) -ыг нь тестлэдэг. Харин сонгодог аргаар бичигдсэн тестүүдэд эцсийн үр дүнг л шалгадаг учир яаж тэнд хүрснийг нь анхаарч авч үздэггүй. Тиймээс дууриамал аргаар хөгжүүлэгдсэн тестүүд методынхоо гүйцэтгэлтэй илүү холбогдсон (coupled) байдаг. Хамтрагчууд (collaborators) руугаа хандах дуудалтыг өөрчилөхөд л дууриамал аргаар бичигдсэн тестыг зогсоож, ажиллахгүй болгодог.

Ингэж холбож (coupling хийж) өгөх нь цаашид анхаарвал зохих хэдэн зүйлүүд рүү хөтөлдөг. Хамгийн чухал нь Тестээр Хөтлөгдөх Хөгжүүлэлт (Test Driven Development)-д гарах өөрчлөлт юм. Дууриамал аргаар бол тест бичих нь таныг гүйцэтгэлийнхээ юу хийх үйлдлүүдийг бодох боломж олгодог. Үүнийг дууриамал аргаар хөгжүүлэгчид давуу тал гэж үздэг. Харин сонгодог аргаар хөгжүүлэгчид зөвхөн гадаад интэрфэйсээс нь юу хийгдэхийг нь анхаарч бодох нь чухал гэж үздэг ба гүйцэтгэлийн бүх нарийн ширийнийг нь тестээ бичиж дуусах хүртлээ анхаарч авч үздэггүй.

Гүйцэтгэлтэйгээ холбох нь мөн *refactoring* хийхэд бас хамаатай. Учир нь дууриамал аргаар бичигдсэн тестүүдэд SUT-ын аливаа гүйцэтгэлийг (implementation) нь өөрчилөхөд л бичигдсэн тестүүд нь ажиллахгүй болох боломж нь сонгодог аргаар бичигдсэн тестүүдээс илүү өндөр байдаг.

Тестээ гүйцэтгэлттэйгээ холбох нь мөн дууриамал аргаар хөгжүүлэх tool-үүдэдээс мөн хамаардаг. Учир нь ихэнхдээ тэдгээр tool-үүдэд тухайн тестэнд чухал хамааралгүй байсан ч методуудын дуудлага болон параметер бүрийг тусгайлан оноож, зааж өгдөг. jMock tool-ийн нэг онцлог нь тэдгээр *тэгэх байх гэсэн нөхцлүүдийг* (expectations) тодорхойлохдоо илүү уян хатан ба чухал хамааралгүй хэсэгт нь нарийн тодорхойлолт оноож, зааж өгөхгүйгээр байх боломжыг олгодог. Энэ нь refactoring хийхэд мөн илүү хялбар байдаг.

Загварчлалын төрөл

Эдгээр тестийн аргуудын нэг сонирхолтой зүйл нь миний хувьд тестийн арга хэрхэн загварчлах шийдвэрт нөлөөлдөгт оршдог. Хоёр аргын хөгжүүлэгч нартай уулзаж ярилцсанаар би тухайн аргуудын дэмждэг загварчлалын хоорондын ялгааны талаар тодорхой ойлголттой болсон. Хэдий тийм ч би дөнгөж өнгөц төдий ойлголттой байгаа гэдэгтээ эргэлзэхгүй байна.

Аргуудын ялгааны талаар би дээр давхаргуудад нэвтрэх хэсэгт дурьдсан. Дууриамал тестийн арга **outside-in** аргачлалыг дэмждэг бол **domain model** -г түрүүлж хөгжүүлэхийг илүүд үздэг хөгжүүлэгчид сонгодог аргачлалаар замнадаг.

Миний анзаарсан нэг зүйл гэвэл дууриамал аргаар хөгжүүлэгчид цуглуулагч объекттой методуудыг илүүд үздэг тул утга буцаадаг методуудаас ер нь татгалзах хандлагатай байдаг. Жишээлбэл: *Report*-үүсгэхийн тулд бүлэг объектуудаас мэдээлэл цуглуулах үйлдлийг авч үзье. Хэвшсэн нэг нийтлэг арга бол *report* хийдэг метод бүлэг объектуудын *string* буцаадаг методуудыг дуудан, үр дүнгүүдийг нь нэгтгэн тур зуурын хувьсагчдад хадгалдаг. Харин дууриамал аргаар хөгжүүлэгч үүнээс илүүтэйгээр **string buffer** -ыг бүлэг объектуудад дамжуулж, тэдгээрийг дамжуулсан *buffer* руу буцаах *string* утгуудыг нь хийлгэдэг. Ингэж **string buffer**-ыг утга цуглуулагч параметер гэж үздэг.

Дууриамал аргаар хөгжүүлэгчид мөн ‘train wrecks’ – методуудын гинжин дуудлага *getThis().getThat().getTheOther()* -с аль болох татгалзах талаар илүү ярьдаг. Үүнийг мөн **Law of Demeter** гэж ч нэрлэдэг. Хэдийгээр гинжин методууд асуудал болдог ч (smell), эсрэгээр хэтэдсэн **middle men** объектууд ч мөн асуудал болдог. (Би байнга **Law of Demeter**-ыг **Suggestion of Demeter** гэж дууддаг ч байгаасаа гэж хүсдэг.)

Объект хандалтад (Object Oriented) загварчлалд хүмүүсийн ойлгоход нилээд бэрх зүйлүүдийн нэг нь “Tell Don’t Ask&#8221; зарчим. Энэ зарчим объектоос мэдээллийг булааж аваад хэрэглэгчийн хэсэгт хийхийн оронд таныг объектодоо хийх зүйлээ шууд хэлэхийг тулгадаг. Дууриамал тестийн арга энэ зарчмыг илүү хөхүүлэн дэмжиж, хэтэдсэн **getter** -үүдээс мөн татгалздаг гэж тухайн аргаар хөгжүүлэгчид ярьдаг. Сонгодог аргаар хөгжүүлэгчид харин үүнээс болгоомжлох өөр олон аргууд байдаг гэж маргадаг.

Төлвөөр батлах (state-based verification) -ын нэг хүлээн зөвшөөрөгдсөн асуудал нь энэхүү арга зөвхөн төлөв шалгах зорилгоор ашиглагдах **query** методуудыг бий болгодог.
Зөвхөн тестлэхийн тулд объектын API-д методууд нэмэх нь яав ч тийм сайн зүйл биш. Харин үйлдлээр батлах (behavior verification) аргаар үүнээс ангижрах боломжтой. Үүний эсрэг хийгддэг нэг тайлбар нь иймэрхүү өөрчлөлтууд амьдрал дээр тийм ч том асуудал болдоггүй гэдэг.

Дууриамал аргаар хөгжүүлэгчид role interface -үүдэд ихээхэн анхаарлаа хандуулдаг ба тус арга нь **role interface**-ыг илүүтэй дэмждэг гэдэг. Учир нь хамтын ажиллагаа (collaboration) бүр тус тусдаа дууриамалаар хийгддэг тул энэ нь илүүтэй **role interface** болон хувирдаг гэдэг. Дээрх миний авсан жишээнд **string buffer** -ыг ашиглахын тулд дууриамал аргаар хөгжүүлэгчид тухайн салбарт (domain-д) тохирсон тусгай **role**-ыг илүүтэй үүсгэх байх. Магадгүй тэр нь **string buffer**-ыг **implement** хийсэн байх болов уу.

Эдгээр загварчлалын ялгаа нь ихэнх дууриамал аргаар хөгжүүлэгчдийн хувьд гол хүчин зүйл нь болдог гэдгийг санаж байх хэрэгтэй. TDD-ын үүсэл нь **evolutionary design** (хувьсах загварчлал)-ыг дэмждэг хүчтэй автомат регрессийн тестүүд бий болгох хүслээс үүдэлтэй. Явцын дунд улмаар хөгжүүлэгчид (practioners) тестээ түрүүлж бичих нь загварчлалын процессд илэрхий үр дүнтэй, хүчтэй хувь нэмэр оруулдгыг ойлгож мэдсэн. Дууриамал аргаар хөгжүүлэгчид ямар загварыг сайн бэ гэдэг дээр хүчтэй ойлголттой байдаг учираас тэдгээрийг хүмүүсд таниулж, хүмүүсийг тухайн загвараар хөгжүүлэхэд нь туслах үүднээс дууриамал тестийн сангуудыг үүсгэсэн байдаг.

Тэгэхээр би сонгодог уу эсвэл дууриамал аргаар тестлэгч үү

Удахгүй орно

Төгсгөлийн бодлууд

Удахгүй орно

 

 

 

 

Үг зүй:

Mock object - Дууриамал объект
Stub - нэг юмны хэлтэрхий тал, тестэнд ашиглахдаа объектын зөвхөн хэрэг болох хэсгийг нь л зааж өгч ашигладаг
Framework - Тогтолцоо
Order - Захиалга
Product - Бараа
Quantity - Бүтээгдэхүүний тоо
Warehouse - Агуулах
Set up - Байгуулах
Inventory - Бараа бүтээгдэхүүний тооллого
Behavoir - Үйлдэл
Community - Бүлгэм
state verification - Төлвийн баталгаа
object-under-test  - тестлэгдэж буй объект
Primary object - Гол оъект
Secondary object - Хоёрдогч объект
Collaborator - Хамтрагч
Library - Сан

Data - Өгөгдөл

Expectation - Тэгэх байх гэсэн хүлээлт, амлалт

Exercise - Хийж, гүйцэлдүүлэх

Assert - Нотолгоо, нотлох

Constructor - Үүсгэгч

Constraint - Хялгаарлалт

Fail - ажилтгүй болох
Call - дуудлага (жишээ нь: method call - методыг дуудах)

 

Орчуулгын эх сурвалж http://martinfowler.com/articles/mocksArentStubs.html

 

Нийтлэлийг бүтнээр нь мөн gitbook.com -с унших, татаж авах боломжтой. Холбоос https://www.gitbook.com/book/erheme318/mocks-aren-t-stubs

Advertisements

11 thoughts on “Mocks Aren’t Stubs (орчуулга)

  1. Бушуу болих TDD -р хөтлөгдөх | Think! ~

Хариулт үлдээх

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Өөрчлөх )

Twitter picture

You are commenting using your Twitter account. Log Out / Өөрчлөх )

Facebook photo

You are commenting using your Facebook account. Log Out / Өөрчлөх )

Google+ photo

You are commenting using your Google+ account. Log Out / Өөрчлөх )

Connecting to %s