我和我的同事正在为我们的代码库实现Google Test,并且在使用标准模板容器方面遇到了Contravariance的一些问题。
因此,Google Test要求我们创建一个纯虚拟接口类,以反映我们实际的类,该类将继承该接口并实现所有虚拟功能。这将在Google Mock中用于测试。这也是工作的严格要求,否则我们需要将模板添加到所有类中,而这只是一种类型……为了使测试代码正常工作,这似乎不太直观。
所以我们只研究了一些显示问题行为的代码:
#include <vector>
#include <string>
#include <iostream>
class Fruit{
public:
Fruit(std::string colorIn) : color(colorIn) {}
std::string color;
};
class Apple : public Fruit{
public:
Apple() : Fruit("Red"){ appleType = "Honey Crisp"; }
Apple(const Fruit& fruit) : Fruit(fruit.color) { appleType = "Honey Crisp"; }
std::string appleType;
};
class Banana : public Fruit{
public:
Banana() : Fruit("Yellow"){ bananaType = "Dole"; }
Banana(const Fruit& fruit) : Fruit(fruit.color) { bananaType = "Dole"; }
std::string bananaType;
};
void takeMyFruit(std::vector<Fruit>& fruits){
if(!fruits.empty()){
std::cout << "Take my " << fruits[0].color << " " << ((Banana)(fruits[0])).bananaType << " banana." << std::endl;
std::cout << "Take my " << fruits[1].color << " " << ((Apple)(fruits[1])).appleType << " apple." << std::endl;
}else{
std::cout << "You gave me an empty bag?" << std::endl;
}
}
int main(){
std::vector<Fruit> fruits;
fruits.push_back(Banana());
fruits.push_back(Apple());
std::vector<Banana> bananas = { Banana() };
std::vector<Apple> apples = { Apple() };
takeMyFruit(fruits); //Why can I do this?
//takeMyFruit(bananas); //Compile error due to contravariance
//takeMyFruit(apples); //Compile error due to contravariance
return 0;
}
我们需要能够编译一些可以采用容器基本类型的东西,即std::vector<BaseType>
,但是我们只用一个DerivedType
来填充它。
在上面创建的代码示例中,为什么我们可以在std::vector<Fruit>
中混合使用两种不同的派生类型(即Apple
和Banana
),但不能传递std::vector<DerivedType>
到接受std::vector<BaseType>
的功能参数上?
解决有关Google Test和Google Mock的问题的最佳方法是什么。他们说,如果要更改生产代码以适合测试需求,则可能不是最佳实践。
我们看到的另一种方法是将派生类型的模板添加到任何将它们定义为成员的类中。这样做将是一个很大的革新,然后将需要我们正在创建的库的任何用户都必须包装这些拥有这些接口/派生类型的新类的每个实例,以使Google Mock正常工作。
目前,我们正在处理遗留代码,无法对其进行太多更改以合并Google Mock。我们也不能只跳过测试这些新的类类型,什么是前进的最佳方法?
答案 0 :(得分:0)
请参见下面的代码。
#include <vector>
#include <string>
#include <iostream>
#include <memory>
using namespace std;
class Fruit
{
public:
Fruit(std::string colorIn) : color(colorIn) { }
std::string color;
};
class Apple : public Fruit{
public:
Apple() : Fruit("Red") { appleType = "Honey Crisp"; }
Apple(const Fruit& fruit) : Fruit(fruit.color) { appleType = "Honey Crisp"; }
std::string appleType;
};
class Banana : public Fruit{
public:
Banana() : Fruit("Yellow") { bananaType = "Dole"; }
Banana(const Fruit& fruit) : Fruit(fruit.color) { bananaType = "Dole"; }
std::string bananaType;
};
void takeMyFruit(std::vector<shared_ptr<Fruit>>& fruits)
{
if (!fruits.empty())
{
for (vector<shared_ptr<Fruit>>::const_iterator it = fruits.begin(); it != fruits.end(); ++it)
std::cout << "Take my " << (*it)->color;
// You shouldn't use the following two lines as you don't know what is in the vector.
// std::cout << "Take my " << fruits[0]->color << " " << std::dynamic_pointer_cast<Banana>(fruits[0])->bananaType << " banana." << std::endl;
// std::cout << "Take my " << fruits[1]->color << " " << std::dynamic_pointer_cast<Apple>(fruits[1])->appleType << " apple." << std::endl;
}
else
{
std::cout << "You gave me an empty bag?" << std::endl;
}
}
int main()
{
std::vector<std::shared_ptr<Fruit>> fruits;
fruits.push_back(std::make_shared<Banana>());
fruits.push_back(std::make_shared<Apple>());
std::vector<std::shared_ptr<Fruit>> bananas = { std::make_shared<Banana>() };
std::vector<std::shared_ptr<Fruit>> apples = { std::make_shared<Apple>() };
takeMyFruit(fruits); //Why can I do this?
takeMyFruit(bananas); //OK now
takeMyFruit(apples); //OK now
return 0;
}
如果您要具有显示水果类型的功能,正确的方法是在Fruit中添加虚拟功能
virtual string FruitDetailType() = 0;
并在派生类中实现它-比方说,苹果类
virtual string FruitDetailType()
{
return "Apple, Honey Crisp";
}
//or,
virtual string FruitDetailType()
{
return appleType;
}