使用 CPP 文件中定义的函数测试模板化 类
Testing templated classes with functions defined in CPP file
对于我正在进行的项目,我需要模拟某些 classes 进行测试以测试函数的不同行为。为了测试,我使用 gtest。因为我正在开发游戏,所以代码的速度和效率至关重要。由于这个要求,我不想使用虚函数模拟我的 classes,但我想用模板模拟我的 classes,所以 classes 的实现将是在编译时定义,我不会在 运行 时失去性能。此外,因为我希望在我的其他 header/source 文件中有最少的代码膨胀,所以我想将我的文件分成 header 和源文件,以便可以在源文件。然而,这种方法有几个问题。
因为模板函数是在源文件中定义的,所以需要在源文件中明确定义 classes。否则这些模板函数将在编译时抛出 'undefined external symbol' 错误。如果我没有两个不同的项目,一个用于游戏,一个用于测试,这将不是问题,因为我无法在测试项目中明确定义模拟。
我尝试了几个解决方案,但它们都有缺点。我将尝试用以下代码演示我所做的事情:(我知道并使用 GMock,但这是一个更简单的示例)
//Game project
//Foo.h
<template class Bar>
class Foo
{
public:
Bar bar;
bool ExampleFunction();
}
//Foo.cpp
#include "Foo.h"
<template class Bar>
bool Foo::ExampleFunction()
{
return bar.Func() > 10;
}
//Testing project
//BarMock.h
class BarMock
{
public:
int Func();
int value;
}
//BarMock.cpp
#include "BarMock.h"
Bar::Func()
{
return value;
}
//TestFoo.cpp
#include "Foo.h"
TEST(Tests, TestExample)
{
Foo<BarMock> mocked;
mocked.bar.value = 100;
ASSERT_TRUE(mocked.ExampleFunction());
}
解决方案一:在测试项目中包含cpp文件
这已经很容易出错,因为包含 cpp 文件通常是不行的。但是如果我只在测试项目的某处包含一次 cpp 文件,它不会给我 'c function already defined' 错误。在我看来,这不是一个可靠的解决方案(尽管它是我目前正在使用的解决方案),因为如果我确实需要在我的测试项目的 2 个位置使用模板化 class,这将(几乎)总是会出错。
//TestFoo.cpp
#include "Foo.h"
#include "Foo.cpp" // error prone, but does compile
TEST(Tests, TestExample)
{
Foo<BarMock> mocked;
mocked.bar.value = 100;
ASSERT_TRUE(mocked.ExampleFunction());
}
解决方案 2:在 header 文件中创建定义
这不太容易出错,但也有一些其他缺点。就像我之前说过的那样,我想将膨胀保持在最低限度,但是通过这个解决方案,我还将包括 Foo header 的所有 headers(假设我需要在 Foo 中并在某处包含 foo ,然后在某个地方我也会有)。
//Game project
//Foo.h
<template class Bar>
class Foo
{
public:
Bar bar;
bool ExampleFunction()
{
return bar.Func() > 10;
}
}
//Foo.cpp removed
解决方案 3:为模拟创建虚函数
这是我最不喜欢的选项,但应该提及。就像我之前说过的那样,这会带来 运行 时间性能损失,我不想将我的大部分功能更改为虚拟功能。但是这样你就不会出错。
//BarMock.h
class BarMock
{
public:
int Func() override;
int value;
}
//BarMock.cpp
#include "BarMock.h"
Bar::Func() override
{
return value;
}
以下哪个选项最好?有没有我错过的方法?我很想听听别人对此的看法,因为我无法在网上找到 'good' 解决方案。
通过重命名文件的解决方案 #1 的变体:
- Foo.h
#pragma once // or/and header guards
<template class Bar>
class Foo
{
public:
Bar bar;
bool ExampleFunction();
};
- Foo.inl(或其他扩展名 .inc、.ixx、...)
#pragma once // or/and header guards
#include "Foo.h"
template <class Bar>
bool Foo<Bar>::ExampleFunction()
{
return bar.Func() > 10;
}
- Foo.cpp
#include "Foo.h"
#include "Foo.inc"
#include "Bar.h"
// explicit instantiation
template <> struct Foo<Bar>;
- FooTest.cpp
#include "Foo.h"
#include "Foo.inc"
#include "BarMock.h"
// Testing code...
对于我正在进行的项目,我需要模拟某些 classes 进行测试以测试函数的不同行为。为了测试,我使用 gtest。因为我正在开发游戏,所以代码的速度和效率至关重要。由于这个要求,我不想使用虚函数模拟我的 classes,但我想用模板模拟我的 classes,所以 classes 的实现将是在编译时定义,我不会在 运行 时失去性能。此外,因为我希望在我的其他 header/source 文件中有最少的代码膨胀,所以我想将我的文件分成 header 和源文件,以便可以在源文件。然而,这种方法有几个问题。
因为模板函数是在源文件中定义的,所以需要在源文件中明确定义 classes。否则这些模板函数将在编译时抛出 'undefined external symbol' 错误。如果我没有两个不同的项目,一个用于游戏,一个用于测试,这将不是问题,因为我无法在测试项目中明确定义模拟。
我尝试了几个解决方案,但它们都有缺点。我将尝试用以下代码演示我所做的事情:(我知道并使用 GMock,但这是一个更简单的示例)
//Game project
//Foo.h
<template class Bar>
class Foo
{
public:
Bar bar;
bool ExampleFunction();
}
//Foo.cpp
#include "Foo.h"
<template class Bar>
bool Foo::ExampleFunction()
{
return bar.Func() > 10;
}
//Testing project
//BarMock.h
class BarMock
{
public:
int Func();
int value;
}
//BarMock.cpp
#include "BarMock.h"
Bar::Func()
{
return value;
}
//TestFoo.cpp
#include "Foo.h"
TEST(Tests, TestExample)
{
Foo<BarMock> mocked;
mocked.bar.value = 100;
ASSERT_TRUE(mocked.ExampleFunction());
}
解决方案一:在测试项目中包含cpp文件
这已经很容易出错,因为包含 cpp 文件通常是不行的。但是如果我只在测试项目的某处包含一次 cpp 文件,它不会给我 'c function already defined' 错误。在我看来,这不是一个可靠的解决方案(尽管它是我目前正在使用的解决方案),因为如果我确实需要在我的测试项目的 2 个位置使用模板化 class,这将(几乎)总是会出错。//TestFoo.cpp
#include "Foo.h"
#include "Foo.cpp" // error prone, but does compile
TEST(Tests, TestExample)
{
Foo<BarMock> mocked;
mocked.bar.value = 100;
ASSERT_TRUE(mocked.ExampleFunction());
}
解决方案 2:在 header 文件中创建定义
这不太容易出错,但也有一些其他缺点。就像我之前说过的那样,我想将膨胀保持在最低限度,但是通过这个解决方案,我还将包括 Foo header 的所有 headers(假设我需要在 Foo 中并在某处包含 foo ,然后在某个地方我也会有)。//Game project
//Foo.h
<template class Bar>
class Foo
{
public:
Bar bar;
bool ExampleFunction()
{
return bar.Func() > 10;
}
}
//Foo.cpp removed
解决方案 3:为模拟创建虚函数
这是我最不喜欢的选项,但应该提及。就像我之前说过的那样,这会带来 运行 时间性能损失,我不想将我的大部分功能更改为虚拟功能。但是这样你就不会出错。//BarMock.h
class BarMock
{
public:
int Func() override;
int value;
}
//BarMock.cpp
#include "BarMock.h"
Bar::Func() override
{
return value;
}
以下哪个选项最好?有没有我错过的方法?我很想听听别人对此的看法,因为我无法在网上找到 'good' 解决方案。
通过重命名文件的解决方案 #1 的变体:
- Foo.h
#pragma once // or/and header guards
<template class Bar>
class Foo
{
public:
Bar bar;
bool ExampleFunction();
};
- Foo.inl(或其他扩展名 .inc、.ixx、...)
#pragma once // or/and header guards
#include "Foo.h"
template <class Bar>
bool Foo<Bar>::ExampleFunction()
{
return bar.Func() > 10;
}
- Foo.cpp
#include "Foo.h"
#include "Foo.inc"
#include "Bar.h"
// explicit instantiation
template <> struct Foo<Bar>;
- FooTest.cpp
#include "Foo.h"
#include "Foo.inc"
#include "BarMock.h"
// Testing code...