【问题标题】:How to write tests for classes relying on the operating system如何为依赖于操作系统的类编写测试
【发布时间】:2017-08-08 17:19:14
【问题描述】:

我编写了一个类来枚举操作系统的物理显示器,检索它们的信息和功能。实际上,我想测试这个类是否能正常工作并总体上接受(单元)测试。

但是,我不知道如何测试这个类。抛开实现细节不谈,基本上是这样定义的:

class VideoMode {
public:
    int64 width;
    int64 height;
    std::vector<int64> frequencies;
}

class Display {
protected:
    std::vector<VideoMode> modes_;
    std::string display_name_;
    std::string adapter_name_;
    bool primary_;

public:
    Display(char* osDevName, char* osAdapterDevName);

    // typical getters
}

我将如何测试高度集成并依赖于操作系统和物理连接的硬件的东西。我知道为这样的课程编写单元测试很困难,那么我有什么替代方案?

【问题讨论】:

  • 并非所有内容都可以进行单元测试。如果有太多的外部依赖项(你不能以一种明智的方式存根),你必须适应其他测试——比如黑盒测试、集成测试、手动测试。悲伤但真实.. 有时尝试对“所有事物”进行完美测试是令人钦佩的,但不值得投资回报 - 记住要现实和务实。
  • 您只需要测试针对char* osDevNamechar* osAdapterDevName的类以及背后的框架。我会尝试为osAdapterDev 提供一个模型,并检查所有函数是否按预期调用。
  • @DanielH,类在构造后如何更改adapater_name_display_name_ 的值?它是否支持基于变化环境的动态重新配置?如果不是,那么这个论点就没有实际意义。至于vector,并将迭代器返回给它,这不是最聪明的主意。除非您真的 提供大多数矢量查询 API(大小、正向/反向迭代器等),但这超出了“普通 getter”。此外,提供 std::vector&lt;&gt;::iterator 作为 API 的一部分与提供 std::vector&lt;&gt; 并没有太大区别,就好像您考虑一下一样。情况更糟。
  • 无论 getter 是做什么用的,以及类是如何派生的,都与这个问题的范围无关。
  • @DanielH,我大声而自豪地说 - 琐碎的设置者和获取者是无用的,纯粹的邪恶。

标签: c++ unit-testing testing


【解决方案1】:

你应该对你的类进行单元测试,而不是操作系统/外部库函数。

您可以添加允许模拟这些方法的外观层。 (您的 UT 不会对该层进行单元测试)。

类似:

class IOsVideoModeRetriever
{
public:
     virtual ~IOsVideoModeRetriever() = default;
     virtual std::vector<VideoMode> RetrieveVideoModes(/*...*/) = 0;
     // ...
};

// CLass with implementation of OS specific functions
class OsVideoModeRetriever : public IOsVideoModeRetriever
{
public:
     std::vector<VideoMode> RetrieveVideoModes(/*...*/) override;
     // ...
};

// Class for UT
class OsVideoModeRetrieverMock : public IOsVideoModeRetriever
{
public:
     MOCK(RetrieveVideoModes(/*...*/)); // Mock according to your framework
     // ...
};

你的其他班级使用它:

class Foo
{
public:
    explicit Foo(IOsVideoModeRetriever&);

private:
    IOsVideoModeRetriever& mOsVideoModeRetriever; // Or use `shared_ptr`
                                                  // depending of life time guaranty
};

现在你可以测试Foo

如果特定于操作系统的功能没有按预期运行(结果格式、处理限制、边缘情况等),您确实会遇到问题,这应该仅限于实现部分而不是接口。

【讨论】:

    【解决方案2】:

    第一步:不要对与操作系统的接口进行单元测试,而是对其余代码进行单元测试。那里有一些代码;不多,但有一些。

    创建一个更底层的 API,让你的 C++ 接口与之对话;它模仿您的操作系统可能提供的 C 风格 API,即使它是 C++ 类。

    喜欢

    struct OSDisplayInterface {
      virtual ~OSDisplayInterface() {}
      virtual std::size_t GetDisplayName( char const* name, char const* adapter, char* name, std::size_t name_buf_len ) = 0;
      virtual bool IsDisplayPrimary( char const* name, char const* adapter ) = 0;
      virtual bool GetVideoModeCount( char const* name, char const* adapter, std::size_t* mode_count ) = 0;
      struct video_mode {
        int64 width, height, frequency;
      };
      virtual bool GetVideoMode( char const* name, char const* adapter, std::size_t n, video_mode* mode ) = 0;
    };
    

    或者尽可能直接与您的较低级别 API 匹配的东西。这个想法是,如果操作系统的 API 失败,则此代码应该只在 真实 的情况下失败。

    然后测试您的 C++ 代码,使用伪造的 OS 视频模式集。


    如果你想更进一步,这基本上是在单元测试之外,但你可以测试上面的“操作系统显示界面”或直接针对操作系统测试你的类。

    但是,要做到这一点,您需要大量不同的硬件配置。

    从一组测试机器开始,您可以在其中远程部署代码并获取结果。

    将您的代码与其他代码库隔离,部署到这些机器上。

    每台机器上都有一个标识符。

    建立一个机器标识符来测试结果表。

    对此进行测试。确保在系统“bob37”上运行时,您获得了正确的分辨率集等。

    然后,当操作系统升级和 API 被弃用时,您会立即收到危险信号。 (当然,当一个新的驱动程序被修补并提供新的频率时,你也会得到危险信号)

    单元测试工具查找机器标识符,运行您的代码,并确认测试结果匹配。

    自动将代码部署到所述机器,这样您就可以在代码更改时在数十个硬件平台上运行测试。

    这充其量是“单元测试”的边界。但您可以想象将操作系统接口代码移植到新操作系统、环境或硬件的过程中,遇到了可能会遇到的问题。

    【讨论】:

      猜你喜欢
      • 2011-10-15
      • 2017-12-25
      • 1970-01-01
      • 1970-01-01
      • 2019-03-18
      • 2017-03-09
      • 1970-01-01
      • 2016-08-30
      • 2013-09-01
      相关资源
      最近更新 更多