标签 Unittest 下的文章

CppUnit入门实践-Tony与Alex的对话系列

Tony : Hi Alex ! you just looks like drowing in your project. what is up?
Alex : 我们的项目要求引入单元测试,but i've no experience in unit test.
Tony : i think cppunit is your best choice.
Alex : 是的,我刚从网上把它down了下来,正准备研究它呢。
Tony : Really ? I have done some practice on unit test before. would you like me to join you?
Alex : Oh Tony, I'm so glad that you could help me !
Tony : My pleasue !
Alex : 我们从哪里开始呢?
Tony : The simplest case! 我们拿一个最简单的例子吧。now we have a class with the name "SimpleCalculator" and it has four basic methods 'add', 'sub', 'mul' and 'div', All we should do is to test whether these methods run as same as we expect. First of all , complete the "SimpleCalculator" class, Alex.
Alex : It is simple!

//SimpleCalculator.h
class SimpleCalculator{
 public :
  int add(int a, int b);
  int sub(int a, int b);
  int mul(int a, int b);
  int div(int a, int b);
};

//SimpleCalculator.cpp
int SimpleCalculator::add(int a, int b){
 return a+b;
}

int SimpleCalculator::sub(int a, int b){
 return a-b;
}

int SimpleCalculator::mul(int a, int b){
 return a*b;
}

int SimpleCalculator::div(int a, int b){
 return a/b;
}

Alex : 这里简单点,div方法没有考虑0作除数的异常情况。
Tony : 可以。还记得我上次讲的测试驱动开发么,不过今天我们不是用它,我们只做些简单的东西,目的就是为了熟悉工具的使用。
Alex : 那我们是不是也应该列出一个test case的list亚?
Tony : 没错。
Alex : 我来随意写几个吧。“add(5,6) == 11” 、“sub(5,6)==-1”、“mul(5,6) == 30”和“div(12,6) == 2”。
Tony : 然后我们一起来学习一下CppUnit的帮助文档吧。

(Tony and Alex are reading the doc of cppunit.)

Tony : 学到了些什么?
Alex : 看来这些xUnit框架的测试工具在概念上几乎是一致的,像TestCase、TestFixture和TestSuite这些概念都大同小异。
Tony : 不错,单元测试在于测试思想,工具只是个必要条件而已,工具并不能决定你的测试就是一个好的测试。下面你就按你理解的CppUnit去做吧。

Alex : Ok. 按照书中所说,我们一次要测试多个method,最好使用TestFixture。我是这样写的,你看看。

#include "SimpleCalculator.h"
#include "CppUnit/TestCase.h"
#include "CppUnit/TestResult.h"
#include "CppUnit/TextOutputter.h"
#include "CppUnit/TestResultCollector.h"
#include "CppUnit/TestCaller.h"
#include "CppUnit/extensions/HelperMacros.h"

class SimpleCalcTest : public CPPUNIT_NS::TestFixture{
private :
 SimpleCalculator * sc;

public:
 virtual void setUp(){
         sc = new SimpleCalculator();
     }
     virtual void tearDown(){
         delete sc;  
     }
 
 void testAdd(){       
         CPPUNIT_ASSERT_EQUAL( sc->add(5,6), 11);
     }

 void testSub(){       
         CPPUNIT_ASSERT_EQUAL( sc->sub(5,6), -1 );
     }

     void testMul(){       
         CPPUNIT_ASSERT_EQUAL( sc->mul(5,6), 30 );
     }

 void testDiv(){       
         CPPUNIT_ASSERT_EQUAL( sc->div(12,6), 2 );
     }
};

我们的主函数如下:
int main()
{
    CPPUNIT_NS::TestResult r;
    CPPUNIT_NS::TestResultCollector result;
    r.addListener( &result );

    CPPUNIT_NS::TestCaller testCase1( "testAdd", &SimpleCalcTest::testAdd );
    CPPUNIT_NS::TestCaller testCase2( "testSub", &SimpleCalcTest::testSub );
    CPPUNIT_NS::TestCaller testCase3( "testMul", &SimpleCalcTest::testMul );
    CPPUNIT_NS::TestCaller testCase4( "testDiv", &SimpleCalcTest::testDiv );
   
    testCase1.run( &r );
    testCase2.run( &r );
    testCase3.run( &r );
    testCase4.run( &r );
   
    CPPUNIT_NS::TextOutputter out( &result, std::cout );
    out.write();
    return 0;
}

Tony : 我觉得可行。运行一下,看看如何。
Alex : 输出结果如下:
OK (4 tests)

Tony : 这的确是一种可行的办法,不过你回想一下我们一起学习的doc中的内容,看看是否还有改进的余地了。现在如果你要在SimpleCalcTest类中加一个测试用例方法,不仅仅SimpleCalcTest要修改,我们的main函数也需要修改,还记得JUnit中有什么概念来支持么?

Alex : 你不提我还真的记不起来了,JUnit中有TestSuite,刚才在cppunit doc中我也看到了suite方法,也许会帮得上忙,稍等一下我再翻翻文档….

Alex : 我找到了。的确有更为简单的方法。我修改一下,引用的头文件不变。

class SimpleCalcTest : public CPPUNIT_NS::TestFixture{

    CPPUNIT_TEST_SUITE( SimpleCalcTest );
        CPPUNIT_TEST( testAdd );
        CPPUNIT_TEST( testSub );
        CPPUNIT_TEST( testMul);
        CPPUNIT_TEST( testDiv );       
    CPPUNIT_TEST_SUITE_END();

private :
 SimpleCalculator * sc;

public:
 virtual void setUp(){
         sc = new SimpleCalculator();
     }
     virtual void tearDown(){
         delete sc;  
     }
 
 void testAdd(){       
         CPPUNIT_ASSERT_EQUAL( sc->add(5,6), 11);
     }

 void testSub(){       
         CPPUNIT_ASSERT_EQUAL( sc->sub(5,6), -1 );
     }

     void testMul(){       
         CPPUNIT_ASSERT_EQUAL( sc->mul(5,6), 30 );
     }

 void testDiv(){       
         CPPUNIT_ASSERT_EQUAL( sc->div(12,6), 2 );
     }
};

CPPUNIT_TEST_SUITE_REGISTRATION( SimpleCalcTest );

主函数修改后如下:
int main()
{
    CPPUNIT_NS::TestResult r;
    CPPUNIT_NS::TestResultCollector result;
    r.addListener( &result );

    CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()->run( &r );
    CPPUNIT_NS::TextOutputter out( &result, std::cout );
    out.write();
    return 0;
}

CppUnit利用宏来解决Suite的问题。在你的TestCase定义里面写入如下的这段代码:
CPPUNIT_TEST_SUITE( YourTestCase );
        CPPUNIT_TEST( testXX);
   …//
CPPUNIT_TEST_SUITE_END();
这段代码实际上是定义了一个函数suite,这个函数返回了一个包含了所有CPPUNIT_TEST定义的测试用例的一个测试集。CPPUNIT_TEST_SUITE_REGISTRATION通过静态注册把这个测试集注册到全局的测试树中,最后通过CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()生成一个包含所有测试用例的测试并且运行。这样的话,一旦要添加新的测试用例函数,我们只需要修改SimpleCalcTest类即可。

Tony : Well done! Alex你独立解决问题的能力越来越强了。相信做到这你已经心里有底儿了,再往后就是在你的实际项目中摸索CppUnit的使用经验了。

Alex : 呵呵。谢谢夸奖!

再谈Mock Object

发现静寂的夜能让我的思维加快。

Mock Object进行Unit Test已经一周多了,发现以前对Mock Object还是很肤浅,即使是现在我也不敢说我对Mock Object的理解就一定正确。

这篇blog假设你已经熟悉JUnit、了解Mock和TDD
如果你是直接开始使用JMock 、Easy Mock或者是MockMaker等Mock Object框架的,我建议你简单了解一下Mock Object的演化历史,这样你在使用Mock Object时才会更有的放矢。

Mock object有关键的两个概念:
* 建立起环境的概念。在www.mockobjects.com的faq中有一句是这样叙述的“I think the fundamental thing to remember about Mock objects is that they are just that – simple shells or placeholders.” Mock object只是替代了被测Object环境的代码。
* test assertions被隐藏在Mock object内部实现中了,在你的test case中用来verify被测代码与Mock object的交互。

在单元测试中,人们发现有一些问题(这些问题在我的“认识Mock Object”中已列出)常见单元测试工具(如JUnit)并不能很好、很便捷的解决,这样Mock object被引入来解决这些问题。

最初人们手工编写Mock Object。随着测试问题越来越复杂,人们自己手工编写的Mock object越来越多,人们开始将编写Mock Object过程中一些通用的东西抽象出来,形成了一些Mock Object Lib,以帮助开发人员快速得到自己需要的Mock Objects。

当前的Mock Object Lib有多种,大致可分为两类:
* Static Mock Objects Lib
* Dynamic Mock Objects Lib

这里简单举例说明一下不同类型Mock object lib的使用方法,并与手工编写进行对比。

问题:我们在测试某个class时,我们需要与MyInterface这个接口进行交互,而该接口尚未实现,这时我们使用Mock object来替代。

接口MyInterface:
public interface MyInterface {
    public SomeClass getSomething();
    public void setSomething(SomeClass aSomething)
        // Other methods omitted…
}

* 手工编写mock object:
public class HandcraftMockMyInterface implements MyInterface {
    public SomeClass getSomething() {
        return something;
    }
    public void setSomething(SomeClass aSomething){
        this.someting = aSomething;
    }
    private SomeClass something;
    //others
}

* 使用Static Mock Object Lib编写Mock Interface:
public class StaticMockMyInterface extends MockObject implements MyInterface{
    private final ExpectationValue something = new ExpectationValue("something");  
    public void setSomething(SomeClass something){
        this.something.setActual(something);
    }
    public void setExpectedSomething(SomeClass something){
        this.something.setExpected(something);
    }
    public void SomeClass getSomething(){
        return this. something;
    }  
}

* 使用Dynamic Mock Object Lib编写Mock Interface:(以JMock为例)
Mock dynamicMockMyInterface = new Mock(MyInterface.class);
MyInterface mi = (MyInterface) dynamicMockMyInterface.proxy();

SomeClass someThing = new SomeClass();
dynamicMockMyInterface.expects(once()).method(“setSomething”).with(eq(someThing));
dynamicMockMyInterface.expects(once()).method(“getSomething”).will(returnValue(someThing));

//执行你的测试代码,比如你的tested object与MyInterface mi交互的代码。

dynamicMockMyInterface.verify();

Mock Object的使用流程
- Setup any state — setup the fixture for your test
- Set expectations for the test
- Run the target code
- Verify that your expectations have been met

Mock Object Practical Experience
- 好的设计是容易测试的设计
- 尽量让测试在内存中完成,不要在硬盘上留下“垃圾”
- 面向接口使用Mock Object。better to implement interface , not inherit class

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats