You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单元测试中访问构造函数内已转移所有权的unique_ptr?

单元测试中访问已转移所有权的unique_ptr的方案

问题

如何在单元测试中访问已在构造函数内被转移所有权的unique_ptr?曾考虑同时使用shared_ptr与unique_ptr,通过shared_ptr在测试中访问,但担心触发未定义行为(UB)。

初始代码示例

struct Base
{
    virtual int bar() = 0;
};

struct B : public Base
{
    MOCK_METHOD(int, bar, (), (override));
};

class A
{
   std::unique_ptr<B> _ptrB;

   public:
   A(std::unique_ptr<B> ptr) : _ptrB(std::move(ptr))
   {
      // A接管Utest::ptrB的所有权
   }
};

struct Utest
{ 
  Utest()
  {
    ptrA = std::make_shared<A>(std::move(ptrB));
  }

  std::shared_ptr<A> ptrA;
  std::unique_ptr<B> ptrB = std::make_unique<B>();
};

TEST_F(Utest, testA)
{
    // ptrB已被转移,无法访问
    EXPECT_CALL(*ptrB, bar()).WillOnce(Return(100));  // 错误:程序异常退出
}

当前解决方案及疑问

通过将Utest设为A的友元类,直接访问A的私有成员_ptrB并向下转型为BaseMock类型用于EXPECT_CALL。但希望能将向下转型步骤移至构造函数,避免在所有单元测试中重复操作,或寻求更优解决方案。

改进后的代码示例

struct Base
{
    virtual int bar() = 0;
};

struct BaseMock : public Base
{
    MOCK_METHOD(int, bar, (), (override));
};

class A
{
   std::unique_ptr<BaseMock> _ptrB;
   friend class Utest;

   public:
   A(std::unique_ptr<BaseMock> ptr) : _ptrB(std::move(ptr))
   {
      // A接管Utest::ptrB的所有权
   }
   
};

struct Utest
{ 
  Utest()
  {
    ptrA = std::make_shared<A>(std::move(ptrB));
  }

  std::shared_ptr<A> ptrA;
  std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>();
};

TEST_F(Utest, testA)
{
    BaseMock* ptr = dynamic_cast<BaseMock*>(_ptrB.get());
    EXPECT_CALL(*ptr, bar()).WillOnce(Return(100)); 
}

优化方案

方案1:测试类中提前保留裸指针

在转移unique_ptr所有权之前,先保存对象的裸指针。由于A持有unique_ptr,只要A的生命周期覆盖测试用例,裸指针就始终有效:

struct Utest
{ 
  Utest()
  {
    _mockPtr = ptrB.get(); // 提前保存裸指针
    ptrA = std::make_shared<A>(std::move(ptrB));
  }

  std::shared_ptr<A> ptrA;
  std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>();
  BaseMock* _mockPtr; // 供测试用的裸指针
};

TEST_F(Utest, testA)
{
    EXPECT_CALL(*_mockPtr, bar()).WillOnce(Return(100)); 
}

该方案无需友元或类型转换,实现简单且无额外耦合。

方案2:给A添加测试专用接口(替代友元)

在A中添加仅测试模式下可见的接口,返回BaseMock的指针,避免友元带来的耦合:

class A
{
   std::unique_ptr<BaseMock> _ptrB;

   public:
   A(std::unique_ptr<BaseMock> ptr) : _ptrB(std::move(ptr)) {}

   // 仅测试编译时启用的接口
   #ifdef TEST_BUILD
   BaseMock* getTestMock() { return _ptrB.get(); }
   #endif
};

struct Utest
{ 
  Utest()
  {
    ptrA = std::make_shared<A>(std::move(ptrB));
  }

  std::shared_ptr<A> ptrA;
  std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>();
};

TEST_F(Utest, testA)
{
    EXPECT_CALL(*ptrA->getTestMock(), bar()).WillOnce(Return(100)); 
}

方案3:将转型逻辑移至测试类构造函数

利用友元关系,在Utest构造时直接获取A内部的_ptrB并保存,避免每个测试用例重复转换:

struct Utest
{ 
  Utest()
  {
    ptrA = std::make_shared<A>(std::move(ptrB));
    _mockPtr = ptrA->_ptrB.get(); // 直接访问A的私有成员
  }

  std::shared_ptr<A> ptrA;
  std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>();
  BaseMock* _mockPtr;
};

TEST_F(Utest, testA)
{
    EXPECT_CALL(*_mockPtr, bar()).WillOnce(Return(100)); 
}

关于混用shared_ptr与unique_ptr的说明

不推荐混用两者:unique_ptr和shared_ptr无法共享对象所有权,若强行将shared_ptr的所有权转移给unique_ptr,shared_ptr会变为空,测试中仍无法访问对象;若让A持有shared_ptr,Utest用weak_ptr观察,则会改变A原本的所有权设计,完全没必要。这种做法要么触发未定义行为,要么达不到测试目的,因此不建议尝试。

内容的提问来源于stack exchange,提问作者xyf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 00:15:45