求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
单元测试 学习笔记 之三
 

发布于2011-08-09

 

第三章 单元测试的工具——测试框架

3.1 常用的单元测试框架

工欲善其事, 必先利其器. 程序员在写单元测试代码时, 如果能借助一些单元测试框架, 那么使单元测试代码的书写、维护、分类、存档、运行和结果检查变得更为容易, 从而成倍地提高工作效率. 在本章中, 我们就将学习两种单元测试的框架.

第一种, 就是鼎鼎大名的xUnit测试框架家族. xUnit测试框架有助于我们更加结构化地书写测试代码, 更方便地运行单元测试并检查运行结果.

第二种框架称为隔离框架(isolation framework). 这种框架旨在帮助程序员自动地生成FakeCollaboratorClass代码. 但是隔离框架一般都要求ClassUnderTest依赖于CollaboratorService抽象接口, 而不是具体类CollaboratorClass, 所以在使用"Virtual and Override"手法时, 隔离框架就帮不忙了, 必须由程序员手写FakeCollaboratorClass的代码.

在接下来的几个小节中, 我们将针对C++, C#和Java语言, 分别介绍适用于它们的单元测试框架.

3.2 用于C++的单元测试框架

对于C++, 我们推荐使用Google C++ Unit Testing Framework(简称gTest, 目前为1.5版)加HippoMocks(目前为3.1版)的组合. 如下的表格演示了如何使用这两个框架.

gTest的基本用法  
声明Test Fixture #ifndef CLASS_UNDER_TEST_TEST_H
#define CLASS_UNDER_TEST_TEST_H
// In [ClassUnderTest]Test.h file.
#include <gtest/gtest.h>
// Test Fixture declaration.
class [ClassUnderTest]Test : public testing::Test
{
protected:
    // Optional SetUp() and TearDown().
    virtual void SetUp();
    virtual void TearDown();
};
#endif
定义Test Method // In [ClassUnderTest]Test.cpp file.
#include <gtest/gtest.h>
#include <hippomocks.h>
#include "[ClassUnderTest]Test.h"
// Optional SetUp() and TearDown().
void [ClassUnderTest]Test::SetUp()
{
    ...
}
void [ClassUnderTest]Test::TearDown()
{
    ...
}
// Test Method definition.
TEST_F([ClassUnderTest]Test, [Feature]_[Scenario]_[ExpectedBehavior])
{
    ...
}

// Temporarily ignore a test case.

TEST_F([ClassUnderTest]Test, DISABLE_[Feature]_[Scenario]_[ExpectedBehavior])
{
    ...
}

Test Runner // Currently, gTest only supports CUI. The main() should be like this.
#include <gtest/gtest.h>
#include "[ClassUnderTest]Test.h"
int main(int argc, char** argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

gTest支持的命令行参数  
--gtest-filter=[filter] 对执行的测试案例进行过滤,支持
?    单个字符
*    任意字符
-    排除,如,-a 表示除了a
:    取或,如,a:b 表示a或b
比如下面的例子:
./foo_test 没有指定过滤条件, 运行所有案例
./foo_test --gtest_filter=* 使用通配符*, 表示运行所有案例
./foo_test --gtest_filter=FooTest.* 运行所有[ClassUnderTest]Test为FooTest的案例
./foo_test --gtest_filter=FooTest.*-FooTest.Bar 运行所有[ClassUnderTest]Test为FooTest的案例, 但是除了FooTest.Bar这个案例
--gtest_repeat=[COUNT] 设置案例重复运行次数. 比如:
--gtest_repeat=1000      重复执行1000次, 即使中途出现错误.
--gtest_repeat=1000 --gtest_break_on_failure     重复执行1000次, 并且在第一个错误发生时立即停止. 这个功能对调试非常有用.
--gtest_print_time 打印每个测试方法运行的所用时间.
--gtest_catch_exceptions 捕捉异常. 这样当测试方法中抛出了异常时, 不会阻碍了后续测试方法的运行. 注意: 这个参数只在Windows下有效.
--gtest_output=xml[:DIRECTORY_PATH/|:FILE_PATH] 产生XML格式的报告。

gTest支持的断言  
断言真伪 ASSERT_TRUE(condition) [<< message];
ASSERT_FALSE(condition) [<< message];
断言比较关系 ASSERT_EQ(expected, actual) [<< message];
ASSERT_NE(expected, actual) [<< message];
ASSERT_LT(expected, actual) [<< message];
ASSERT_LE(expected, actual) [<< message];
ASSERT_GT(expected, actual) [<< message];
ASSERT_GE(expected, actual) [<< message];
ASSERT_FLOAT_EQ(expected, actual) [<< message];
ASSERT_DOUBLE_EQ(expected, actual) [<< message];
ASSERT_NEAR(expected, actual, tolerance) [<< message];
断言C字符串关系 ASSERT_STREQ(expected_cstr, actual_cstr) [<< message];
ASSERT_STRNE(expected_cstr, actual_cstr) [<< message];
ASSERT_STRCASEEQ(expected_cstr, actual_cstr) [<< message];
ASSERT_STRCASENE(expected_cstr, actual_cstr) [<< message];
断言异常 ASSERT_THROW({ statements }, exception_ctor);
ASSERT_NO_THROW({ statements });
ASSERT_ANY_THROW({ statements });

HippoMocks用于动态stub生成  
准备工作 // In [ClassUnderTest]Test.cpp file.
#include <gtest/gtest.h>
#include <hippomocks.h>
#include "[ClassUnderTest]Test.h"
// Test Method definition.
TEST_F([ClassUnderTest]Test, [Feature]_[Scenario]_[ExpectedBehavior])
{
    MockRepository mockEngine;
    CollaboratorService* stub = mockEngine.InterfaceMock<CollaboratorService>();
    ...
}
指定返回值 // We don't care about the passed-in arguments for CollaboratorService::methodName().
mockEngine.OnCall(stub, CollaboratorService::methodName).Return(stubResult);
// We care about the passed-in arguments for CollaboratorService::methodName().
mockEngine.OnCall(stub, CollaboratorService::methodName).With(arg1, arg2, ...).Return(stubResult);
// If CollaboratorService::methodName() is overloaded, use the following invocation forms.
mockEngine.OnCallOverload(stub, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName).Return(stubResult);
mockEngine.OnCallOverload(stub, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName).With(arg1, arg2, ...).Return(stubResult);
指定抛出异常 // We don't care about the passed-in arguments for CollaboratorService::methodName().
mockEngine.OnCall(stub, CollaboratorService::methodName).Throw(exception_ctor());
// We care about the passed-in arguments for CollaboratorService::methodName().
mockEngine.OnCall(stub, CollaboratorService::methodName).With(arg1, arg2, ...).Throw(exception_ctor());
// If CollaboratorService::methodName() is overloaded, use the following invocation forms.
mockEngine.OnCallOverload(stub, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName).Throw(exception_ctor());
mockEngine.OnCallOverload(stub, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName).With(arg1, arg2, ...).Throw(exception_ctor());

HippoMocks用于动态mock生成  
准备工作 // In [ClassUnderTest]Test.cpp file.
#include <gtest/gtest.h>
#include <hippomocks.h>
#include "[ClassUnderTest]Test.h"
// Test Method definition.
TEST_F([ClassUnderTest]Test, [Feature]_[Scenario]_[ExpectedBehavior])
{
    MockRepository mockEngine;
    CollaboratorService* mock = mockEngine.InterfaceMock<CollaboratorService>();
    ...
}
设置期望:
一定不被调用
mockEngine.NeverCall(mock, CollaboratorService::methodName);
mockEngine.NeverCallOverload(mock, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName);
设置期望:
不一定被调用
mockEngine.OnCall(mock, CollaboratorService::methodName);
mockEngine.OnCallOverload(mock, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName);
设置期望:
不一定被调用, 但如果一旦被调, 那么关心输入参数
mockEngine.OnCall(mock, CollaboratorService::methodName).With(arg1, arg2, ...);
mockEngine.OnCallOverload(mock, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName).With(arg1, arg2, ...);
设置期望:
一定被调用, 但不关心输入参数
mockEngine.ExpectCall(mock, CollaboratorService::methodName);
mockEngine.ExpectCallOverload(mock, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName);
设置期望:
一定被调用, 并关心输入参数
mockEngine.ExpectCall(mock, CollaboratorService::methodName).With(arg1, arg2, ...);
mockEngine.ExpectCallOverload(mock, (return_type(CollaboratorService::*)(arg1_type, arg2_type, ...))&CollaboratorService::methodName).With(arg1, arg2, ...);
设置期望:
一定被调用, 但不关心被调用的次序
mockEngine.autoExpect = false;
mockEngine.ExpectCall(mock, CollaboratorService::methodName1);
mockEngine.ExpectCall(mock, CollaboratorService::methodName2);
设置期望:
一定被调用, 并关心被调用的次序
mockEngine.ExpectCall(mock, CollaboratorService::methodName1);
mockEngine.ExpectCall(mock, CollaboratorService::methodName2);
显式验证 mockEngine.VerifyAll();

3.3 用于C#的单元测试框架

对于C#, 我们推荐使用NUnit(目前为2.5版)加RhinoMocks(目前为3.6版)的组合. 如下的表格演示了如何使用这两个框架.

NUnit的基本用法  
Test Fixture和Test Method using NUnit.Framework;
using Rhino.Mocks;
[TestFixture]
public class [ClassUnderTest]Test
{
    [SetUp]
    public void SetUp()
    {
        ...
    }
    [TearDown]
    public void TearDown()
    {
        ...
    }
    [Test]
    public void [Feature]_[Scenario]_[ExpectedResult]()
    {
        ...
    }

// Temporarily ignore a test case.

[Test]

[Ignore]
    public void [Feature]_[Scenario]_[ExpectedResult]()
    {
        ...
    }

}

Test Runner NUnit提供了GUI界面的Test Runner, 只需把编译生成的程序集(assembly)加载到该Test Runner中即可.

NUnit支持的断言  
断言真伪 Assert.That(condition, Is.True);
Assert.That(condition, Is.False);
断言比较关系 Assert.That(actual, Is[.Not].EqualTo(expected));
Assert.That(actual. Is.LessThan(expected));
Assert.That(actual. Is.GreaterThan(expected));
Assert.That(actual. Is.AtMost(expected));
Assert.That(actual. Is.AtLeast(expected));
Assert.That(actual. Is.LessThan(expected));
Assert.That(actual. Is.EqualTo(expected).Within(tolerance));
断言对象关系 Assert.That(obj, Is[.Not].Null);
Assert.That(obj, Is[.Not].InstanceOfType(type));
Assert.That(actualObj, Is[.Not].SameAs(expectedObj));
断言容器关系 Assert.That(container, Is[.Not].Empty);
Assert.That(container, Has.Length(length));
断言字符串关系 Assert.That(actual, Is[.Not].EqualTo(expected)[.IgnoreCase]);
Assert.That(phrase, Is[.Not].StringContaining(substring)[.IgnoreCase]);
Assert.That(phrase, StartsWith(substring)[.IgnoreCase]);
Assert.That(phrase, EndsWith(substring)[.IgnoreCase]);
断言异常 Assert.That(() => { statements }, Throws.Exception.TypeOf<exception_ctor>());
Assert.That(() => { statements }, Throws.Exception);
Assert.That(() => { statements }, Throws.Nothing;

RhinoMocks用于动态stub生成  
准备工作 using NUnit.Framework;
using Rhino.Mocks;
[TestFixture]
public class [ClassUnderTest]Test
{
    [Test]
    public void [Feature]_[Scenario]_[ExpectedResult]()
    {
        MockRepository mockEngine = new MockRepository();
        CollaboratorService stub = mockEngine.Stub<CollaboratorService>();
        ...
        mockEngine.ReplayAll();
        ...
    }
}
指定返回值 // We don't care about the passed-in arguments for CollaboratorService::methodName().
Expect.Call(stub.methodName(dummy1, dummy2, dummy3, ...)).Return(stubResult);
// We care about the passed-in arguments for CollaboratorService::methodName().
Expect.Call(stub.methodName(dummy1, dummy2, dummy3, ...)).
    Constraints(Rhino.Mocks.Constraints.Is.Equal(arg1), Rhino.Mocks.Constraints.Is.Null(), Rhino.Mocks.Constraints.Is.Same(arg3), ...).
    Return(stubResult);
指定抛出异常 // We don't care about the passed-in arguments for CollaboratorService::methodName().
Expect.Call(stub.methodName(dummy1, dummy2, dummy3, ...)).
    Constraints(Rhino.Mocks.Constraints.Is.Anything(), Rhino.Mocks.Constraints.Is.Anything(), Rhino.Mocks.Constraints.Is.Anything(), ...).
    Throw(new exception_ctor());
// We care about the passed-in arguments for CollaboratorService::methodName().
Expect.Call(stub.methodName(arg1, arg2, arg3, ...)).Throw(new exception_ctor());

RhinoMocks用于动态mock生成  
准备工作 using NUnit.Framework;
using Rhino.Mocks;
[Test]
public void [Feature]_[Scenario]_[ExpectedResult]()
{
    MockRepository mockEngine = new MockRepository();
    CollaboratorService mock = mockEngine.DynamicMock<CollaboratorService>();
    ...
    mockEngine.ReplayAll();
    ...
}
设置期望:
一定不被调用
Expect.Call(() => { mock.methodName(dummy1, dummy2, ...); }).Repeat.Never();
设置期望:
不一定被调用
Expect.Call(() => { mock.methodName(dummy1, dummy2, ...); }).Repeat.Any();
设置期望:
一定被调用, 但不关心输入参数
Expect.Call(() => { mock.methodName(dummy1, dummy2, ...); }).
    Constraints(Rhino.Mocks.Constraints.Is.Anything(), Rhino.Mocks.Constraints.Is.Anything(), ...);
设置期望:
一定被调用, 并关心输入参数
Expect.Call(() => { mock.methodName(arg1, arg2, ...); });
设置期望:
一定被调用, 但不关心调用次序
Expect.Call(() => { mock.methodName1(arg1, arg2, ...); });
Expect.Call(() => { mock.methodName2(arg1, arg2, ...); });
设置期望:
一定被调用, 并关心调用次序
using (mockEngine.Ordered())
{
    Expect.Call(() => { mock.methodName1(arg1, arg2, ...); });
    Expect.Call(() => { mock.methodName2(arg1, arg2, ...); });
}
显式验证 mockEngine.VerifyAll();

相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践

 
分享到
 
 
     


LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...