精华内容
下载资源
问答
  • 单元测试什么

    2020-01-02 11:33:18
    单元测试是开发人员编写的、用于检测在特定条件下目标代码正确性的代码。单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体...

    单元测试是开发人员编写的、用于检测在特定条件下目标代码正确性的代码。单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

    单元测试位于测试金字塔的最底层,越向上反馈的时间越长,实现的成本也越高。

    测试的好处不仅仅是在编码时可以快速验证我们的程序是否满足预期,更大的好处是未来修改另一个功能时,可以帮助我们快速回归之前的所有测试,以确定此修改的影响范围。

    单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。

    单元测试是由程序员自己来完成,最终受益的也是程序员自己。程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。

    其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本。进行充分的单元测试,是提高编码质量,降低开发成本的必由之路。

    要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。

    有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。对于C++来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头文件中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。

    使用效果

    单元测试会为我们的承诺做保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码。

    什么时候测试?单元测试越早越好,早到什么程度?极限编程(Extreme Programming,或简称XP)讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的直接返回一个合适值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。

    由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。

    关于桩代码,单元测试应避免编写桩代码。桩代码就是用来代替某些代码的代码,例如,产品函数或测试函数调用了一个未编写的函数,可以编写桩函数来代替该被调用的函数,桩代码也用于实现测试隔离。采用由底向上的方式进行开发,底层的代码先开发并先测试,可以避免编写桩代码,这样做的好处有:减少了工作量;测试上层函数时,也是对下层函数的间接测试;当下层函数修改时,通过回归测试可以确认修改是否导致上层函数产生错误。

    误解

    1、它浪费了太多的时间

    一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到实际的系统开始启动工作了。单元测试这样的活动也许会被看作是通往这个阶段点的道路上的障碍, 推迟了对整个系统进行联调这种真正有意思的工作启动的时间。

    系统能够正常工作的可能性是很小的,更多的情况是充满了各式各样的Bug。而且当这个系统投入使用时也无法确保它能够可靠运行。

    在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。

    2、它仅仅是证明这些代码做了什么

    这是那些没有首先为每个单元编写一个详细的规格说明而直接跳到编码阶段的开发人员提出的一条普遍的抱怨, 当编码完成以后并且面临代码测试任务的时候,他们就阅读这些代码并找出它实际上做了什么,把他们的测试工作基于已经写好的代码的基础上。

    如果他们首先写好一个详细的规格说明,测试能够以规格说明为基础。代码就能够针对它的规格说明,而不是针对自身进行测试。这样的测试能找到更多的编码错误,甚至是一些规格说明中的错误。好的规格说明可以使测试的质量更高,所以最后的结论是高质量的测试需要高质量的规格说明。

    这个过程的主要输入条件是要阅读那些程序代码和注释, 主要针对这个单元, 及调用它和被它调用的相关代码。画出流程图是非常有帮助的,你可以用手工或使用某种工具。可以组织对这个概要规格说明的走读(Review),以确保对这个单元的说明没有基本的错误, 有了这种最小程度的代码深层说明,就可以用它来设计单元测试了。

    3、我是个很棒的程序员, 我是不是可以不进行单元测试?

    每个人都会犯错误。真正的软件系统是非常复杂的。真正的软件系统不可以寄希望于没有进行广泛的测试和Bug修改过程就可以正常工作。

    编码不是一个可以一次性通过的过程。软件产品必须进行维护以对操作需求的改变作出反应, 并且要对最初的开发工作遗留下来的Bug进行修改。依靠那些原始作者进行修改吗?在开发人员做出修改后进行可重复的单元测试可以避免产生那些令人不快的负作用。

    4、集成测试将会抓住所有的Bug。

    规模越大的代码集成意味着复杂性就越高。如果软件的单元没有事先进行测试,开发人员很可能会花费大量的时间仅仅是为了使软件能够运行,而任何实际的测试方案都无法执行。

    一旦软件可以运行了,开发人员又要面对这样的问题:在考虑软件全局复杂性的前提下对每个单元进行全面的测试。这是一件非常困难的事情。

    最后的结果是测试将无法达到它所应该有的全面性。一些缺陷将被遗漏,并且很多Bug将被忽略过去。

    让我们类比一下,假设我们要清洗一台已经完全装配好的食物加工机器!无论你喷了多少水和清洁剂,一些食物的小碎片还是会粘在机器的死角位置,只有任其腐烂并等待以后再想办法。但如果这台机器是拆开的, 这些死角也许就不存在或者更容易接触到了,并且每一部分都可以毫不费力的进行清洗。

    成本效率

    无论什么时候作出修改都需要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量都得到最好的保证。Bug发现的越晚,修改它所需的费用就越高,因此从经济角度来看, 应该尽可能早的查找和修改Bug。在修改费用变的过高之前,单元测试是一个在早期抓住Bug的机会。

    相比后阶段的测试,单元测试的创建更简单,维护更容易,并且可以更方便的进行重复。

    结论

    经验表明一个尽责的单元测试方法将会在软件开发的某个阶段发现很多的Bug,并且修改它们的成本也很低。无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。在提供了经过测试的单元的情况下,系统集成过程将会大大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。

    优点

    1、它是一种验证行为。

    程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。

    2、它是一种设计行为。

    编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。

    3、它是一种编写文档的行为。

    单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。

    4、它具有回归性。

    自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。

    范畴

    如果要给单元测试定义一个明确的范畴,指出哪些功能是属于单元测试,这似乎很难。但下面讨论的四个问题,基本上可以说明单元测试的范畴,单元测试所要做的工作。

    1、 它的行为和我期望的一致吗?

    这是单元测试最根本的目的,我们就是用单元测试的代码来证明它所做的就是我们所期望的。

    2、 它的行为一直和我期望的一致吗?

    编写单元测试,如果只测试代码的一条正确路径,让它正确走一遍,并不算是真正的完成。软件开发是一项复杂的工程,在测试某段代码的行为是否和你的期望一致时,你需要确认:在任何情况下,这段代码是否都和你的期望一致;譬如参数很可疑、硬盘没有剩余空间、缓冲区溢出、网络掉线的时候。

    3、 我可以依赖单元测试吗?

    不能依赖的代码是没有多大用处的。既然单元测试是用来保证代码的正确性,那么单元测试也一定要值得依赖。

    4、 单元测试说明我的意图了吗?

    单元测试能够帮我们充分了解代码的用法,从效果上而言,单元测试就像是能执行的文档,说明了在你用各种条件调用代码时,你所能期望这段代码完成的功能。

    不写的借口

    到这里,我们已经列举了使用单元测试的种种理由。也许,每个人都同意,是的,该做更多的测试。这种人人同意的事情还多着呢,是的,该多吃蔬菜,该戒烟,该多休息,该多锻炼……这并不意味着我们中的所有人都会这么去做,不是吗?

    1、 编写单元测试太花时间了。

    我们知道,在开发时越早发现BUG,就能节省更多的时间,降低更多的风险。

    如果你仍然认为在编写产品代码的时候,还是没有时间编写测试代码,那么请先考虑下面这些问题:

    1)、对于所编写的代码,你在调试上面花了多少时间。

    2)、对于以前你自认为正确的代码,而实际上这些代码却存在重大的bug,你花了多少时间在重新确认这些代码上面。

    3)、对于一个别人报告的bug,你花了多少时间才找出导致这个bug 的源码位置。

    回答完这些问题,你一定不再以“太花时间”作为拒绝单元测试的借口。

    2、 运行测试的时间太长了。

    合适的测试是不会让这种情况发生的。实际上,大多数测试的执行都是非常快的,因此你在几秒之内就可以运行成千上万个测试。但是有时某些测试会花费很长的时间。这时,需要把这些耗时的测试和其他测试分开。通常可以每天运行这种测试一次,或者几天一次。

    3、 测试代码并不是我的工作。

    你的工作就是保证代码能够正确的完成你的行为,恰恰相反,测试代码正是你不可缺少的工作。

    4、 但是这些代码都能够编译通过。

    我们前面已经说过,代码通过编译只是验证它的语法通过。但并不能保证它的行为就一定正确。

    5、 公司请我来是为了写代码,而不是写测试。

    公司付给你薪水是为了让你编写产品代码,而单元测试大体上是一个工具,是一个和编辑器、开发环境、编译器等处于同一位置的工具。

    6、 我的公司并不会让我在真实系统中运行单元测试。

    我们所讨论的只是针对开发者的单元测试。也就是说,如果你可以在其他的环境下(例如在正式的产品系统中)运行这些测试的话,那么它们就不再是单元测试,而是其他类型的测试了。实际上,你可以在你的本机运行单元测试,使用你自己的数据库,或者使用mock 对象。

    代码编写

    本文以C++为例,后半部分所介绍的单元测试工具也只介绍C++单元测试工具。下面的示例代码的开发环境是VC6.0。

    产品类

    class CMyClass{

    public:

    int Add(int i,int j);

    CMyClass();

    virtual ~CMyClass();

    private:

    int mAge; //年龄

    CString mPhase; //年龄阶段,如"少年","青年"

    };

    建立对应的测试类CMyClassTester,为了节约编幅,只列出源文件的代码:

    void CMyClassTester::CaseBegin()

    {

    //pObj是CMyClassTester类的成员变量,是被测试类的对象的指针,

    //为求简单,所有的测试类都可以用pObj命名被测试对象的指针。

    pObj = new CMyClass();

    }

    void CMyClassTester::CaseEnd()

    {

    delete pObj;

    }

    测试类的函数CaseBegin()和CaseEnd()建立和销毁被测试对象,每个测试用例的开头都要调用CaseBegin(),结尾都要调用CaseEnd()。

    产品函数

    接下来,我们建立示例的产品函数:

    int CMyClass::Add(int i,int j)

    {

    return i+j;

    }

    和对应的测试函数:

    void CMyClassTester::Add_int_int()

    {

    }

    把参数表作为函数名的一部分,这样当出现重载的被测试函数时,测试函数不会产生命名冲突。下面添加测试用例:

    void CMyClassTester::Add_int_int()

    {

    //第一个测试用例

    CaseBegin();{ //1

    int i = 0; //2

    int j = 0; //3

    int ret = pObj->Add(i,j); //4

    ASSERT(ret == 0); //5

    }CaseEnd(); //6

    }

    测试用例

    下面说说测试用例、输入数据及预期输出。输入数据是测试用例的核心,对输入数据的定义是:被测试函数所读取的外部数据及这些数据的初始值。外部数据是对于被测试函数来说的,实际上就是除了局部变量以外的其他数据,老纳把这些数据分为几类:参数、成员变量、全局变量、IO媒体。IO媒体是指文件、数据库或其他储存或传输数据的媒体,例如,被测试函数要从文件或数据库读取数据,那么,文件或数据库中的原始数据也属于输入数据。一个函数无论多复杂,都无非是对这几类数据的读取、计算和写入。预期输出是指:返回值及被测试函数所写入的外部数据的结果值。返回值就不用说了,被测试函数进行了写操作的参数(输出参数)、成员变量、全局变量、IO媒体,它们的预期的结果值都是预期输出。一个测试用例,就是设定输入数据,运行被测试函数,然后判断实际输出是否符合预期。下面举一个与成员变量有关的例子:

    产品函数

    void CMyClass::Grow(int years)

    {

    mAge += years;

    if(mAge < 10)

    mPhase = "儿童";

    else if(mAge <20)

    mPhase = "少年";

    else if(mAge <45)

    mPhase = "青年";

    else if(mAge <60)

    mPhase = "中年";

    else

    mPhase = "老年";

    }

    测试函数中的一个测试用例:

    CaseBegin();{

    int years = 1;

    pObj->mAge = 8;

    pObj->Grow(years);

    ASSERT( pObj->mAge == 9 );

    ASSERT( pObj->mPhase == "儿童" );

    }CaseEnd();

    在输入数据中对被测试类的成员变量mAge进行赋值,在预期输出中断言成员变量的值。现在可以看到老纳所推荐的格式的好处了吧,这种格式可以适应很复杂的测试。在输入数据部分还可以调用其他成员函数,例如:执行被测试函数前可能需要读取文件中的数据保存到成员变量,或需要连接数据库,老纳把这些操作称为初始化操作。例如,上例中 ASSERT( ...)之前可以加pObj->OpenFile();。为了访问私有成员,可以将测试类定义为产品类的友元类。例如,定义一个宏:

    #define UNIT_TEST(cls) friend class cls##Tester;

    然后在产品类声明中加一行代码:UNIT_TEST(ClassName)。

    测试用例设计

    测试用例的核心是输入数据。预期输出是依据输入数据和程序功能来确定的,也就是说,对于某一程序,输入数据确定了,预期输出也就可以确定了,至于生成/销毁被测试对象和运行测试的语句,是所有测试用例都大同小异的,因此,我们讨论测试用例时,只讨论输入数据。

    前面说过,输入数据包括四类:参数、成员变量、全局变量、IO媒体,这四类数据中,只要所测试的程序需要执行读操作的,就要设定其初始值,其中,前两类比较常用,后两类较少用。显然,把输入数据的所有可能取值都进行测试,是不可能也是无意义的,我们应该用一定的规则选择有代表性的数据作为输入数据,主要有三种:正常输入,边界输入,非法输入,每种输入还可以分类,也就是平常说的等价类法,每类取一个数据作为输入数据,如果测试通过,可以肯定同类的其他输入也是可以通过的。下面举例说明:

    正常输入

    例如字符串的Trim函数,功能是将字符串前后的空格去除,那么正常的输入可以有四类:前面有空格;后面有空格;前后均有空格;前后均无空格。

    边界输入

    上例中空字符串可以看作是边界输入。

    再如一个表示年龄的参数,它的有效范围是0-100,那么边界输入有两个:0和100。

    非法输入

    非法输入是正常取值范围以外的数据,或使代码不能完成正常功能的输入,如上例中表示年龄的参数,小于0或大于100都是非法输入,再如一个进行文件操作的函数,非法输入有这么几类:文件不存在;目录不存在;文件正在被其他程序打开;权限错误。

    如果函数使用了外部数据,则正常输入是肯定会有的,而边界输入和非法输入不是所有函数都有。一般情况下,即使没有设计文档,考虑以上三种输入也可以找出函数的基本功能点。实际上,单元测试与代码编写是“一体两面”的关系,编码时对上述三种输入都是必须考虑的,否则代码的健壮性就会成问题。

    白盒覆盖

    上面所说的测试数据都是针对程序的功能来设计的,就是所谓的黑盒测试。单元测试还需要从另一个角度来设计测试数据,即针对程序的逻辑结构来设计测试用例,就是所谓的白盒测试。在老纳看来,如果黑盒测试是足够充分的,那么白盒测试就没有必要,可惜“足够充分”只是一种理想状态,例如:真的是所有功能点都测试了吗?程序的功能点是人为的定义,常常是不全面的;各个输入数据之间,有些组合可能会产生问题,怎样保证这些组合都经过了测试?难于衡量测试的完整性是黑盒测试的主要缺陷,而白盒测试恰恰具有易于衡量测试完整性的优点,两者之间具有极好的互补性,例如:完成功能测试后统计语句覆盖率,如果语句覆盖未完成,很可能是未覆盖的语句所对应的功能点未测试。

    白盒测试针对程序的逻辑结构设计测试用例,用逻辑覆盖率来衡量测试的完整性。逻辑单位主要有:语句、分支、条件、条件值、条件值组合,路径。语句覆盖就是覆盖所有的语句,其他类推。另外还有一种判定条件覆盖,其实是分支覆盖与条件覆盖的组合,在此不作讨论。跟条件有关的覆盖就有三种,解释一下:条件覆盖是指覆盖所有的条件表达式,即所有的条件表达式都至少计算一次,不考虑计算结果;条件值覆盖是指覆盖条件的所有可能取值,即每个条件的取真值和取假值都要至少计算一次;条件值组合覆盖是指覆盖所有条件取值的所有可能组合。老纳做过一些粗浅的研究,发现与条件直接有关的错误主要是逻辑操作符错误,例如:||写成&&,漏了写!什么的,采用分支覆盖与条件覆盖的组合,基本上可以发现这些错误,另一方面,条件值覆盖与条件值组合覆盖往往需要大量的测试用例,因此,在老纳看来,条件值覆盖和条件值组合覆盖的效费比偏低。效费比较高且完整性也足够的测试要求是这样的:完成功能测试,完成语句覆盖、条件覆盖、分支覆盖、路径覆盖。做过单元测试的朋友恐怕会对老纳提出的测试要求给予一个字的评价:晕!或者两个字的评价:狂晕!因为这似乎是不可能的要求,要达到这种测试完整性,其测试成本是不可想象的,不过,出家人不打逛语,老纳之所以提出这种测试要求,是因为利用一些工具,可以在较低的成本下达到这种测试要求,后面将会作进一步介绍。

    测试工具

    现在开始介绍单元测试工具,分别按编程语言进行分组介绍。

    C/C++

    CppUnit

    首先是CppUnit,这是C++单元测试工具的鼻祖,免费的开源的单元测试框架。建议读一下Cpluser 所作的《CppUnit测试框架入门》。

    C++Test

    然后介绍C++Test,这是Parasoft公司的产品。[C++Test是一个功能强大的自动化C/C++单元级测试工具,可以自动测试任何C/C++函数、类,自动生成测试用例、测试驱动函数或桩函数,在自动化的环境下极其容易快速的将单元级的测试覆盖率达到100%]。

    gtest

    gtest测试框架是在不同平台上(Linux,Mac OS X,Windows,Cygwin,Windows CE和Symbian)为编写C++测试而生成的。它是基于xUnit架构的测试框架,支持自动发现测试,丰富的断言集,用户定义的断言,death测试,致命与非致命的失败,类型参数化测试,各类运行测试的选项和XML的测试报告。

    应用

    极限编程

    单元测试是极限编程的基础,依赖于自动化的单元测试框架。

    极限编程创建单元测试用于测试驱动开发。首先,开发人员编写单元测试用于展示软件需求或者软件缺陷。因为需求尚未实现或者现有代码中存在软件缺陷,这些测试会失败。然后,开发人员遵循测试要求编写最简单的代码去满足它,直到测试得以通过。

    系统中大多数代码都经过单元测试,但并非所有代码路径都必需单元测试。极限编程强调“测试所有可能中断”的策略,而传统方法是“测试所有执行路径”。这使得极限编程开发人员比传统开发少写单元测试,但这并不是问题。不争的事实是传统方法很少完全遵循完整地测试所有执行路径的要求。极限编程相互地认识到测试很少能完备,提供了如何有效地将有限资源集中投入可花费的代价到问题关键的导引。

    技术

    单元测试通常情况下自动进行,但也可被手动执行。单元测试的目标是隔离程序单元并验证其正确性。自动执行使目标达成更有效,也可获得本文上述单元测试收益。

    借助于自动化测试框架,开发人员可以抓住关键进行编码并通过测试去验证程序单元的正确性。在测试案例执行期间,框架通过日志记录了所有失败的测试准则。很多测试框架可以自动标记和提交失败的测试案例总结报告。根据失败的程度不同,框架可以中止后续测试。

    总体说来,单元测试会激发程序员创造解耦的和内聚的代码体。单元测试实践有利于促进健康的软件开发习惯。设计模式、单元测试和重构经常一起出现在工作中,借助于它们,开发人员可以生产出最为完美的解决方案。

    GTEST

    When using googletest, you start by writing assertions, which are statements that check whether a condition is true. An assertion's result can be success, nonfatal failure, or fatal failure. If a fatal failure occurs, it aborts the current function; otherwise the program continues normally.

    Tests use assertions to verify the tested code's behavior. If a test crashes or has a failed assertion, then it fails; otherwise it succeeds.

    A test suite contains one or many tests. You should group your tests into test suites that reflect the structure of the tested code. When multiple tests in a test suite need to share common objects and subroutines, you can put them into a test fixture class.

    A test program can contain multiple test suites.

    googletest assertions are macros that resemble function calls. You test a class or function by making assertions about its behavior. When an assertion fails, googletest prints the assertion's source file and line number location, along with a failure message. You may also supply a custom failure message which will be appended to googletest's message.

    Basic Assertions

    These assertions do basic true/false condition testing.

    Fatal assertion

    Nonfatal assertion

    Verifies

    ASSERT_TRUE(condition);

    EXPECT_TRUE(condition);

    condition is true

    ASSERT_FALSE(condition);

    EXPECT_FALSE(condition);

    condition is false

    Remember, when they fail, ASSERT_* yields a fatal failure and returns from the current function, while EXPECT_* yields a nonfatal failure, allowing the function to continue running. In either case, an assertion failure means its containing test fails.

    Binary Comparison

    This section describes assertions that compare two values.

    Fatal assertion

    Nonfatal assertion

    Verifies

    ASSERT_EQ(val1, val2);

    EXPECT_EQ(val1, val2);

    val1 == val2

    ASSERT_NE(val1, val2);

    EXPECT_NE(val1, val2);

    val1 != val2

    ASSERT_LT(val1, val2);

    EXPECT_LT(val1, val2);

    val1 < val2

    ASSERT_LE(val1, val2);

    EXPECT_LE(val1, val2);

    val1 <= val2

    ASSERT_GT(val1, val2);

    EXPECT_GT(val1, val2);

    val1 > val2

    ASSERT_GE(val1, val2);

    EXPECT_GE(val1, val2);

    val1 >= val2

    Value arguments must be comparable by the assertion's comparison operator or you'll get a compiler error. We used to require the arguments to support the << operator for streaming to an ostream, but it's no longer necessary. If << is supported, it will be called to print the arguments when the assertion fails; otherwise googletest will attempt to print them in the best way it can. 

     
     

    展开全文
  • 背锅侠一个有个性的订阅号1.单元测试什么单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确,通常而言,一个单元测试是用于判断某个特定条件(或者场景)...

    背锅侠 一个有个性的订阅号


    1.单元测试是什么

    单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确,通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为1。

    长按图片识别二维码

    入群-学习-合作-投稿

    加小石阿微信 带你上高速

    2.单元测试的好处

    1,单元测试不但会使你的工作完成得更轻松。而且会令你的设计会变得更好,甚至大大减少你花在调试上面的时间 

    2,提高代码质量 

    3,减少bug,快速定位bug 

    4,放心地修改、重构 

    5,显得专业(玩笑话)

    3.写单元测试要注意什么

    1,不能只测试一条正确执行路径,要考虑到所有可能的情况 

    2,要确保所有测试都能够通过,避免间接损害 

    3,如果一个函数复杂到无法单测,那就说明模块的抽象有问题 

    4,配置不是单元测试的难点,难点是mock(后文讲),做单元测试需要伪造被测函数用到的大部分函数间接损害:在整个系统中,当某一部分加入了新特性,或者修复了一个bug之后,给系统的其他(与前面可能是互不相关的)部分引入了一个新的bug(或者损害)。如果无视这种损害并且继续开发的话,那么将可能带来一个很危险的问题,最后可能会导致整个系统崩溃,并且没人能够修复。

    4.为什么写单元测试(为什么会拒绝单元测试)?

    编写单元测试太花时间了?考虑下面问题:

    1,对于所编写的代码,你在调试上面画了多少时间? 

    2,对于以前你自认为正确的代码,而实际上这些代码却存在重大的bug,你画了多少时间在重新确认这些代码上面? 

    3,对于一个别人报告的bug,你花了多少时间才找出导致这个bug的源码位置? 

    对于那些没有使用单元测试的程序员而言,上面这些问题所耗费的时间的递增速度是很快的,而且随着项目深入,递增速度会变得更快;而另一方面,适当的单元测试却可以很大程度地减少这些时间,从而为你腾出足够的时间来编写所有的单元测试——甚至可能还有剩余的空闲时间。

    a.运行测试的时间太长?

    一般合适的测试是不会让这种情况发生的。 

    有些真的会花很长时间的,可以把耗时的测试和其他测试分开。

    b.不清楚代码的行为?

    如果实在不清楚代码的行为,那么现在应该也不是应该编码的时候。

    c.代码都能编译通过啊?

    ok,你的代码语法正确,应该也是可以运行的。但是代码的行为和你的预期是一样的么?

    public void addItem(Object itemObject){
        List myList = new List;
        myList.add(itemObject);
        myList.add(itemObject);
        ...
    }
    

    本文部分来源于网络如有侵权请联系删除

    背锅侠Tester

    长按关注下方二维码领取资料

    linux

    jmeter

    python

    appscan

    mysql

    爬虫

    sele

    面试

    WE WISH YOU HAPPY

    NEW YEAR

    2019

    留言 在看 分享 你看着整儿

    展开全文
  • 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中...

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

    详解
    单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。
    单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。
    工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。
    其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本,也降低了开发商的竞争力。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。
    对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
    要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。我认为,比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。首先就几个概念谈谈我的看法。
    一般认为,在结构化程序时代,单元测试所说的单元是指函数,在当今的面向对象时代,单元测试所说的单元是指类。以我的实践来看,以类作为测试单位,复杂度高,可操作性较差,因此仍然主张以函数作为单元测试的测试单位,但可以用一个测试类来组织某个类的所有测试函数。单元测试不应过分强调面向对象,因为局部代码依然是结构化的。单元测试的工作量较大,简单实用高效才是硬道理。
    有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。对于C++来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头文件中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。
    使用效果
    什么时候测试?单元测试越早越好,早到什么程度?极限编程(Extreme Programming,或简称XP)讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的直接返回一个合适值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。
    误解
    在明确了什么是单元测试以后,我们可以进行"反调论证"了。在下面的章节里,我们列出了一些反对单元测试的普遍的论点。然后用充分的理由来证明这些论点是不足取的。
    它浪费了太多的时间
    一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到实际的系统开始启动工作了。这在外表上看来是一项明显的进步,而象单元测试这样的活动也许会被看作是通往这个阶段点的道路上的障碍, 推迟了对整个系统进行联调这种真正有意思的工作启动的时间。
    在这种开发步骤中,真实意义上的进步被外表上的进步取代了。系统能够正常工作的可能性是很小的,更多的情况是充满了各式各样的Bug。在实践中,这样一种开发步骤常常会导致这样的结果:软件甚至无法运行。更进一步的结果是大量的时间将被花费在跟踪那些包含在独立单元里的简单的Bug上面,在个别情况下,这些Bug也许是琐碎和微不足道的,但是总的来说,他们会导致在软件集成为一个系统时增加额外的工期, 而且当这个系统投入使用时也无法确保它能够可靠运行。
    在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。而调试人员的不受控和散漫的工作方式只会花费更多的时间而取得很少的好处。
    使用AdaTEST和Cantata这样的支持工具可以使单元测试更加简单和有效。但这不是必须的,单元测试即使是在没有工具支持的情况下也是一项非常有意义的活动。
    它仅仅是证明这些代码做了什么
    这是那些没有首先为每个单元编写一个详细的规格说明而直接跳到编码阶段的开发人员提出的一条普遍的抱怨, 当编码完成以后并且面临代码测试任务的时候,他们就阅读这些代码并找出它实际上做了什么,把他们的测试工作基于已经写好的代码的基础上。当然,他们无法证明任何事情。所有的这些测试工作能够表明的事情就是编译器工作正常。是的,他们也许能够抓住(希望能够)罕见的编译器Bug,但是他们能够做的仅仅是这些。
    如果他们首先写好一个详细的规格说明,测试能够以规格说明为基础。代码就能够针对它的规格说明,而不是针对自身进行测试。这样的测试仍然能够抓住编译器的Bug,同时也能找到更多的编码错误,甚至是一些规格说明中的错误。好的规格说明可以使测试的质量更高,所以最后的结论是高质量的测试需要高质量的规格说明。
    在实践中会出现这样的情况:一个开发人员要面对测试一个单元时只给出单元的代码而没有规格说明这样吃力不讨好的任务。你怎样做才会有更多的收获,而不仅仅是发现编译器的Bug?第一步是理解这个单元原本要做什么, — 不是它实际上做了什么。比较有效的方法是倒推出一个概要的规格说明。这个过程的主要输入条件是要阅读那些程序代码和注释, 主要针对这个单元, 及调用它和被它调用的相关代码。画出流程图是非常有帮助的,你可以用手工或使用某种工具。可以组织对这个概要规格说明的走读(Review),以确保对这个单元的说明没有基本的错误, 有了这种最小程度的代码深层说明,就可以用它来设计单元测试了。
    我是个很棒的程序员, 我是不是可以不进行单元测试?
    在每个开发组织中都至少有一个这样的开发人员,他非常擅长于编程,他们开发的软件总是在第一时间就可以正常运行,因此不需要进行测试。你是否经常听到这样的借口?
    在真实世界里,每个人都会犯错误。即使某个开发人员可以抱着这种态度在很少的一些简单的程序中应付过去。但真正的软件系统是非常复杂的。真正的软件系统不可以寄希望于没有进行广泛的测试和Bug修改过程就可以正常工作。
    编码不是一个可以一次性通过的过程。在真实世界中,软件产品必须进行维护以对操作需求的改变作出反应, 并且要对最初的开发工作遗留下来的Bug进行修改。你希望依靠那些原始作者进行修改吗? 这些制造出这些未经测试的原始代码的资深专家们还会继续在其他地方制造这样的代码。在开发人员做出修改后进行可重复的单元测试可以避免产生那些令人不快的负作用。
    不管怎样,集成测试将会抓住所有的Bug。
    我们已经在前面的讨论中从一个侧面对这个问题进行了部分的阐述。这个论点不成立的原因在于规模越大的代码集成意味着复杂性就越高。如果软件的单元没有事先进行测试,开发人员很可能会花费大量的时间仅仅是为了使软件能够运行,而任何实际的测试方案都无法执行。
    一旦软件可以运行了,开发人员又要面对这样的问题:在考虑软件全局复杂性的前提下对每个单元进行全面的测试。这是一件非常困难的事情,甚至在创造一种单元调用的测试条件的时候,要全面的考虑单元的被调用时的各种入口参数。在软件集成阶段,对单元功能全面测试的复杂程度远远的超过独立进行的单元测试过程。
    最后的结果是测试将无法达到它所应该有的全面性。一些缺陷将被遗漏,并且很多Bug将被忽略过去。
    让我们类比一下,假设我们要清洗一台已经完全装配好的食物加工机器!无论你喷了多少水和清洁剂,一些食物的小碎片还是会粘在机器的死角位置,只有任其腐烂并等待以后再想办法。但我们换个角度想想,如果这台机器是拆开的, 这些死角也许就不存在或者更容易接触到了,并且每一部分都可以毫不费力的进行清洗。

    展开全文
  • 什么是单元测试

    千次阅读 2019-01-30 16:04:24
    百度百科 对于单元测试 的定义 如下 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,...

    百度百科 对于单元测试 的定义 如下

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。 

    二: 为什么需要使用 单元测试

    1.0  设想你在在一个庞大的工程里面加个了 小功能 A ,按照我们前面的说法,是不是每次都要把工程跑起来,然后 去到 A处 然后进行测试。甚至如果我们仅仅是想 测试一个 接口 返回的参数 而这个接口又需要使用到现有工程中的很多参数 等 如果使用 单元测试的话 就不用每次去 把整个工程跑起来,只需要跑你测试的部分。

    2.0 我们可以使用单元测试测 某个方法的耗时和性能,单次 和 多次运行的整体对比的。当然你可以在 方法执行前 获取时间 ,方法结束 后获取时间等方式 获取时间消耗 姑且这样写 麻烦不说 但是如何 计算 CPU占用这些消耗呢?当然我们可以使用instrument 来做更专业的测试。 相对而言 单元测试更加便捷 和 方便使用给我们省不少事。

     

    展开全文
  • 单元测试

    千次阅读 2016-07-12 14:40:27
    摘要:单元测试是软件测试的基础,本文详细的论述了单元测试的两个步骤人工静态检查法与动态执行跟踪法,所需执行的工作项目及相关的策略和方法。通过对这两个步骤的描述作者将多年的单元测试经验及测试理论注入于...
  • 一、 单元测试的概念  单元通俗的说就是指一个实现简单功能的函数。单元测试就是只用一组特定的输入(测试用例)测试函数是否功能正常,并且返回了正确的输出。  测试的覆盖种类  1.语句覆盖:语句覆盖...
  • 单元测试  是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试,测试重点是系统的模块,包括子程序的正确性验证等。  集成测试  也...
  • 前面向大家介绍了黑盒测试和白盒测试,但是软件测试的...单元测试按照字面意思的理解就是对软件最基本组成单元的测试,一般属于软件测试中最低级的测试方法。一般被测单元都是在被隔离的情况下进行单元测试的。 思...
  • Vue 组件单元测试究竟测试什么

    千次阅读 2020-02-07 15:03:49
    关于 Vue 组件单元测试最常见的问题就是“我究竟应该测试什么?” 虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头。毕竟,没有人愿意自己的组件未经测试从而导致应用程序在生产中崩溃。 在...
  • 单元测试应该测什么

    2015-10-08 20:43:37
    单元测试应该测什么
  • 什么是单元测试,黑盒测试,白盒测试

    千次阅读 2009-12-15 20:47:00
    人到用时方知少~今天被要求作单元测试,黑盒测试白盒测试难住了,想当年也不是没学过。。。。唉。。。。 读后感,好的测试能得到好质量的程序。。。对几人组的项目来说成本还是可观的:..小项目一般采用动态分析。。...
  • 软件测试_单元测试和集成测试

    千次阅读 2019-11-25 19:39:27
    什么是单元测试 单元测试就是对已实现的软件最小单元进行测试,以保证构成软件的各个单元的质量。 单元测试的目的 单元实现其特定功能 单元的运行能够覆盖预先设定的各种逻辑 在单元工作过程中,其内部数据能够...
  • JUnit单元测试

    千次阅读 2017-05-03 11:33:47
    软件测试有很多分类,从测试的方法上可分为:黑盒测试、白盒测试、静态测试、动态测试等;...那么今天我们就来说说什么是单元测试,为什么要进行单元测试,以及如更好的何进行单元测试单元测试目的是什
  • ios单元测试什么我的应用程序在每次发行后都会变得如此麻烦? 为什么我的质量检查小组经常报告重复性问题和崩溃? 遏制此类问题的最佳解决方案是单元测试。 在此博客文章中,我们将看到如何将单元测试有效地...
  • python单元测试unittest

    万次阅读 2011-12-26 23:41:50
    无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。...虽然会很快熟悉内容,但是修改和调试将是一件痛苦的事情,如果你在修改了代码后出现问题的话,而单元测试可以帮助我们很快准确的...
  • Django单元测试

    千次阅读 2019-09-05 11:51:08
    测试例子部分源于官方文档,部分为自己碰到的案例,官方文档链接:...Python有unittest库来进行单元测试,Django的单元测试是基于unittest库的,只不过在unittest...
  • C#单元测试学习笔记

    2021-02-24 14:27:16
    本文来自博客园,本文主要介绍什么是单元测试单元测试的工具以及单元测试的标准,希望对您的学习有所帮助。(1)协助程序员尽快找到代码中bug的具体位置(2)能够让程序员对自己的程序更有自信(3)能够让程序员在...
  • Android单元测试

    千次阅读 2016-06-11 21:24:15
    什么是单元测试 为什么要做单元测试 JUnit Mockito Robolectric Dagger2 一个具体的app例子实践 神秘的bonus JUnit测试在写测试之前,让我们做下简单的检查,确保工程配置正确。首先,确认在Build Variants窗口内的...
  • 在VS2010中,单元测试的功能很强大,使得建立单元测试和编写单元测试代码,以及管理和运行单元测试都变得简单起来,通过私有访问器可以对私有方法也能进行单元测试,并且支持数据驱动的单元测试。 1、建立单元测试...
  • 在VS2010中,单元测试的功能很强大,使得建立单元测试和编写单元测试代码,以及管理和运行单元测试都变得简单起来,通过私有访问器可以对私有方法也能进行单元测试,并且支持数据驱动的单元测试。 1、建立单元测试...
  • 文档表达的核心意思:1、我们之前为什么不写单元测试;2、我们现在为什么要写单元测试;3、如何写好单元测试;以及Junit、Mockito、Hamcrest、Dbunit、jacoco等常用测试工具的使用
  • 隔离的基本方法就是打桩,将测试任务之外的,并且与测试任务相关的代码,用桩来代替,从而实现分离测试任务。例如函数A调用了函数B,函数B又调用了函数C和D,如果函数B用桩来代替,函数A就可以完全割断与函数C和D...
  • VS2010 测试 -单元测试

    千次阅读 2013-12-13 09:34:24
    Visual Studio 2010 单元测试共分七个部分: 普通单元测试、顺序单元测试、压力测试,Generic测试、数据库测试、UI界面测试和Web性能测试。  Visual Studio 2010 单元测试之一---普通单元测试:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 80,807
精华内容 32,322
关键字:

单元测试是什么意思