单元测试 学习笔记 之三

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

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();

posted @ 2011-07-01 19:19  李嘉 (Justin)  阅读(2420)  评论(0编辑  收藏  举报