Can Googletest value-parameterized with multiple, different types of parameters match mbUnit flexibility?

29,361

Solution 1

Yes, there's a single parameter. You can make that parameter be arbitrarily complex, though. You could adapting the code from the documentation to use you Row type, for example:

class AndyTest : public ::testing::TestWithParam<Row> {
  // You can implement all the usual fixture class members here.
  // To access the test parameter, call GetParam() from class
  // TestWithParam<T>.
};

Then define your parameterized test:

TEST_P(AndyTest, CountViaDirectSQLCommand)
{
  // Call GetParam() here to get the Row values
  Row const& p = GetParam();
  std::string dbFilePath = testDBFullPath(p.dbname);
  {
    StAnsi fpath(dbFilePath);
    StGdbConnection db(p.fpath);
    db.Connect(p.fpath);
    int result = db.ExecuteSQLReturningScalar(StAnsi(p.command));
    EXPECT_EQ(p.numRecs, result);
  }
}

Finally, instantiate it:

INSTANTIATE_TEST_CASE_P(InstantiationName, AndyTest, ::testing::Values(
  Row("Empty.mdb", "select count(*) from collar", 0),
  Row("SomeCollars.mdb", "select count(*) from collar", 17),
  Row("SomeCollars.mdb", "select count(*) from collar where max_depth=100", 4)
));

Solution 2

An alternative to using a custom structure as the parameter is to use the parameter generator ::testing::Combine(g1, g2, ..., gn). This generator allows you to combine the other parameter generators into a set of parameters with a type std::tuple that has a template type that matches the types of the values provided.

Note that this generator produces the Cartesian product of the values provided. That means that every possible ordered tuple will be created. I believe the original question is asking for a strict array of parameters with the provided values, which this does not support. If you need to have an array of strict parameters, you could use a tuple with the parameter generator ::testing::Values(v1, v2, ..., vN) where each value is a separate tuple.

Example:

#include <string>
#include <tuple>

class MyTestSuite : 
  public testing::TestWithParam<std::tuple<std::string, std::string, int>>
{

};

TEST_P(MyTestSuite, TestThatThing)
{
  functionUnderTest(std::get<0>(GetParam()), 
                    std::get<1>(GetParam()), 
                    std::get<2>(GetParam()));
  . . .
}

INSTANTIATE_TEST_SUITE_P(
  MyTestGroup,
  MyTestSuite,
  ::testing::Combine(
    ::testing::Values("FirstString1", "FirstString2"),
    ::testing::Values("SecondString1", "SecondString2"),
    ::testing::Range(10, 13)));

INSTANTIATE_TEST_SUITE_P(
  MyOtherTestGroupThatUsesStrictParameters,
  MyTestSuite,
  ::testing::Values(
    {"FirstString1", "SecondString1", 10},
    {"FirstString2", "SecondString2", 32},
    {"FirstString3", "SecondString3", 75}));

In the above example, the parameters created for MyTestGroup would look like the following:

[
  {"FirstString1", "SecondString1", 10},
  {"FirstString1", "SecondString1", 11},
  {"FirstString1", "SecondString1", 12},
  {"FirstString1", "SecondString2", 10},
  {"FirstString1", "SecondString2", 11},
  {"FirstString1", "SecondString2", 12},
  {"FirstString2", "SecondString1", 10},
  {"FirstString2", "SecondString1", 11},
  {"FirstString2", "SecondString1", 12},
  {"FirstString2", "SecondString2", 10},
  {"FirstString2", "SecondString2", 11},
  {"FirstString2", "SecondString2", 12}
]

Refer to the GoogleTest documentation for further details. (Accessed on 12/17/2019)

Share:
29,361
Andy Dent
Author by

Andy Dent

CEO and Founder of mobile messaging startup touchgr.am. Xamarin SDK developer at Realm 2015...2017 Graduate at the Founder Institute, from the 2014 semester in Perth Western Australia. Author of "Getting Started with LevelDB" from Packt 2013. non-computing interests: Chow Gar Kung Fu and Cheng Man-ch'ing Tai Chi, reading and (so-far unpublished) writing SF snow skiing scuba diving, solar housing, carpentry computing interests: interface animation Designing for naive users programming languages and tools code generation Usability, cross-platform development

Updated on July 21, 2022

Comments

  • Andy Dent
    Andy Dent almost 2 years

    I'd like to write C++ Google tests which can use value-parameterized tests with multiple parameters of different data types, ideally matching the complexity of the following mbUnit tests written in C++/CLI.

    For an explanation of mbUnit, see the Hanselman 2006 article. As of this 2019 edit, the other links he includes are dead.

    Note how compact this is, with the [Test] attribute indicating this is a test method and the [Row(...)] attributes defining the values for an instantiation.

    [Test]
    [Row("Empty.mdb", "select count(*) from collar", 0)]
    [Row("SomeCollars.mdb", "select count(*) from collar", 17)]
    [Row("SomeCollars.mdb", "select count(*) from collar where max_depth=100", 4)]
    void CountViaDirectSQLCommand(String^ dbname, String^ command, int numRecs)
    {
       String^ dbFilePath = testDBFullPath(dbname);
       {
           StAnsi fpath(dbFilePath);
           StGdbConnection db( fpath );
           db->Connect(fpath);
           int result = db->ExecuteSQLReturningScalar(StAnsi(command));
           Assert::AreEqual(numRecs, result);
       }
    }
    

    Or even better, this more exotic testing from C# (pushing the boundaries of what can be defined in .Net attributes beyond what's possible in C++/CLI):

    [Test]
    [Row("SomeCollars.mdb", "update collar set x=0.003 where hole_id='WD004'", "WD004",
        new string[] { "x", "y" },
        new double[] { 0.003, 7362.082 })]  // y value unchanged 
    [Row("SomeCollars.mdb", "update collar set x=1724.8, y=6000 where hole_id='WD004'", "WD004",
        new string[] { "x", "y" },
        new double[] { 1724.8, 6000.0 })]
    public void UpdateSingleRowByKey(string dbname, string command, string idValue, string[] fields, double[] values)
    {
    ...
    }
    

    The help says Value-parameterized tests will let you write your test only once and then easily instantiate and run it with an arbitrary number of parameter values. but I'm fairly certain that is referring to the number of test cases.

    Even without varying the data types, it seems to me that a parameterized test can only take one parameter?

    2019 update

    Added because I got pinged about this question. The Row attribute shown is part of mbUnit.

    For an explanation of mbUnit, see the Hanselman 2006 article. As of this 2019 edit, the other links he includes are dead.

    In the C# world, NUnit added parameterised testing in a more powerful and flexible way including a way to handle generics as Parameterised Fixtures.

    The following test will be executed fifteen times, three times for each value of x, each combined with 5 random doubles from -1.0 to +1.0.

    [Test]
    public void MyTest(
        [Values(1, 2, 3)] int x,
        [Random(-1.0, 1.0, 5)] double d)
    {
        ...
    }
    

    The following test fixture would be instantiated by NUnit three times, passing in each set of arguments to the appropriate constructor. Note that there are three different constructors, matching the data types provided as arguments.

    [TestFixture("hello", "hello", "goodbye")]
    [TestFixture("zip", "zip")]
    [TestFixture(42, 42, 99)]
    public class ParameterizedTestFixture
    {
        private string eq1;
        private string eq2;
        private string neq;
    
        public ParameterizedTestFixture(string eq1, string eq2, string neq)
        {
            this.eq1 = eq1;
            this.eq2 = eq2;
            this.neq = neq;
        }
    
        public ParameterizedTestFixture(string eq1, string eq2)
            : this(eq1, eq2, null) { }
    
        public ParameterizedTestFixture(int eq1, int eq2, int neq)
        {
            this.eq1 = eq1.ToString();
            this.eq2 = eq2.ToString();
            this.neq = neq.ToString();
        }
    
        [Test]
        public void TestEquality()
        {
            Assert.AreEqual(eq1, eq2);
            if (eq1 != null && eq2 != null)
                Assert.AreEqual(eq1.GetHashCode(), eq2.GetHashCode());
        }
    
        [Test]
        public void TestInequality()
        {
            Assert.AreNotEqual(eq1, neq);
            if (eq1 != null && neq != null)
                Assert.AreNotEqual(eq1.GetHashCode(), neq.GetHashCode());
        }
    }