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

How do I unit test (default) move operations?

本文关键字:单元测试 默认 移动 操作      更新时间:2023-10-16

当我尝试为仅移动类编写单元测试时,我遇到了这个问题。我不知道如何编写一个测试来检查移动操作是否真的移动了类的数据成员。我在这里包含一个简化的类示例,该示例与我正在处理的内容相似。实际上,该类支持更多的操作和(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的移动操作时是否调用其移动操作。

这似乎简单明了。这指示 (MyClassdefaulted( 的 ( ed( 移动操作是否使用了每个数据成员的移动操作。数据成员的类型应负责提供适当的移动操作。

我唯一的问题是,如果不对整个代码进行模板化,我不知道如何模拟我无法访问其实现的数据类型(如用于模拟非虚拟函数的谷歌模拟文档中所述(。

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

可能的办法2

基于@Eljay的回答。

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

我不确定这种方法如何充分测试所有数据成员,而不仅仅是mark数据成员。此外,这让我想起了很多只是嘲笑数据成员,这可以追溯到方法 1。

这是一种使用 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 << "DONEn";
}
} // anon
int main() {
test();
}