Blink‎ > ‎

Unit Testing in Blink

WARNING: This document is a work in progress!


Unit Testing Tools

GTest and GMock are both imported into Blink and can be used in unit tests. Most existing tests are purely GTest based, but GMock is slowly being used more.

GTest - Google Unit Testing Framework

"Google's framework for writing C++ tests on a variety of platforms (Linux, Mac OS X, Windows, Cygwin, Windows CE, and Symbian). Based on the xUnit architecture. Supports automatic test discovery, a rich set of assertions, user-defined assertions, death tests, fatal and non-fatal failures, value- and type-parameterized tests, various options for running the tests, and XML test report generation."

Further Documentation

Tip: GTest and regular expressions (regexp)

If you are using gtest "Death Tests" or GMock's EXPECT_THAT with MatchesRegex or ContainsRegex, you have to be very careful with your regex

There is no common syntax between all operating system for character class matches;
  • Character class short cuts are NOT part of the POSIX regex standard and DO NOT work on Mac OS X. It also wont give you a warning saying the regex is invalid.
EXPECT_THAT("abc", MatchesRegex("\w*")) # Does NOT work on Mac OS X.
  • Character classes (IE the square bracketed kind) DO NOT work with the gtest internal regex engine, and thus on Windows. At least it will warn you that the regex is invalid.
 EXPECT_THAT("abc", MatchesRegex("[a-c]*")) # Does NOT work on Windows.

(CL https://codereview.chromium.org/55983002/ proposes making chromium only use the gtest internal regex engine which would fix this problem.)

Tip: Using GMock matchers with GTest

You can use GMock EXPECT_THAT and the GMock matchers inside a GTest test for more powerful matching. 

Quick example;
EXPECT_THAT(Foo(), testing::StartsWith("Hello"));
EXPECT_THAT(Bar(), testing::MatchesRegex("Line \\d+"));
ASSERT_THAT(Baz(), testing::AllOf(testing::Ge(5), testing::Le(10)));

Value of: Foo()
  Actual: "Hi, world!"
Expected: starts with "Hello"

More information at;

Error: Has the "template<typename T> operator T*()" private.


Workaround:

namespace testing {
namespace internal {

// gtest tests won't compile with clang when trying to EXPECT_EQ a class that
// has the "template<typename T> operator T*()" private.
// (See https://code.google.com/p/googletest/issues/detail?id=442)
//
// Work around is to define this custom IsNullLiteralHelper.
char(&IsNullLiteralHelper(const WebCore::CSSValue&))[2];

}
}




GMock - Google C++ Mocking Framework


Inspired by jMock, EasyMock, and Hamcrest, and designed with C++'s specifics in mind, Google C++ Mocking Framework (or GMock for short) is a library for writing and using C++ mock classes.

Further Documentation

  • Blah
  • Blah

Tip: GMock and regular expressions (regexp)

GMock uses the gtest for doing the regexs, see the section under gtest above.

Tip: Mocking non-virtual functions

For speed reasons, a majority of Blink's functions are non-virtual. This can make them quite hard to mock. Here are some tips for working around these problems;

Tip: Mock Injection (Dependency Injection)



Using a proxy interface internally in your class;

// MyClass.h
// ------------------------------------------------------------
class MyClass {
private:
    class iExternal {
    public:
        virtual void function1(int a, int b);
        virtual bool function2();

        static iExternal* instance() { return pInstance(); }
        static void setInstanceForTesting(iExternal* newInstance) { pInstance(true, newInstance); }
        static void clearInstanceForTesting() { pInstance(true0); }

    protected:
        iExternal() { }

    private:
        inline static iExternal* pInstance(bool set = false, iExternal* newInstance = 0)
        {
            static iExternal* defaultInstance = new iExternal();
            static iExternal* instance = defaultInstance;
            if (set) {
                if (!newInstance) {
                    newInstance = defaultInstance;
                }
                instance = newInstance;
            }
            return instance;
        }
    };

public:
    void aFunction() {
        if (iExternal::instance()->function2()) {
            iExternal::instance()->function1(12);
        }
    }
}



// MyClassTest.cpp
// ------------------------------------------------------------
class MyClassTest : public ::testing::Test {
    class iExternalMock : public MyClass::iExternal {
    public:
        MOCK_METHOD0(function1, void(intint));
        MOCK_METHOD0(function2, bool());
    };

    void setInstanceForTesting(MyClass::iExternal& mock) {
        MyClass::iExternal::setInstanceForTesting(&mock);
    }
};

TEST_F(MyClassTest, aFunctionTest)
{
    iExternalMock externalMock;

    EXPECT_CALL(externalMock, function2())
        .WillOnce(Return(true))
        .WillOnce(Return(false));

    EXPECT_CALL(externalMock, function1(12));

    setInstanceForTesting(externalMock);

    MyClass c;
    c.aFunction();
}

Tip: Mocks and OwnPtr (PassOwnPtr)

OwnPtr and mocking objects can be tricky to get right, here is some important information.

The Problem

As normal, once you return an object via a PassOwnPtr you no longer control the life cycle of the object. This means that you must not use the object as an expectation (EXPECT_CALL) for another function call because;
  • On each call, GMock checks if any of the expectations match.
  • On termination, if something went wrong GMock might try to print the expectation (for both matched and unmatched expectations).

Here is some example code which is WRONG:
  • At (1) myA1 has been deleted, but GMock will check both the mockb EXPECT_CALLs.
  • At (2) both myA1 and myA2 have been deleted, but if EXPECT_CALL is not matched GMock may try to print myA1 and myA2.
// Actual implementation
class A {};

class B {
   virtual use(A& a) {} {}
};

class C {
   B* m_b;
   C(B* b): m_b(b) {}

   void doIt(PassOwnPtr<A> myA) {
     m_b->use(*myA);
     // As we own myA it gets deleted here.
   }
};

// Mocks
class MockB : public B {
    MOCK_METHOD0(use, void(A&));
};

// Test
TEST(MyTest, CDoItTest)
{
  OwnPtr<A> myA1 = adoptPtr(new A());
  OwnPtr<A> myA2 = adoptPtr(new A());

  MockB mockb;
  EXPECT_CALL(mockb, use(Ref(*myA1.get()))); // Ref() means "is a reference to"
  EXPECT_CALL(mockb, use(Ref(*myA2.get())));

  C c(&mockb);
  c.doIt(myA1.release());
  c.doIt(myA2.release()); // (1)

  // (2)
}

Solutions that don't work

Creating a specialization of OwnedPtrDeleter

template <> struct OwnedPtrDeleter<MyClass> {}

Why?
The OwnedPtrDeleter specialization must be visible at the location that the OwnPtr/PassOwnPtr is created.




Test Helpers

Test helpers are an important part of making Blink easier to test for everyone. The more test helpers that exist, the easier it is to write new unit tests as you have to write less boilerplate code and find it easier to debug failing tests.

Test helpers include;
  • Pretty printing functions for types.
  • Shared fake implementations of complex types.
  • Quick creation functions for types.
  • operator== definitions to allow EXPECT_EQ and comparisons in EXPECT_CALL mocks to work. 
Test helpers should;
  • be define in a "XXXTestHelper.h" file, where XXX is the type (or type group) that it helps (there might also be a XXXTestHelper.cpp in rare cases).

  • have some basic tests to make sure they work. Nobody wants to debug test helpers while writing their own tests!
    • This is specially important for PrintTo functions to make sure they actually print what you expect. You can use the EXPECT_THAT with Regex from GMock to make these tests easier to write.
    • These should be in a XXXTestHelperTest.cpp file (shouldn't need a header file).

Operator==

Both the EXPECT_EQ and the EXPECT_CALL methods use a == b to compare if two objects are equal. However for many reasons you don't want this operator to be generally available in Blink. You can define the operator in the test helper instead and then it will only be available during tests. 

Unlike PrintTo, operator== is not a template so the normal type hierarchy applies.

bool operator==(const TYPE& a, const TYPE& b)
{
    return SOMETHING
}

Operator== Gotchas - Namespacing

The operator== MUST be define in the same namespace as the type for EXPECT_EQ to work. For example, if type is ::WebCore::AnimatableValue the operator must be in the ::WebCore namespace.

Pretty Printers

Pretty printers make it much easier to see what is going on in your test and why a match isn't working. They should be created for any simple type which has a useful string representation.


void PrintTo(const A& a, ::std::ostream* os)
{
    *os << "A@" << &a;
}

More Information on creating pretty printers can be found at GTest Advanced Guide: Teaching Google Test How to Print Your Values.

Pretty Printers Gotchas - Namespace

Pretty Printers must be define in the same namespace as the class which it is printing.

namespace A {
  class A{};
}

namespace {
  void PrintTo(const A& a, ::std::ostream* os) {} // Never called
}

Pretty Printers Gotchas - Type matching

Pretty Printers only work on exact and known type match. This means that;
  • A PrintTo for a base class will not apply to children classes.
  • A PrintTo for a specific class will not apply when that class is referenced/pointed to as a base class.
Further information at the gtest bug tracker - https://code.google.com/p/googletest/issues/detail?id=443

This is hard to understand, but shown by the following example (also attached as printto-confusing.cpp).

#include <iostream>
#include <gtest/gtest.h>

using std::cout;
using testing::PrintToString;

class A {};
class B : public A {};
class C : public B {};


void PrintTo(const A& a, ::std::ostream* os)
{
    *os << "A@" << &a;
}

void PrintTo(const B& b, ::std::ostream* os)
{
    *os << "B@" << &b;
}

int main() {
    A a;
    B b;
    C c;

    A* a_ptr1 = &a;
    B* b_ptr = &b;
    A* a_ptr2 = &b;
    C* c_ptr = &c;
    A* a_ptr3 = &c;

    cout << PrintToString(a) << "\n";       // A@0xXXXXXXXX
    cout << PrintToString(b) << "\n";       // B@0xYYYYYYYY
    cout << PrintToString(c) << "\n";       // 1-byte object <60>
    cout << PrintToString(*a_ptr1) << "\n"// A@0xXXXXXXXX
    cout << PrintToString(*b_ptr) << "\n";  // B@0xYYYYYYYY
    cout << PrintToString(*a_ptr2) << "\n"// A@0xYYYYYYYY
}


You can work around this problem by also defining a couple of extra PrintTo methods (also attached as printto-workaround.cpp).

#include <iostream>
#include <gtest/gtest.h>

using std::cout;
using testing::PrintToString;

#define OVERRIDE override

// MyClass.h
// ---------------------------------------------------------------

class MyClassA {

// As WebKit is compiled without RTTI, the following idiom is used to
// emulate RTTI type information.
protected:
   enum MyClassType {
     BType,
     CType
   };

   virtual MyClassType type() const = 0;

public:
    bool isB() const { return type() == BType; }
    bool isC() const { return type() == CType; }
};

class MyClassB : public MyClassA {
    virtual MyClassType type() const OVERRIDE { return BType; }
};
class MyClassC : public MyClassB {
    virtual MyClassType type() const OVERRIDE { return CType; }
};

// MyClassTestHelper.h
// ---------------------------------------------------------------

void PrintTo(const MyClassB& b, ::std::ostream* os)
{
    *os << "B@" << &b;
}

// Make C use B's PrintTo
void PrintTo(const MyClassC& c, ::std::ostream* os)
{
    PrintTo(*static_cast<const MyClassB*>(&c), os);
}

// Call the more specific subclass PrintTo method
// *WARNING*: The base class PrintTo must be defined *after* the other PrintTo
// methods otherwise it'll just call itself.
void PrintTo(const MyClassA& a, ::std::ostream* os)
{
    if (a.isB()) {
        PrintTo(*static_cast<const MyClassB*>(&a), os);
    } else if (a.isC()) {
        PrintTo(*static_cast<const MyClassC*>(&a), os);
    } else {
        //ASSERT_NOT_REACHED();
    }
}

int main() {
    MyClassB b;
    MyClassC c;

    MyClassB* b_ptr = &b;
    MyClassA* a_ptr1 = &b;
    MyClassC* c_ptr = &c;
    MyClassA* a_ptr2 = &c;

    cout << PrintToString(b) << "\n";       // B@0xYYYYYYYY
    cout << PrintToString(*b_ptr) << "\n";  // B@0xYYYYYYYY
    cout << PrintToString(*a_ptr1) << "\n"// B@0xYYYYYYYY

    cout << PrintToString(c) << "\n";       // B@0xAAAAAAAA
    cout << PrintToString(*c_ptr) << "\n";  // B@0xAAAAAAAA
    cout << PrintToString(*a_ptr2) << "\n"// B@0xAAAAAAAA
}



Future Proposals

All these issues are up for discussion and have yet to be decided on;

Č
ċ
ď
printto-confusing.cpp
(1k)
Tim Ansell,
Oct 28, 2013, 6:11 PM
ċ
ď
printto-workaround.cpp
(2k)
Tim Ansell,
Oct 28, 2013, 8:36 PM
Comments