当前位置:文档之家› 软件测试之单元测试

软件测试之单元测试

软件测试之单元测试我们所做的产品测试包括了下文所说的软件测试词汇表中的大部分,也就是“单元测试”,组件测试,系统测试,集成测试,压力测试和验收测试。

开发团队成员做的或者参与的是“单元测试”,集成测试。

这里的单元测试我加了引号是因为看完下面的文章,我发现我们所做的单元测试并不是严格意义上的单元测试,叫功能测试比较恰当。

下文所说的功能测试遇到的问题在我们的实际项目中也遇到了。

希望日后有机会改进。

1. 你做的是单元测试么?我看到过至少6个公司因为他们有“单元测试(unit test)”而满脸自豪。

而我们看到的是这种“单元测试”结果会是一个麻烦。

其他人讨论单元测试有多么伟大,但是它确实变得让人痛苦不堪。

这种测试需要45分钟才能跑完,还有你对代码只做了一点改动,但却破坏了7个测试用例”。

这些家伙用的是一堆功能测试(functional test)。

他们掉入了一个流行的思维陷阱,认为只要是使用Junit来运行的测试用例,就必须是单元测试。

你只需要一点点词汇量,90%的问题就都能解决。

2. 软件测试词汇表单元测试(unit test):可测试代码的最小的一部分。

通常是一个单一的方法,不会使用其它方法或者类。

非常快!上千个单元测试能够在10秒以内跑完!单元测试永远不会使用:数据库一个app服务器(或者任何类型的服务器)文件/网络 I/O或者文件系统另外的应用控制台(System.out,system.err等等)日志大多数其他类(但不包括DTO‘s,String,Integer,mock和一些其他的类)单元测试几乎总是回归测试套件(regression suite)的一部分。

回归测试套件(Regression Suite):能够立刻被运行的测试用例的集合。

一个例子就是放在一个特定文件夹中的能够被Junit运行的所有测试用例。

一个开发人员能够在一天中把一个单元测试回归套件运行20次或者他们可能一个月跑两次功能测试回归套件。

功能测试(Functional Test):比一个单元要大,比一个完整的组件测试要小。

通常为工作在一起的的几个方法/函数/类。

上百的测试用例允许运行几个小时。

大部分功能测试是功能测试回归套件的一部分。

通常由Junit来运行。

集成测试(Integration Test):测试两个或者更多的组件一起工作的情况。

有时候是回归套件的一部分。

组件测试(Component Test):运行一个组件。

经常由QA,经理,XP客户等等来执行。

这种类别的测试不是回归套件的一部分,它不由Junit来执行。

组件验收测试(Component Acceptance Test C.A.T.):作为正常流程的一部分,它是在众多人面前运行的一个组件测试。

由大家共同决定这个组件是不是满足需求标准。

系统测试(system Test):所有的组件在一起运行。

系统验收测试(System Acceptance Test S.A.T.):作为正常流程的一部分,它是在众多人面前运行的一个系统测试,由大家来共同决定这个系统是不是满足需求标准。

压力测试(Stress Tests):另外一个程序加载一个组件,一些组件或者整个系统。

我曾经看到过把一些小的压力测试放到回归功能测试中来进行——这是测试并发代码的一个很聪明的做法。

Mock:在单元测试或者功能测试中使用的一些代码,通过使用这些代码来确保你要测试的代码不会去使用其它的产品代码(production code)。

一个mock类覆盖了一个产品类中的所有public方法,它们用来插入到尝试使用产品类的地方。

有时候一个mock类用来实现一个接口,它替换了用来实现同样接口的产品代码。

Shunt:有点像继承(extends)产品代码的mock类,只是它的意图不是覆盖所有的方法,而只是覆盖足够的代码,所以你能够测试一些产品方法,同时mock剩余的产品方法。

如果你想测试一个可能会使用I/O的类它会变得尤为有用,你的shunt能够重写I/O方法同时来测试非I/O方法。

3. 使用太多功能测试(functional test)会有麻烦不要误解我的意思。

功能测试有很大的价值。

我认为一个测试良好的app将会有一个功能测试的回归套件和一个非回归功能测试的集合。

通常情况下对于一磅产品代码,我都想看到两磅单元测试代码和两盎司(注:1磅=16盎司)功能测试代码。

但是在太多的项目中我看到的现象是没有一丁点单元测试,却有一磅功能测试。

下面的两幅图表明了一些类的使用情况。

用一些功能测试来测试这些类一块工作的情况。

修复一个类的bug会破坏许多功能测试上面的情况我看到过多次。

其中的一个例子是一个很小的改动破坏了47个测试用例。

我们通过开会来决定这个bug是不是要被留在代码中。

最后决定我们要留足够的时间来fix所有的case。

几个月过去了,事情依然糟糕。

结果是这个工程变的更加灵活。

4. 功能测试认知纠错“通过只编写功能测试用例,我可以写更少的测试代码,同时测试更多的功能代码!”这是真的!但是这会以你的工程变得更加脆弱为代价。

另外,如果不使用单元测试,你的应用有些地方很难被测试。

同时达到最好的覆盖率和灵活性是使用功能测试和单元测试的组合,其中单元测试的比重要大,功能测试的比重要小。

“我的业务逻辑是让所有的类一块工作,所以只测试一个方法是没有意义的。

”我建议你单独测试所有的方法。

同时我也并不建议你不使用功能测试,它们也是有价值的。

“我不介意我的单元测试组件会花费几分钟来运行”但是你的团队中的其他人介意么?你的team lead介意么?你的manager呢?如果它花费几分钟而不是几秒钟,你还会在一天的时间把整个测试套件运行多次么?在什么情况下人们根本不会运行测试?回到顶部5. 单元测试mock基础下面是单元测试的一个简单例子,测试各种情况却不依赖其他方法。

1415 assertEquals( "-111.44" , Normalize.longitude( "-111.44w" ) );1617 assertEquals( "-111.44" , Normalize.longitude( "-111.44W" ) );1819 assertEquals( "-111.44" , Normalize.longitude( "-111.44 w" ) );2021 assertEquals( "-111.44" , Normalize.longitude( "-111.44 W" ) );2223 assertEquals( "-111.44" , Normalize.longitude( "-111.44" ) );2425 assertEquals( "-111.44" , Normalize.longitude( "111.44-" ) );2627 assertEquals( "-111.44" , Normalize.longitude( "111.44 -" ) );2829 assertEquals( "-111.44" , Normalize.longitude( "111.44west" ) );3031 // ...3233 }当然,任何人都能为上面这种情况做单元测试。

但是大部分业务逻辑都使用了其它业务逻辑:8 String buildingID = servletData.getParameter("buildingID");910 if ( able( species ) && able( buildingID ) )1112 {1314 FarmEJBRemote remote = FarmEJBUtil.getHome().create();1516 remote.addAnimal( species , buildingID );1718 }1920 }2122 }这里不仅仅调用了其他业务逻辑,还调用了应用服务器!可能还会访问网络!上千次的调用可能会花费不少于10秒的时间。

另外对EJB的修改可能会破坏我对这个方法的测试!所以我们需要引入一个mock对象。

首先是创建mock。

如果FarmEJBRemote是一个类,我将会继承(extend)它并且重写(override)它所有的方法。

但是既然它是一个接口,我会编写一个新类并实现(implement)所有方法:89 int addAnimal_calls = 0;1011 public void addAnimal( String species , String buildingID )1213 {1415 addAnimal_species = species ;1617 addAnimal_buildingID = buildingID ;1819 addAnimal_calls++;2021 }2223 }这个类什么都没做,只是携带了单元测试和需要被测试代码之间要交互的数据。

这个类会让你感觉不舒服么?应该是这样。

在我刚接触它的时候有两件事情把我弄糊涂了:类的属性不是private的,并且命名上有下划线。

如果你需要mock java.sql.connection。

总共有40个方法!为每个方法的各个参数,返回值和计数都实现Getters和setters?嗯…稍微想一下…我们把属性声明为private是为了封装,把事情是如何做的封装在内部,于是日后我们就可以修改我们的业务逻辑代码而不用破坏决定要进入我们的内脏的其他代码(也就是要调用我们的业务逻辑的代码)。

但这对于mock来说并不适用,不是么?根据定义,mock没有任何业务逻辑。

进一步来说,它没有任何东西不是从其他地方拷贝过来的。

所有的mock对象都能100%在build阶段生成!..所以虽然有时候我仍然觉的这么实现Mock有一点恶心,但是最后我会重拾自信,这是最好的方法了。

只是闻起来会让你有些不舒服,但是效果比使用其它方法好多了。

现在我需要使用mock代码来替代调用应用服务器的部分。

我对需要使用mock的地方做了高亮:1 public class FarmServlet extends ActionServlet23 {45 public void doAction( ServletData servletData ) throws Exception67 {89 String species = servletData.getParameter("species");1011 String buildingID = servletData.getParameter("buildingID");1213 if ( able( species ) && able( buildingID ) )1415 {1617 FarmEJBRemote remote = FarmEJBUtil.getHome().create();1819 remote.addAnimal( species , buildingID );2021 }2223 }2425 }首先,让我们把这句代码从其他猛兽中分离出来:23 {45 private FarmEJBRemote getRemote()7 {9 return FarmEJBUtil.getHome().create();11 }1213 public void doAction( ServletData servletData ) throws Exception1415 {1617 String species = servletData.getParameter("species");1819 String buildingID = servletData.getParameter("buildingID");2021 if ( able( species ) && able( buildingID ) )2223 {2425 FarmEJBRemote remote = getRemote();2627 remote.addAnimal( species , buildingID );2829 }3031 }3233 }这有一点痛..我将会继承我的产品类然后重写getRemote(),于是我可以把mock代码混入到这个操作中了。

相关主题