The pImpl idiom makes testing far easier. It's strange enough to see a set of answers on the theme of "don't test the implementation" to motivate answering so long after the OP.
In usual, non-pimpl based C++ you have a class with public and private fields. Public fields are easy to test, private fields somewhat more tedious. The division between public and private is important though, since it decreases the width of the api and usually makes later changes easier.
When using this idiom a better option is available. You can have exactly the same "public" interface as with a single class, but now there's only one private field containing a pointer of some sort, e.g.
class my_things
{
public:
my_things();
~my_things();
void do_something_important(int);
int also_this();
private:
struct my_things_real;
std::unique_ptr<my_things_real> state;
};
The my_things_real class is expected to be visible in the same source file as the destructor of the externally visible class, but not in the header. It isn't part of the public interface, so all the fields can be public.
void my_things::do_something_important(int x) { state->doit(x); } // etc
class my_things_real // I'd probably write 'struct'
{
public:
int value;
void doit(int x) { value = x; }
int getit() { return value; }
};
Unit tests are then written against the real class. Test as much or as little of it as you like. I've deliberately called it "real" instead of "impl" to help ensure that it isn't mistaken for a mere implementation detail.
Testing this class is very easy since all the fields are public. The external interface is very small since it's defined by the other class. The wafer-thin translation layer is difficult to get wrong, but you're still welcome to test through the external api as well. This is a clear win from more significantly separating interface and implementation.
On a vaguely related note, it strikes me as absurd that so many otherwise coherent people advocate skipping unit testing for anything that is not readily accessible through the external API. The lowest level functions are hardly immune to programmer errors. Testing to verify that the api is usable is both important and orthogonal to verifying that the implementation details are correct.