《xUnit Test Patterns》学习笔记5 - xUnit基础

简介:

这几节我看的比较快一些,因为内容之间其实是有联系的,所以合在一起做一个笔记。6-10节主要介绍了什么是Fixture,如何保证一个Fresh Fixture,如何使用Setup,Tearndown,如何进行验证(Verify),等等。

什么是Fixture?

The test fixture is everything we need to have in place to exercise the SUT.

从作者的英文解释来看,Fixture确实是一个比较难定义的东西,所以作者用了everything这个词。

什么是Fresh Fixture?

一个测试案例一般都包含以下几个步骤:

  1. Setup
  2. Exercise
  3. Verify
  4. Teardown

Fresh Fixture是指每个案例执行时,都会生成一个全新的Fixture,好处是不受其他案例的影响。避免了Interacting Tests(之前有提到的)。

image

什么是Setup?

Setup是案例的准备阶段,主要有三种实现方式:In-line Fixture Setup, Delegated Setup, Implicit Setup。推荐使用的是Implicit Setup。

In-line Fixture Setup

直接在测试方法内部做一些具体的Setup操作 :

复制代码
public   void  testStatus_initial() {
      
//  In-line setup
      Airport departureAirport  =   new  Airport( " Calgary " " YYC " );
      Airport destinationAirport 
=   new  Airport( " Toronto " " YYZ " );
      Flight flight 
=   new  Flight( flightNumber,
                                 departureAirport,
                                 destinationAirport);
      
//  Exercise SUT and verify outcome
      assertEquals(FlightState.PROPOSED, flight.getStatus());
      
//  tearDown:
      
//     Garbage-collected
}
复制代码

缺点是容易造成很多重复的代码,不易维护。

Delegated Setup

相比In-line Fixture Setup,将里面具体的Setup操作提取出来,作为一个公用的方法,提高了复用性。

复制代码
public   void  testGetStatus_inital() {
       
//  Setup
      Flight flight  =  createAnonymousFlight();
      
//  Exercise SUT and verify outcome
      assertEquals(FlightState.PROPOSED, flight.getStatus());
      
//  Teardown
      
//  Garbage-collected
}
复制代码


Implicit Setup

几乎所有的xUnit家族的框架都支持SetUp,比如,使用Google Test中指定的函数名SetUp,NUnit使用[Setup]Attribute。这种方法,不需要我们自己去调用Setup方法,框架会在创建Fresh Fixture后调用Setup。因此,我们只管实现SetUp方法。

复制代码
Airport departureAirport;
Airport destinationAirport;
Flight flight;
public   void  testGetStatus_inital() {
   
//  Implicit setup
   
//  Exercise SUT and verify outcome
   assertEquals(FlightState.PROPOSED, flight.getStatus());
}
public   void  setUp()  throws  Exception{
   
super .setUp(); 
   departureAirport 
=   new  Airport( " Calgary " " YYC " );
   destinationAirport 
=   new  Airport( " Toronto " " YYZ " );
   BigDecimal flightNumber 
=   new  BigDecimal( " 999 " );
   flight 
=   new  Flight( flightNumber , departureAirport,
                           destinationAirport);
}
复制代码


什么是Teardown?

为了保证每个案例都拥有一个Fresh Fixture,必须在案例的结束时做一些清理操作,这就是Teardown。和Setup一样,Teardown也有三种实现方式:In-line Fixture Teardown, Delegated Teardown, Implicit Teardown。同样,推荐使用Implicit Teardown。

image


什么是Shared Fixture?

多个测试方法共用一个Fixture,这时,Setup只会在第一个测试方法执行时被执行。gtest中,同时还拥有一个公共的TearDownTestCases方法。

image


Result Verification

前面说过,测试案例必须拥有Self-Checking的能力。Verification分两种:State Verification和Behavior Verification。

State Verification

执行SUT后,验证SUT的状态:

image

验证时,可以使用Build-in Assertions,比如xUnit框架提供的assertTrue, assertEquals等方法。或者Custom Assertion等等。

Behavior Verification

不仅仅验证SUT的状态,同时还对SUT的行为对外部因素造成的影响进行验证。

image

比如下面这个例子:

复制代码
public   void  testRemoveFlightLogging_recordingTestStub()
            
throws  Exception {
      
//  fixture setup
      FlightDto expectedFlightDto  =  createAnUnregFlight();
      FlightManagementFacade facade 
=
            
new  FlightManagementFacadeImpl();
      
//     Test Double setup
      AuditLogSpy logSpy  =   new  AuditLogSpy();
      facade.setAuditLog(logSpy);
      
//  exercise
      facade.removeFlight(expectedFlightDto.getFlightNumber());
      
//  verify
      assertEquals( " number of calls " 1 ,
                   logSpy.getNumberOfCalls());
      assertEquals(
" action code " ,
                   Helper.REMOVE_FLIGHT_ACTION_CODE,
                   logSpy.getActionCode());
      assertEquals(
" date " , helper.getTodaysDateWithoutTime(),
                   logSpy.getDate());
      assertEquals(
" user " , Helper.TEST_USER_NAME,
                   logSpy.getUser());
      assertEquals(
" detail " ,
                   expectedFlightDto.getFlightNumber(),
                   logSpy.getDetail());
}
复制代码

除此之外,我们还可以使用一些Mock框架,使用基于行为的验证方式,这种方式,不需要我们显式的调用验证的方法。(Expected Behaivor Specification)

复制代码
public   void  testRemoveFlight_JMock()  throws  Exception {
      
//  fixture setup
      FlightDto expectedFlightDto  =  createAnonRegFlight();
      FlightManagementFacade facade 
=
            
new  FlightManagementFacadeImpl();
      
//  mock configuration
      Mock mockLog  =  mock(AuditLog. class );
      
mockLog.expects(once()).method( " logMessage " )
               .with(eq(helper.getTodaysDateWithoutTime()),
                     eq(Helper.TEST_USER_NAME),
                     eq(Helper.REMOVE_FLIGHT_ACTION_CODE),
                     eq(expectedFlightDto.getFlightNumber()));
      
//  mock installation
      facade.setAuditLog((AuditLog) mockLog.proxy());
      
//  exercise
      facade.removeFlight(expectedFlightDto.getFlightNumber());
      
//  verify
      
//  verify() method called automatically by JMock
}
复制代码


如何使测试代码变得简洁,减少重复?

Expected Object

需要比较对象内部很多属性时,使用对象比较会更简单。

原有案例代码:

复制代码
public   void  testInvoice_addLineItem7() {
      LineItem expItem 
=   new  LineItem(inv, product, QUANTITY);
      
//  Exercise
      inv.addItemQuantity(product, QUANTITY);
      
//  Verify
      List lineItems  =  inv.getLineItems();
      LineItem actual 
=  (LineItem)lineItems.get( 0 );
      assertEquals(expItem.getInv(), actual.getInv());
      assertEquals(expItem.getProd(), actual.getProd());
      assertEquals(expItem.getQuantity(), actual.getQuantity());
}
复制代码

改进后:

复制代码
public   void  testInvoice_addLineItem8() {
      LineItem expItem 
=   new  LineItem(inv, product, QUANTITY);
      
//  Exercise
      inv.addItemQuantity(product, QUANTITY);
      
//  Verify
      List lineItems  =  inv.getLineItems();
      LineItem actual 
=  (LineItem)lineItems.get( 0 );
      
assertEquals( " Item " , expItem, actual);
}
复制代码

 

Custom Assersions

需要验证的细节很多时,可以自己定义一个Assersion,隐藏掉这些细节。比如:

复制代码
static   void  assertLineItemsEqual(
                     String  msg, LineItem exp, LineItem act) {
      assertEquals (msg
+ "  Inv " ,  exp.getInv(), act.getInv());
      assertEquals (msg
+ "  Prod " , exp.getProd(), act.getProd());
      assertEquals (msg
+ "  Quan " , exp.getQuantity(), act.getQuantity());
}
复制代码

 

Verification Methods

和Custom Asserions很像,唯一不同的是,Custom Assertion只包含验证的代码,Verification Methods同时还包含对SUT的操作。比如:

复制代码
void  assertInvoiceContainsOnlyThisLineItem(
                                     Invoice inv,
                                     LineItem expItem) {
      
List lineItems  =  inv.getLineItems();
      assertEquals(
" number of items " , lineItems.size(),  1 );
      LineItem actual 
=  (LineItem)lineItems.get( 0 );
      assertLineItemsEqual(
"" ,expItem, actual);
}
复制代码

 

Parameterized and Data-Driven Tests

对于测试逻辑一致,只是测试数据有不同的测试案例,适合使用参数化测试,或者叫数据驱动测试。比如,Google Test就很好的提供了参数化的测试,见:

玩转 Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化

通过参数化,可以简化测试代码,不需要为大量不同的输入数据分别编写测试案例。

Avoiding Conditional Test Logic

验证时,不要使用一些条件相关的逻辑!比如,不要使用if, loop之类的语句!下面是一个例子:

使用if的情况:

复制代码
List lineItems  =  invoice.getLineItems();
if  (lineItems.size()  ==   1 ) {
   LineItem expected 
=
      
new  LineItem(invoice, product, 5 ,
                   
new  BigDecimal( " 30 " ),
                   
new  BigDecimal( " 69.96 " ));
   LineItem actItem 
=  (LineItem) lineItems.get( 0 );
   assertEquals(
" invoice " , expected, actItem);
else  {
   fail(
" Invoice should have exactly one line item " );
}
复制代码

可以看出,上面的写法是不好的,验证中有逻辑判断意味着有可能案例不够单一,使得案例难以理解。因此,比较好的是改成下面的方式:

复制代码
List lineItems  =  invoice.getLineItems();
assertEquals( " number of items " , lineItems.size(),  1 );
LineItem expected 
=
   
new  LineItem(invoice, product,  5 ,
                
new  BigDecimal( " 30 " ),
                
new  BigDecimal( " 69.96 " ));
LineItem actItem 
=  (LineItem) lineItems.get( 0 );
assertEquals(
" invoice " , expected, actItem);
复制代码

 

Working Backward

一个编写测试案例的小技巧或者说是习惯吧,就是实现一个测试案例时,从最后一行开始写起,比如,先写Assertions。可以一试。

Using Test-Driven Development to Write Test Utility Method

我们经常实现一些测试用的辅助方法,这些方法在实现过程中,使用TDD的方式去实现,编写一些简单的测试案例,保证辅助方法也是正确的。也就是说,测试案例代码本身也是需要被测试的。

 

 

本文转自CoderZh博客园博客,原文链接:http://www.cnblogs.com/coderzh/archive/2010/01/24/xUnit-Test-Patterns-5.html,如需转载请自行联系原作者

相关文章
|
13天前
|
安全 索引 Python
【攻防世界】Web_python_template_injection
【攻防世界】Web_python_template_injection
|
27天前
|
测试技术 编译器 持续交付
【Conan 入门教程 】深入理解Conan中的测试包:test_package目录的精髓
【Conan 入门教程 】深入理解Conan中的测试包:test_package目录的精髓
55 0
|
7月前
|
测试技术 数据库
ABAP TEST-SEAM 的使用方法
ABAP TEST-SEAM 的使用方法
35 0
|
自然语言处理 测试技术 Python
基于UIAutomation+Python+Unittest+Beautifulreport的WindowsGUI自动化测试框架common目录解析
基于UIAutomation+Python+Unittest+Beautifulreport的WindowsGUI自动化测试框架common目录解析
439 0
基于UIAutomation+Python+Unittest+Beautifulreport的WindowsGUI自动化测试框架common目录解析
|
资源调度 前端开发 JavaScript
|
测试技术 .NET 开发框架
使用xUnit为.net core程序进行单元测试(4)
第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 第3部分: http://www.
1029 0
|
测试技术 .NET 开发框架
使用xUnit为.net core程序进行单元测试(2)
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] public void CalculateFullN...
1041 0