문제

Note: I know that active_ could be "anything" in my example. This is not what this question is about. It's about making an "undefined value" reliably fail a unit test.

Edit: Changed from "no constructor" to "empty constructor".

I'm working on a C++ class and I'm using TDD. Now I want to ensure that a bool class member is initialized properly - that a value is assigned to it in the constructor. So I write the following test (using the Google Mock/Google Test framework):

TEST(MyClass, isNotActiveUponCreation) {
    MyClass my;
    ASSERT_FALSE(my.isActive());
}

And the following class definition:

class MyClass {
public:
    // Note: Constructor doesn't initialize active_
    MyClass() {}

    bool isActive() const { return active_; }
private:
    bool active_;
};

The problem: On my machine, this test currently always passes, even though active_ is never initialized. Now we know that the value of active_ is undefined, since it's a primitive type and never initialized. So in theory, it might be true at some point, but in the end, it's impossible to know. Bottom line is, I cannot reliably test for missing initialization using this approach.

Does anybody have an idea how I might test this situation in a way that's deterministic and repeatable? Or do I have to live with it, omit this kind of test and hope that I'll never forget to initialize a boolean member, or that other tests will always catch resulting defects?

도움이 되었습니까?

해결책

After reading TobiMcNamobi's answer, I remembered placement new and got an idea how to solve my problem. The following test reliably fails unless I initialize active_ in the constructor:

#include <gmock/gmock.h>
#include <vector>

class MyClass {
public:
    // Note: Constructor doesn't initialize active_
    MyClass() {}

    bool isActive() const { return active_; }
private:
    bool active_;
};

TEST(MyClass, isNotActiveUponCreation) {
    // Memory with well-known content
    std::vector<char> preFilledMemory(sizeof(MyClass), 1);

    // Create a MyClass object in that memory area using placement new
    auto* myObject = new(preFilledMemory.data()) MyClass();


    ASSERT_FALSE(myObject->isActive());
    myObject->~MyClass();
}

Now I'll admit that this test is not the most readable one, and likely not immediately clear at first sight, but it works reliably and independent of any 3rd-party tools like valgrind. Is it worth the additional effort? I'm not sure. It heavily depends on MyClass internals, which will make it very brittle. Anyway, it is one way to test for correctly initialized objects in C++..

다른 팁

Such kind of problems is actually quite easy to unit tests, once you have unit tests in place.

Just run unit tests under memory checker (valgrind on linux, not sure what is used on windows).

Instead of creating gtest executable, I created a simple example :

#include <iostream>

class MyClass {
public:
    // Note: no constructor

    bool isActive() const { return active_; }
private:
    bool active_;
};

int main()
{
    MyClass c;  // line 17

    std::cout << c.isActive() << std::endl;
}

Running it under valgrind, I got next output (trimmed unneeded lines) :

==9217== 
==9217== Conditional jump or move depends on uninitialised value(s)
.....
==9217==    by 0x40094F: main (garbage.cpp:17)

When you execute your unit tests with valgrind, you will get all kind of problems related to memory access. You will also get backtraces.

My little test:

#include <stdlib.h>
#include <vector>
#include <iostream>

class MyClass {
public:
    // Note: Constructor doesn't initialize active_
    MyClass() {}

    bool isActive() const { return active_; }
private:
    bool active_;
};

int main(int argc, char* argv[])
{
    std::vector<MyClass> vec(1000);
    for (int i = 0; i < 1000; i++)
    {
        std::cout << (vec[i].isActive() ? "1" : "0");
    }

    return system("pause");
}

So what happens when this is executed (compiled with VS2012)?

Debug configuration: A thousand 1's are written.

Release config: A thousand 0's are written. I raised the iteration count to 100,000 and got a hundred-thousand 0's ... no wait the first few numbers were 10101110000000... and there! Somewhere in between another sequence like this is hidden.

What does this mean? The result was more or less expected. You cannot predict how a single uninitialized bit is set. What you want to do here is initialize some space in memory and create an object just there as if that memory had not initialized before.

So until I'm proven wrong: You cannot unit-test it.

Unless you use a tool (e.g. valgrind, see other answer).

TDD is great I think but it has its limits of course. I'd just initialize the flag and continue the red-green-refactor cycle.

A TDD test should exercise behavior rather than implementation details. Tests for constructor initialization, setter initialization, etc. depend on a specific implementation and will be fragile if the implementation is refactored.

What you are trying to do is to unit test for undefined behavior which is pointless as all results would obviously need to be accepted.

If you compile your unit test suite with MemorySanitizer, then any reads from uninitialized memory should cause the tests to fail.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top