首页 > 解决方案 > 如何对移动操作进行单元测试(默认)?

问题描述

当我尝试为只移动类编写单元测试时,我遇到了这个问题。我不知道如何编写一个测试来检查移动操作是否真的移动了类的数据成员。我在这里包含了一个与我正在研究的类相似的类的简化示例。实际上,该类支持更多的操作和 ( std::multimap) 数据成员,这在此处不应该相关。

#include <vector>

class MyClass {
 public:
  inline MyClass() = default;

  MyClass(const MyClass& other) = delete;
  MyClass(MyClass&& other) = default;

  MyClass& operator=(const MyClass& other) = delete;
  MyClass& operator=(MyClass&& other) = default;

  ~MyClass() = default;

  inline void set(const std::vector<MyStruct>& data) {data_ = data;}
  inline const std::vector<MyStruct>& get() {return data_;}
 private:
  std::vector<MyStruct> data_;
};

MyStruct仅包含原始数据类型 ( int,floatdouble) 和一些std::string类型作为 ( public) 数据成员。为了完整起见,我MyStruct在最后添加了一个定义。

我什至不知道如何开始,也无法通过在线搜索找到任何东西。理想情况下,googletest 或 googlemock 解决方案会很棒,但只是一种通用方法或它在其他测试框架中的工作方式可能会帮助我理解它并在我喜欢的框架中实现它。

#include <string>

struct MyStruct {
  int foo;
  float bar;
  double baz;
  std::string fips;
};

到目前为止的解决方案(来自下面的评论和答案):

可能的方法1

基于我与@MutableSideEffect 的交流

模拟数据成员并测试在调用移动操作时是否调用了它们MyClass的移动操作。

这看起来简单明了。这表明(defaulted)移动操作是否MyClass使用了每个数据成员的移动操作。提供正确的移动操作应该是数据成员类型的责任。

我唯一的问题是,如果不对我的整个代码进行模板化,我不知道如何模拟我无法访问其实现的数据类型(如 google mock docs for mocking non-virtual functions 中所述)。

不过,在这种特殊情况下,我仍然可以模拟MyStruct并测试在调用移动操作时是否调用了它MyClass的移动操作。

可能的方法2

基于@Eljay 的回答。

添加一个数据成员,例如Marker mark,添加到我定义的每个类,或者添加到我想知道在调用类的移动操作时它们的数据成员是否被移动的每个类。指示Marker类以内部状态记录其构造函数中的哪个构造函数。然后测试是否MyClass导致其数据成员的内部状态mark反映它被移动的移动操作。

我不确定这种方法如何完全测试所有数据成员都被移动了,而不仅仅是mark数据成员。此外,这让我想起了很多只是模拟可以追溯到方法 1 的数据成员。

标签: c++unit-testinggoogletestmove-semanticsgooglemock

解决方案


这是一种使用 Marker 成员变量跟踪对象状态的方法。请记住,std::move这并不意味着对象被移出,它只会使对象有资格被移出。

一旦一个对象被移出,它就处于“有效但未指定的状态”,适合被破坏或重新分配。(一些标准 C++ 库类型对移出后的状态有稍强的保证,例如std::vectorstd::unique_ptr。)

#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <cassert>

#ifndef TESTING
#define TESTING 1
#endif

namespace {

struct Marker final {
    char const* state;
    ~Marker() { state = "destructed"; }
    Marker() : state{"constructed"} {}
    Marker(Marker const&) noexcept : state{"copy constructed"} {}
    Marker(Marker&& other) noexcept : state{"move constructed"} { other.state = "move constructed husk"; }
    Marker& operator=(Marker const&) noexcept { state = "assigned"; return *this; }
    Marker& operator=(Marker&& other) noexcept { state = "move assigned"; other.state = "move assigned husk"; return *this; }
    void print(std::ostream&) const;
};

void Marker::print(std::ostream& out) const {
    out << state;
}

std::ostream& operator<<(std::ostream& out, Marker const& marker) {
    marker.print(out);
    return out;
}

void test();

class BigFancyClass {
    friend void test();
    std::vector<std::string> v;
public:
    BigFancyClass() = default;
    BigFancyClass(BigFancyClass const&) = default;
    BigFancyClass(BigFancyClass&&) = default;
    BigFancyClass& operator=(BigFancyClass const&) = default;
    BigFancyClass& operator=(BigFancyClass&&) = default;

#if TESTING
    Marker mark;
#endif
};

void test() {
    std::cout << "Running test()... ";
    BigFancyClass bfc;
    bfc.v.push_back("hello");
    bfc.v.push_back("world");
    assert(bfc.v.size() == 2);
    assert(std::strcmp(bfc.mark.state, "constructed") == 0);
    BigFancyClass bfc2 = std::move(bfc);
    assert(bfc.v.size() == 0);
    assert(bfc2.v.size() == 2);
    assert(std::strcmp(bfc.mark.state, "move constructed husk") == 0);
    assert(std::strcmp(bfc2.mark.state, "move constructed") == 0);
    BigFancyClass bfc3;
    bfc3 = std::move(bfc2);
    assert(std::strcmp(bfc2.mark.state, "move assigned husk") == 0);
    assert(std::strcmp(bfc3.mark.state, "move assigned") == 0);
    std::cout << "DONE\n";
}

} // anon

int main() {
    test();
}

推荐阅读