问题描述
背景
这是我第一次用C ++编写单元测试。我正在使用Catch2作为测试框架,并且在Visual Studio解决方案中设置了2个项目:一个用于我的应用程序,另一个用于测试。
Application.h
#ifndef APPLICATION_H
#define APPLICATION_H
namespace Rival {
class Application {
public:
void start();
};
} // namespace Rival
#endif // APPLICATION_H
Application.cpp
#include "pch.h"
#include "Application.h"
#include <SDL.h>
namespace Rival {
void Application::start() {
Uint32 nextUpdateDue = SDL_GetTicks();
while (!exiting) {
Uint32 frameStartTime = SDL_GetTicks();
if (nextUpdateDue <= frameStartTime) {
// Update the game logic,as many times as necessary to keep it
// in-sync with the refresh rate.
while (nextUpdateDue <= frameStartTime) {
state->update();
nextUpdateDue += TimerUtils::timestepMs;
}
state->render();
} else {
// Sleep until next frame is due
Uint32 sleepTime = nextUpdateDue - frameStartTime;
SDL_Delay(sleepTime);
}
}
}
} // namespace Rival
问题
问题出在#include <SDL.h>
。
我希望能够从此标头中模拟方法,例如SDL_GetTicks()
。
如果可以帮助,我实际上不想 包括SDL;我想让单元测试轻巧,并且不受任何窗口创建/渲染代码的影响。
通常如何完成?
解决方法
答案是应用Fundamental Theorem of Software Engineering,它表示您可以通过添加新的抽象层来解决几乎所有问题。
这意味着您将SDL调用包装在可以在测试时换出的类中。定义一个接口(抽象基类),然后实现一个使用SDL的派生类,并实现另一个(或使用一个模拟对象)进行测试。只有SDL实现会了解SDL,因此测试甚至不会包含或链接它。
BTW,有关C ++测试的重要摘要,请参见this episode of CppCast on designing for test。
,最后,我通过对所使用的库函数进行存根来解决了这个问题。您可以使用有效的测试实现here找到我的提交。
这有点类似于@metal的建议,但是我没有添加新的抽象层,而是依靠我的测试项目不包含库头的事实,这意味着我可以自由提供我自己的替代品,用于测试。
也许我在过程中产生的文档会帮助其他人:
测试项目包含Main-Project定义的所有标头,但不包含第三方库的标头。这是为了使测试轻巧。我们不想每次运行测试时都创建一个OpenGL上下文。
这意味着我们必须为依赖的任何第三方定义提供存根实现。还可以为严重依赖第三方库的文件(例如
Texture
)提供Main-Project定义的存根或模拟实现。可以根据需要将Main-Project中的其他源文件直接包含在测试项目中。为使项目井井有条,已创建了多个过滤器:
- 测试框架::运行测试所需的文件。
- 源文件:未经测试的未经修改的源文件,直接从Main-Project导入。
- 测试双打: Main-Project标头的仅测试实现。
- 测试:测试本身。
顺便说一句,到目前为止,Catch2框架非常出色,并且没有增加任何额外的复杂性。