Reputation: 4823
The code below uses the #define
macro. I'd like to avoid this but I don't see a way that uses C++ features and is more readable. The code is for testing a class ClassUnderTest
that merges two instances of PoorLegacyClass
into one. This PoorLegacyClass
has many getters and setters and for testing purposes I need to call a lot of setters (or do I?).
In short, instead of writing
a.setValueX(20);
b.setValueX(30);
I would like to write something like
set(a, b, ValueX, 20, 30);
// or
set<ValueX>(a, 20, b, 30);
// or even
set(a, b, &PoorLegacyClass::setValueX, 20, 30);
Here is code similar to the code I use at the moment:
#include <boost/test/unit_test.hpp>
#include "ClassUnderTest.h"
BOOST_AUTO_TEST_SUITE(Test_ClassUnderTest);
#define SETTER_A_B(fieldname, valueA, valueB)\
a.set##fieldname(valueA);\
b.set##fieldname(valueB);
struct TestContext
{
PoorLegacyClass a, b;
};
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description1, TestContext)
{
SETTER_A_B(ValueX, 20, 30);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(20 == result.getValueX());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description2, TestContext)
{
SETTER_A_B(ValueY, 21, 37);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(21 + 37 == result.getValueY());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description3, TestContext)
{
SETTER_A_B(ValueZ, 12, 83);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(12 + 83 == result.getValueZ());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description4, TestContext)
{
SETTER_A_B(ValueY, 212, 37);
SETTER_A_B(ValueX, 20, 30);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(a.getValueY() == result.getValueY());
}
// more test cases that are similar to those above
BOOST_AUTO_TEST_SUITE_END();
While I will refactor PoorLegacyClass
in the future that is not the subject of this question. What I would like to know, though, is how to avoid using the #define
macro.
Upvotes: 1
Views: 102
Reputation: 171177
Since you're willing to accept the syntax set(a, b, &PoorLegacyClass::setValueX, 20, 30);
, implementing such a set
should be easy:
template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), T valForA, T valForB)
{
(a.*setter)(valForA);
(b.*setter)(valForB);
}
You could also make it so that the valFor
parameters use T
in a non-deduced context, so that T
can only be deduced from the setter and implicit conversions happen on the values:
template <class T>
struct NonDeduced { using type = T; }
template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), typename NonDeduced<T>::type valForA, typename NonDeduced<T>::type valForB)
Upvotes: 2
Reputation: 3324
Both set(a, b, ValueX, 20, 30)
or set<ValueX>(a, 20, b, 30)
would require some kind of preprocessing to concatenate "set"
and "ValueX"
into a new term.
Personally I think a bit of preprocessor is better than a ton of boilerplate, but as @Angew said, you can easily go with the third option:
set(a, b, &PoorLegacyClass::setValueX, 20, 30);
The only thing I wanted to add is you can use variadic templates to make this work for an arbitrary amount of objects:
template<class T> void set(void (PoorLegacyClass::*setter)(T))
{}
template<class T, typename... Args> void set(void (PoorLegacyClass::*setter)(T),
PoorLegacyClass& obj, T val, Args&&... args)
{
(obj.*setter)(val);
set(setter, std::forward<Args>(args)...);
}
The call syntax would slightly change in this case:
set(&PoorLegacyClass::setValueX, a, 20, b, 30);
Upvotes: 1
Reputation: 33655
You could try this:
BOOST_FUSION_ADAPT_STRUCT
to make a fusion sequence of your structure.fusion::at_c<N>
to access the Nth element and set it's valueat_c<N>
to access a member appropriately.Should avoid the need for macros..
Upvotes: 0