如何单元测试(默认)移动操作?

How do I unit test (default) move operations?

我 运行 在尝试为仅移动 class 编写单元测试时参与其中。 我不知道如何编写一个测试来检查移动操作是否真的移动了 class 的数据成员。 我包括了这里是一个 class 的简化示例,它与我正在做的事情相似。实际上,class 支持更多的操作和 (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 仅包含基本数据类型(intfloatdouble)和一些 std::string 类型(public)数据成员。为了完整起见,我在最后添加了 MyStruct 的定义。

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

#include <string>

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

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

可能的方法 1

根据我与@MutableSideEffect 的交流

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

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

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

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

可能的方法 2

基于@Eljay 的回答。

向我定义的每个 class 添加一个数据成员,比如 Marker mark,或者向每个 class 添加一个数据成员,比如 Marker mark class 的移动操作被调用。指示 Marker class 在内部状态中记录其构造函数导致其构造。然后测试 MyClass 的移动操作是否导致其数据成员 mark 的内部状态反映它已被移动。

我不确定这种方法测试 所有 数据成员被移动的程度,而不仅仅是 mark 数据成员。此外,这让我想起了很多只是嘲笑回到方法 1 的数据成员。

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

一旦一个对象被移出,它就会处于 "valid but unspecified state" 适合被破坏或被重新分配的状态。 (一些标准 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();
}