给定这些接口:
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中提交一个新问题,同时等待更好的答案。