乾淨的test program會幫助你更有效率

以前花很多時間在維護測試程式, 每次越維護越覺得測試自動化真的是浪費時間, 效果在哪裡也不知道.

最近在看 "xUnit Test Pattern", 我發現很多地方我們似乎做錯了, 所以只是越做越賠錢.

他書中有個簡單的範例, 告訴我們如何 refactor 測試程式, 來達到 test case 可以當作說明文件. 大家可以參考看看, 這樣 refactor 後的結果是否真的幫你很多.


Public testAddItemQuality_severalQuantity_v1() {
  Address billingAddress = null;
  Address shippingAddress = null;
  Customer customer = null;
  Product product = null;
  Invoice invoice = null;
  try {
     // set up fixture
    billingAddress = new Address (“122 1 st St SW”, “Calgary”, “Alberta”,
      “T2N 2V2”, “Canda” );
    shippingAddress = new Address (“133 1 st St SW”, “Calgary”, “Alberta”,
      “T2N 2V2”, “Canda” );
    customer = new Customer(99, “Jon”, “Doe”, new BigDecimal(“30”),
      billingAddress,  shippingAddress);
    product = new Product(88, “SomeWidget”, new BigDecimal(“19.99”));
    invoice = new Invoice(customer);
    //Execise SUT
    invoice.addItemQuantity(product, 5);
    // Verify outcome
    List listItems = invoice.geLineItems();
    if (lineItems.size() ==  1)
      LineItem actItem = (LineItem) lineItems.get(0);
      assertEquals(“inv”, invoice, actItem.getInv());
      assertEquals(“prod”, product, actItem.getProd());
      assertEquals(“quant”, 5, actItem.getQuantity());
      assertEquals(“discount”, new BigDecimal(“30”),
        actItem.getPercentDiscount() );
      assertEquals(“imot price”, new BigDecimal(“19.99”),
        actItem.getUnitPrice() );
      assertEquals(“extened”, new BigDecimal(69.96”),
        actItem.getExtndedPrice() );
    } else {
      assertTrue(“Invoice should have 1 item”, false);
    }
    //Execise SUT
    invoice.addItemQuantity(product, 5);
    // Verify outcome
    LineItems expected = new LineItem (invoice, product, 5,
      new BigDecimal(“30”), new BigDecimal(“69,96”);
   
    assertContainExactlyOneLineItem(invoice, expected);
}
  } finally {
    //Teardown
    deleteObject(invoice);
    deleteObject(product);
    deleteObject(customer);
    deleteObject(billingAddress);
    deleteObject(shippingAddress);
}

1. Refactor Assertion
(1) Refactor 前
作者覺得比 LineItem 的每個細項很冗長, 不容易很清楚在 high level 上, 你要比的是甚麼.

    //Execise SUT
    invoice.addItemQuantity(product, 5);
    // Verify outcome
    List listItems = invoice.geLineItems();
    if (lineItems.size() ==  1)
      LineItem actItem = (LineItem) lineItems.get(0);
      assertEquals(“inv”, invoice, actItem.getInv());
      assertEquals(“prod”, product, actItem.getProd());
      assertEquals(“quant”, 5, actItem.getQuantity());
      assertEquals(“discount”, new BigDecimal(“30”),
        actItem.getPercentDiscount() );
      assertEquals(“imot price”, new BigDecimal(“19.99”),
        actItem.getUnitPrice() );
      assertEquals(“extened”, new BigDecimal(69.96”),
        actItem.getExtndedPrice() );
    } else {
      assertTrue(“Invoice should have 1 item”, false);
    }

(2) Refactor 後
因此他先產生一個期待的執行結果, 拿它來跟實際的 LineItem 作比較. 並且是由一個檢查是否有一項的函式來做處理.
    //Execise SUT
    invoice.addItemQuantity(product, 5);
    // Verify outcome
    LineItems expected = new LineItem (invoice, product, 5,
      new BigDecimal(“30”), new BigDecimal(“69,96”);
   
    assertContainExactlyOneLineItem(invoice, expected);
}

2. Refactor TearDown
(1) Refactor 前
若是在呼叫deleteObject函式的過程中當掉了, 哪我們的程式便會出問題

  } finally {
    //Teardown
    deleteObject(invoice);
    deleteObject(product);
    deleteObject(customer);
    deleteObject(billingAddress);
    deleteObject(shippingAddress);
}

(2) Refactor 後
作者將測試程式要 create 的 object 註冊到一個 object 的 list 中, 之後再一併一起刪除
a. 註冊 object 的程式
List testObjects

protected void setUp() throws Exception {
  super.setUp();
  ttestObjects = new ArrayList();
}
protected void registerTestObject (Object testObject) {
  testobjects.add(testObject);
}

b. tear down所有 object
public  tearDown() {
  Iterator i = testObjects.iteratior();
  while(i.hasNext() ) {
    try {
      deleteObject(i.next());
    } catch(RuntimeException e) { … }
  }
}

c. Create 完 object, 要立刻註冊進去 list object
Public testAddItemQuality_severalQuantity_v1() {
  ……
  try {
     // set up fixture
    billingAddress = new Address (“122 1 st St SW”, “Calgary”, “Alberta”,
      “T2N 2V2”, “Canda” );
    registerTestObject(billingAddress);
    shippingAddress = new Address (“133 1 st St SW”, “Calgary”, “Alberta”,
      “T2N 2V2”, “Canda” );
    registerTestObject(shippingAddress);
    customer = new Customer(99, “Jon”, “Doe”, new BigDecimal(“30”),
      billingAddress,  shippingAddress);
    registerTestObject(customer);
    product = new Product(88, “SomeWidget”, new BigDecimal(“19.99”));
    registerTestObject(product);
    invoice = new Invoice(customer);
    registerTestObject(invoice);


3. Refactor Set up Fixture
(1) Refactor 前
相信大家一定覺得set up這一段很混亂.作者提出幾個地方要改進
a. 有時候set up的資料可能跟這次測試沒有關係. 例如billingAddress和shipingAddress的內容, 和我們這次測試主題無關. 因為我們只想知道是否invoice只有一筆product資料

. 可是因為要能順利測試, 我們需要把這些資料create出來, 可是這些動作很冗長, 會讓大家在讀test code時失去了焦點
b. 另外若是這些資料存到db去, 可能導致下次在執行時, 因為資料已經存在, 所以使得測試不成功

Public testAddItemQuality_severalQuantity_v1() {
  ……
  try {
     // set up fixture
    billingAddress = new Address (“122 1 st St SW”, “Calgary”, “Alberta”,
      “T2N 2V2”, “Canda” );
    registerTestObject(billingAddress);
    shippingAddress = new Address (“133 1 st St SW”, “Calgary”, “Alberta”,
      “T2N 2V2”, “Canda” );
    registerTestObject(shippingAddress);
    customer = new Customer(99, “Jon”, “Doe”, new BigDecimal(“30”),
      billingAddress,  shippingAddress);
    registerTestObject(customer);
    product = new Product(88, “SomeWidget”, new BigDecimal(“19.99”));
    registerTestObject(product);
    invoice = new Invoice(customer);
    registerTestObject(invoice);

(2) Refactor 後
作者建議由另外的creation method來處理. 只留下跟這次測試有關的項目. 由creation method來處理每此資料的唯一性, 確保測試的重複性.

Public testAddItemQuality_severalQuantity_v1() {
  // set up fixture
  Customer cust = createCustomer(new BigDecimal(“30”));
  Product prod = createProduct(new BigDecimal(“19.99”));
  Invoice invoice = createInvoice(cust);
  //Execise SUT
  invoice.addItemQuantity(product, 5);
  // Verify outcome
  LineItems expected = new LineItem (invoice, product, 5,
    new BigDecimal(“30”), new BigDecimal(“69,96”);
   
  assertContainExactlyOneLineItem(invoice, expected);
}

4. Refacotr magic number
目前測試程式中有許多magic number導致可讀性不高
(1) Refactor 前
Public testAddItemQuality_severalQuantity_v1() {
  // set up fixture
  Customer cust = createCustomer(new BigDecimal(“30”));
  Product prod = createProduct(new BigDecimal(“19.99”));
  Invoice invoice = createInvoice(cust);
  //Execise SUT
  invoice.addItemQuantity(product, 5);
  // Verify outcome
  LineItems expected = new LineItem (invoice, product, 5,
    new BigDecimal(“30”), new BigDecimal(“69,96”);
   
  assertContainExactlyOneLineItem(invoice, expected);
}

(2) Refactor 後
你最後看到這個測試程式, 是否可以很明確知道他要測是甚麼.以及這個受測程式要如何被使用.

Public testAddItemQuality_severalQuantity_v1() {
  final int QUANTITY = 5;
  final BigDecimal UNIT_PRICE = new Big Decimal(“19.99”);
  final BigDecimal CUST_DISCOUNT_PC = new Big Decimal(“30”);
  // set up fixture
  Customer cust = createCustomer(CUST_DISCOUNT_PC);
  Product prod = createProduct(UNIT_PRICE);
  Invoice invoice = createInvoice(cust);
  //Execise SUT
  invoice.addItemQuantity(product, 5);
  // Verify outcome
  final BigDecimal EXTENDED_PRICE = new BigDecimal(“69.96”);
  LineItems expected = new LineItem (invoice, product, QUANTITY,
    CUST_DISCOUNT_PC, EXTENDED_PRICE);
   
  assertContainExactlyOneLineItem(invoice, expected);
}

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 kojenchieh 的頭像
    kojenchieh

    David Ko的學習之旅

    kojenchieh 發表在 痞客邦 留言(0) 人氣()