xUnit之一次测试代码重构之旅

发表于:2017-5-31 13:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:ntop    来源:简书

  现在代码已经变得很简洁了,由于消除了 try-finally 语句的缘故,也不需要提前声明 billingAddress、customer ... 这些变量,所以代码可以更简洁:
public void testAddItemQuantity_severalQuantity_v9(){
//   Set up fixture
Address billingAddress = new Address("1222 1st St SW",
"Calgary", "Alberta", "T2N 2V2", "Canada");
registerTestObject(billingAddress);
Address shippingAddress = new Address("1333 1st St SW",
"Calgary", "Alberta", "T2N 2V2", "Canada");
registerTestObject(shippingAddress);
Customer customer = new Customer(99, "John", "Doe",
new BigDecimal("30"),
billingAddress,
shippingAddress);
registerTestObject(shippingAddress);
Product product = new Product(88, "SomeWidget",
new BigDecimal("19.99"));
registerTestObject(shippingAddress);
Invoice invoice = new Invoice(customer);
registerTestObject(shippingAddress);
// Exercise SUT
invoice.addItemQuantity(product, 5);
// Verify outcome
LineItem expected =
new LineItem(invoice, product, 5,
new BigDecimal("30"),
new BigDecimal("69.95"));
assertContainsExactlyOneLineItem(invoice, expected);
}
  构建测试环境部分 - Setup Fixture
  这部分代码的特点是每个对象被创建完之后都会被注册到框架中,一个简单快速的重构方案是创建一个 createXXX 方法来做这些事情:先创建对象再注册到框架中,额外的好处是如果对象的构造方法改变了,我们不需要去修改每个调用构造方法的地方:
public void testAddItemQuantity_severalQuantity_v10(){
//   Set up fixture
Address billingAddress =
createAddress( "1222 1st St SW", "Calgary", "Alberta",
"T2N 2V2", "Canada");
Address shippingAddress =
createAddress( "1333 1st St SW", "Calgary", "Alberta",
"T2N 2V2", "Canada");
Customer customer =
createCustomer( 99, "John", "Doe", new BigDecimal("30"),
billingAddress, shippingAddress);
Product product =
createProduct( 88,"SomeWidget",new BigDecimal("19.99"));
Invoice invoice = createInvoice(customer);
// Exercise SUT
invoice.addItemQuantity(product, 5);
// Verify outcome
LineItem expected =
new LineItem(invoice, product,5, new BigDecimal("30"),
new BigDecimal("69.96"));
assertContainsExactlyOneLineItem(invoice, expected);
}
  这段代码依然有重构的空间:
  无法判断输入和输出的关系,比如 Customer 的构造需要6个传入参数,但是我们无法判断哪一个参数才是被验证的,哪些参数是无关的,如果修改 Customer 的 Address 参数会不会影响测试结果,解决这个问题需要突出被测试的参数。
  使用硬编码的测试数据,在构建 Customer 时在代码中写死了6个参数,每次执行测试这6个参数都是不变的,假如每次构建Customer时需要将对象持久化在数据库中,那么第二次执行测试时就会因为字段写入冲突而失败(假如Customer的name字段必须保持唯一),所以每次执行TestCase时对某些字段要随机化每次都生成不一样的字段。
  由于我们已经把对象的创建拿到了createXXX方法中,所以可以很容易做下面的重构:
public void testAddItemQuantity_severalQuantity_v11(){
final int QUANTITY = 5;
//   Set up fixture
Address billingAddress = createAnAddress();
Address shippingAddress = createAnAddress();
Customer customer = createACustomer(new BigDecimal("30"),
billingAddress, shippingAddress);
Product product = createAProduct(new BigDecimal("19.99"));
Invoice invoice = createInvoice(customer);
// Exercise SUT
invoice.addItemQuantity(product, QUANTITY);
// Verify outcome
LineItem expected =
new LineItem(invoice, product, 5, new BigDecimal("30"),
new BigDecimal("69.96"));
assertContainsExactlyOneLineItem(invoice, expected);
}
private Product createAProduct(BigDecimal unitPrice) {
BigDecimal uniqueId = getUniqueNumber();
String uniqueString = uniqueId.toString();
return new Product(uniqueId.toBigInteger().intValue(),
}
  这种模式叫 Anonymous Creation Method 模式,把不重要的字段放在构造方法内部实现,把需要测试的字段通过方法的传入参数暴露出来。同样的道理,如果 Address 字段不影响测试,那么可以进一步的隐藏Address的构建:
public void testAddItemQuantity_severalQuantity_v12(){
//  Set up fixture
Customer cust = createACustomer(new BigDecimal("30"));
Product prod = createAProduct(new BigDecimal("19.99"));
Invoice invoice = createInvoice(cust);
// Exercise SUT
invoice.addItemQuantity(prod, 5);
// Verify outcome
LineItem expected = new LineItem(invoice, prod, 5,
new BigDecimal("30"), new BigDecimal("69.96"));
assertContainsExactlyOneLineItem(invoice, expected);
}
  最后一个问题,我们使用了魔数,这在学C语言的时候,老师已经强调过。需要把这些数字替换成更有意义的符号:
public void testAddItemQuantity_severalQuantity_v13(){
final int QUANTITY = 5;
final BigDecimal UNIT_PRICE = new BigDecimal("19.99");
final BigDecimal CUST_DISCOUNT_PC = new BigDecimal("30");
//   Set up fixture
Customer customer = createACustomer(CUST_DISCOUNT_PC);
Product product = createAProduct( UNIT_PRICE);
Invoice invoice = createInvoice(customer);
// Exercise SUT
invoice.addItemQuantity(product, QUANTITY);
// Verify outcome
final BigDecimal EXTENDED_PRICE = new BigDecimal("69.96");
LineItem expected =
new LineItem(invoice, product, QUANTITY,
CUST_DISCOUNT_PC, EXTENDED_PRICE);
assertContainsExactlyOneLineItem(invoice, expected);
}
  虽然如此“69.6”的出现依然是个问题,无法判断这个数字是怎么得到的,如果它是通过某种计算得到的,应该在测试代码中体现这种行为,所以这才是最终版本的重构:
public void testAddItemQuantity_severalQuantity_v14(){
final int QUANTITY = 5;
final BigDecimal UNIT_PRICE = new BigDecimal("19.99");
final BigDecimal CUST_DISCOUNT_PC =  new BigDecimal("30");
// Set up fixture
Customer customer = createACustomer(CUST_DISCOUNT_PC);
Product product = createAProduct( UNIT_PRICE);
Invoice invoice = createInvoice(customer);
// Exercise SUT
invoice.addItemQuantity(product, QUANTITY);
// Verify outcome
final BigDecimal BASE_PRICE =
UNIT_PRICE.multiply(new BigDecimal(QUANTITY));
final BigDecimal EXTENDED_PRICE =
BASE_PRICE.subtract(BASE_PRICE.multiply(
CUST_DISCOUNT_PC.movePointLeft(2)));
LineItem expected =
createLineItem(QUANTITY, CUST_DISCOUNT_PC,
EXTENDED_PRICE, product, invoice);
assertContainsExactlyOneLineItem(invoice, expected);
}
  总结
  上面的重构把原来硕大的方法体修改到11行,测试代码简洁明了了很多。但是有时候我们不禁要问这样的重构值得吗?因为我们实际上是把更多的代码变成工具方法转移到了别的地方。如果仅仅只写了这一个TestCase,这种重构略显尴尬,如果还需要写更多的TestCase,重构会让之前的付出有所收获,比如添加更多的TestCase时,只需要:
public void testAddLineItem_quantityOne(){
final BigDecimal BASE_PRICE = UNIT_PRICE;
final BigDecimal EXTENDED_PRICE = BASE_PRICE;
//   Set up fixture
Customer customer = createACustomer(NO_CUST_DISCOUNT);
Invoice invoice = createInvoice(customer);
//   Exercise SUT
invoice.addItemQuantity(PRODUCT, QUAN_ONE);
// Verify outcome
LineItem expected =
createLineItem( QUAN_ONE, NO_CUST_DISCOUNT,
EXTENDED_PRICE, PRODUCT, invoice);
assertContainsExactlyOneLineItem( invoice, expected );
}
public void testChangeQuantity_severalQuantity(){
final int ORIGINAL_QUANTITY = 3;
final int NEW_QUANTITY = 5;
final BigDecimal BASE_PRICE =
UNIT_PRICE.multiply(   new BigDecimal(NEW_QUANTITY));
final BigDecimal EXTENDED_PRICE =
BASE_PRICE.subtract(BASE_PRICE.multiply(
CUST_DISCOUNT_PC.movePointLeft(2)));
//   Set up fixture
Customer customer = createACustomer(CUST_DISCOUNT_PC);
Invoice invoice = createInvoice(customer);
Product product = createAProduct( UNIT_PRICE);
invoice.addItemQuantity(product, ORIGINAL_QUANTITY);
// Exercise SUT
invoice.changeQuantityForProduct(product, NEW_QUANTITY);
// Verify outcome
LineItem expected = createLineItem( NEW_QUANTITY,
CUST_DISCOUNT_PC, EXTENDED_PRICE, PRODUCT, invoice);
assertContainsExactlyOneLineItem( invoice, expected );
}
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号