如何在GoogleTest for C++中添加期望值和ASSERT_DEATH



给定这些接口:

class ITemperature
{
public:
virtual ~ITemperature() = deafult;

virtual int get_temp() const = 0;
};
class IHumidity
{
public:
virtual ~IHumidity() = deafult;

virtual int get_humidity() const = 0;
};

这个SUT:

class SoftwareUnderTest
{
public:
SoftwareUnderTest(std::unique_ptr<ITemperature> p_temp,
std::unique_ptr<IHumidity> p_humidity)
: m_temp{std::move(p_temp)}, m_humidity{std::move(p_humidity)}
{}

bool checker()
{
assert(m_temp && "No temperature!");

if (m_temp->get_temp() < 50)
{
return true;
}

assert(m_humidity && "No humidity");

if (m_humidity->get_humidity() < 50)
{
return true;
}

return false;
}


private:
std::unique_ptr<ITemperature> m_temp;
std::unique_ptr<IHumidity> m_humidity;
};

这嘲弄了:

class MockITemperature : public ITemperature
{
public:
MOCK_METHOD(int, get_temp, (), (const override));
};
class MockIHumidity : public IHumidity
{
public:
MOCK_METHOD(int, get_humidity, (), (const override));
};

我想做一个测试,检查get_temp是否被调用,以及第二个断言(检查湿度是否为nullptr的断言(,但当一个做这个测试时,我得到了断言,但我的期望告诉我它从未被调用过(但它实际上被调用了一次(

这就是测试:

class Fixture : pu`blic testing::Test
{
protected:
void SetUp() override
{
m_sut = std::make_unique<SoftwareUnderTest>(m_mock_temperature, m_mock_humidity);
}

std::unique_ptr<StrickMockOf<MockITemperature>> m_mock_temperature = std::make_shared<StrickMockOf<MockITemperature>>();
std::unique_ptr<StrickMockOf<MockIHumidity>> m_mock_humidity;

std::unique_ptr<SoftwareUnderTest> m_sut;
};
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
EXPECT_CALL(*m_mock_temperature, get_temp).Times(1);

ASSERT_DEATH(m_sut->checker(), "No humidity");
}

显然,这是一个已知的限制,请参阅此处和此处。

到目前为止,我通过实验发现:

如果你能接受关于泄漏mock的错误消息(还没有检查它是真是假,通过AllowLeak抑制它会触发实际的崩溃(,那么可以通过使mock的寿命超过测试套件,然后将指向它们的引用/指针封装在另一个接口实现中来完成。

//mocks and SUT as they were
namespace
{
std::unique_ptr<testing::StrictMock<MockIHumidity>> mock_humidity;
std::unique_ptr<testing::StrictMock<MockITemperature>> mock_temperature;
}

struct MockITemperatureWrapper : MockITemperature
{
MockITemperatureWrapper(MockITemperature* ptr_) : ptr{ptr_} {assert(ptr);}
int get_temp() const override { return ptr->get_temp(); }
MockITemperature* ptr;
};
struct Fixture : testing::Test
{
void SetUp() override
{
mock_temperature
= std::make_unique<testing::StrictMock<MockITemperature>>(); 
m_mock_temperature = mock_temperature.get();
// testing::Mock::AllowLeak(m_mock_temperature);
m_sut = std::make_unique<SoftwareUnderTest>(
std::make_unique<MockITemperatureWrapper>(m_mock_temperature), nullptr);
}

testing::StrictMock<MockITemperature>* m_mock_temperature;
std::unique_ptr<SoftwareUnderTest> m_sut;
};
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
EXPECT_CALL(*m_mock_temperature, get_temp).WillOnce(testing::Return(60));
ASSERT_DEATH(m_sut->checker(), "No humidity");
}

https://godbolt.org/z/vKnP7TsrW

另一个选项是将包含整个的lambda传递给ASSERT_DEATH:

TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{

ASSERT_DEATH(
[this] {
EXPECT_CALL(*m_mock_temperature, get_temp)
.WillOnce(testing::Return(60));
m_sut->checker();
}(), "No humidity");
}

工作,但看起来很难看,看这里。

最后但同样重要的是:可以使用自定义断言或替换__assert_failed函数并从中抛出(可能是一些自定义异常(,然后使用assert_throw而不是assert_DEATH。虽然我不确定取代__assert_failed是否符合法律标准(可能不是(,但它在实践中是有效的:

struct AssertFailed : std::runtime_error
{
using runtime_error::runtime_error;
};

void __assert_fail(
const char* expr,
const char *filename,
unsigned int line,
const char *assert_func )
{
std::stringstream conv;
conv << expr << ' '  << filename << ' ' << line << ' ' << assert_func;
throw AssertFailed(conv.str());
}

示例:https://godbolt.org/z/Tszv6Echj

我想做一个测试,检查get_temp是否被调用第二个断言(检查湿度nullptr(,但当执行此测试时,我会得到断言,但我期望告诉我它从未被调用(但它实际上被调用一次(

首先你必须了解死亡测试是如何工作的。

  • 在执行宏ASSERT_DEATH中的代码之前,gtest会分叉测试进程,这样当死亡发生时,测试可以继续。

  • 然后分叉进程正在执行代码,这将导致进程死亡。

  • 现在测试进程加入分叉进程以查看结果。

结果是在一个过程中执行checker()并调用mock,而在测试过程中不调用checker(),因此也不调用mock。这就是为什么你会得到一个mock不满意的错误。

现在来自alager的应答使mock成为永恒的,所以丢失的预期呼叫不会被报告。由于代码使用全局状态,添加其他测试将导致一些问题。因此,我不建议采用这种方法
编辑后,他将EXPECT_CALL移到了ASSERT_DEATH中,因此现在只有分叉的进程需要调用,但这并没有得到验证,因为在验证mock之前进程就死了。因此,我也不建议采用这种方法。


所以问题是你们应该怎么做?IMO你的问题是你正在测试很多实现细节。您应该放宽测试要求(去掉StrictMock,甚至使其成为NiceMock(。尽管如此,我还是觉得这有点笨拙。现场演示

我会以这样的方式更改代码,即不可能构造具有nullptr依赖关系的SoftwareUnderTest。为此,您可以使用gsl::not_null。

这似乎是由于谷歌测试中使用的一些棘手的机制来断言死亡(他们提到创建一个子进程(。我没有找到正确修复它的方法,但我找到了一个(不太好(的解决方法:

SoftwareUnderTest(ITemperature* p_temp, IHumidity* p_humidity)  // changed signature to allow leaks, I guess you cannot really do it in the production

然后:

class Fixture : public testing::Test
{
public:
Fixture(): m_mock_temperature(new MockITemperature), m_mock_humidity(nullptr) {}
~Fixture() {
// Mock::VerifyAndClearExpectations(m_mock_temperature);  // if I uncomment that, for some reason the test will fail anyway
std::cout << "Dtor" << std::endl;
// delete m_mock_temperature;  // if I delete the mock correctly, the test will fail
}
protected:
void SetUp() override
{
// m_sut.reset(new SoftwareUnderTest(m_mock_temperature.get(), m_mock_humidity.get()));
m_sut.reset(new SoftwareUnderTest(m_mock_temperature, m_mock_humidity));
}

// std::unique_ptr<MockITemperature> m_mock_temperature;  // if I use smart pointers, the test will fail
// std::unique_ptr<MockIHumidity> m_mock_humidity;
MockITemperature* m_mock_temperature;
MockIHumidity* m_mock_humidity;
std::unique_ptr<SoftwareUnderTest> m_sut;
};
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
EXPECT_CALL(*m_mock_temperature, get_temp).Times(1).WillOnce(Return(60));  // this is to ensure to go over first check, seems you forgot
ASSERT_DEATH(m_sut->checker(), "No humidity");
std::cout << "after checks" << std::endl;
}

对不起,我现在只能想这些了。也许你可以在gtest github中提交一个新问题,同时等待更好的答案。

最新更新