Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testing private members - ability to write test cases in class bodies #76

Open
r-barnes opened this issue Jun 24, 2017 · 12 comments
Open
Labels
has-workaround There is a workaround for the issue which can be used before (and if) fixing verdict/stale Issue is not precise and has been inactive for a long time

Comments

@r-barnes
Copy link

Description

Some classes have private methods which cannot be conveniently tested through a public interface.

What is the recommended way to test a private method using doctest?

Extra information

  • doctest version: v1.2.1
  • Operating System: Lubuntu 17.04 (Zesty)
  • Compiler+version: G++ 6.3.0
@onqtam
Copy link
Member

onqtam commented Jun 26, 2017

This is the only thing I'm able to come up with:

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"

class A {
    void private_method() { printf("hello!\n"); }
    
#ifdef DOCTEST_LIBRARY_INCLUDED
public:
    static void test1() {
        A a;
        a.private_method();
    }
#endif
};

DOCTEST_REGISTER_FUNCTION(A::test1, "test name")

// or like this:
// TEST_CASE("test name") { A::test1(); }

The problem is that I need to create a global dummy variable outside of the class to force the execution of code before main() is called so the test is automatically registered. That's why tests cannot be fully embedded inside of classes...

@onqtam onqtam closed this as completed Aug 3, 2017
@onqtam
Copy link
Member

onqtam commented Aug 3, 2017

I'm closing this - not sure what else I could suggest

@onqtam onqtam changed the title Private members [question] Private members Aug 3, 2017
@JankoDedic
Copy link

Could this be solved using C++17 inline variables? A global dummy variable could be defined inside of the class as inline static.

@onqtam
Copy link
Member

onqtam commented Feb 9, 2019

@JankoDedic turns out it is possible! there was one other problem but I managed to solve it, so now I'm thinking of what to call the new macro - perhaps TEST_CASE_CLASS ...?

@onqtam onqtam reopened this Feb 9, 2019
@onqtam onqtam changed the title [question] Private members testing private members - ability to write test cases in class bodies Feb 9, 2019
@JankoDedic
Copy link

That's amazing to hear! I just made it work a few minutes ago as well :). Not sure about the name, but TEST_CASE_CLASS is good with me :)

@onqtam
Copy link
Member

onqtam commented Feb 9, 2019

I just pushed the change in the dev branch - here is how to use it (no matter if in a source or a header file):

class my_type {
    int data = 5;

    TEST_CASE_CLASS("private test! no problem") {
        my_type local;
        CHECK(local.data == 2);
    }

public:
    my_type() = default;

    TEST_CASE_CLASS("public test! doesn't matter") {
        my_type local;
        CHECK(local.data == 2);
    }
};

@u3shit
Copy link

u3shit commented Mar 27, 2021

Unfortunately this TEST_CASE_CLASS macro requires writing the test case inside the class body, which might be ok when you have a header only library, but it's not that good when you have cpp/hpp files, and you would have to include a bunch of heavy includes in the hpp just for the test case. But I think it should be possible to split it into two, something like this (POC code follows, this won't work with templates):

#define DOCTEST_TEST_CASE_CLASS_DECLARE(name) \
    static const int DOCTEST_CAT(name, _VAR); \
    static void DOCTEST_CAT(name, _FUNC)()
#define DOCTEST_TEST_CASE_CLASS_DEFINE(name, decorators)                    \
    const int DOCTEST_CAT(name, _VAR) = doctest::detail::regTest(           \
      doctest::detail::TestCase(                                            \
        DOCTEST_CAT(name, _FUNC), __FILE__, __LINE__,                       \
        doctest_detail_test_suite_ns::getCurrentTestSuite()) * decorators); \
    void DOCTEST_CAT(name, _FUNC)()

Then you can do DOCTEST_TEST_CASE_CLASS_DECLARE(something); in the class body, and later in the cpp DOCTEST_TEST_CASE_DEFINE(class_name::something, "some test") { ... }. Compared to TEST_CASE_CLASS this allows you to write your tests not inline and it does not require C++17, compared to the original suggestion by @onqtam this does not require making the test function public. It still requires a declaration for each test case you have in your header though.


A different solution (I don't think this would need doctest support, I'm just mentioning this for possible users ending up here after a google search) that we use at my company (with google test, eww) is to have a second class (e.g. foo and foo_test), friend that second class, and write tests as public member functions of this class. This is more boilerplate and pretty ugly, but this way you don't have to clutter your public headers with test relted cruft (it's just a fwd decl+a friend).

@onqtam
Copy link
Member

onqtam commented Mar 28, 2021

@u3shit In that case I'm reopening this so I don't forget about it!

@cdeln
Copy link

cdeln commented Aug 15, 2024

I'm curious that good old friend has not been discussed yet. Portable and trivial way to get access to private members of a class.

@cdeln
Copy link

cdeln commented Aug 16, 2024

Also good old getter :) I suggest we close this due to several reasons

  1. Feature creep, several simple workarounds already available
  2. It's C++17 and above. The TEST_CASE_CLASS feature was a semi-good idea to begin with.

@cdeln cdeln added verdict/stale Issue is not precise and has been inactive for a long time has-workaround There is a workaround for the issue which can be used before (and if) fixing labels Aug 16, 2024
@cdeln
Copy link

cdeln commented Aug 18, 2024

Here is a c++17 workaround template that people can use if they really want to test private members of a class

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

template <typename T>
struct Inspect {
    const T &self;

    Inspect(const T &self) : self{self} {}

    auto get();
};

class X {
private:
    int data;
public:
    X(int data) : data{data} {}

    friend class Inspect<X>;
};

template <> auto Inspect<X>::get() {
    return self.data;
}

TEST_CASE("private") {
    X x(1337);
    REQUIRE(Inspect(x).get() == 1337);
}

@ns6089
Copy link

ns6089 commented Sep 23, 2024

Also adding forward declarations into the mix allows to create minimal macros:

#define TEST_PRIVATE struct test_inspector_t; friend test_inspector_t;
#define TEST_PRIVATE_IMPL(x) struct x::test_inspector_t
class testee_t
{
    int m_private_field = 1;
    TEST_PRIVATE
};
TEST_PRIVATE_IMPL(testee_t)
{
    TEST_CASE_CLASS("private field")
    {
        testee_t testee;
        CHECK(testee.m_private_field == 1);
    }
};

Maybe similar macros can be added to doctest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has-workaround There is a workaround for the issue which can be used before (and if) fixing verdict/stale Issue is not precise and has been inactive for a long time
Projects
None yet
Development

No branches or pull requests

6 participants