2

I have a C++ 11 header which has a const value declared as my_const_value. And a function called GetValue that runs a complex logic using the const value and returns an expected value.

I want to unit test GetValue with different values of my_const_value.

I know this is not advisable but for writing a unit test for GetValue, I wish to test GetValue with different values of my_const_value. Is there some hack-ish way in C++ to change the value of a const even if it a const?

//MyHeader.hpp
namespace myheader {

const int my_const_value = 5;

int GetValue() {
    // In real-world, lets say below line of code is a complex logic that needs to be tested by a unit test
    return /my_const_value * 5) / 25;
}

}

#include "MyHeader.hpp"
#include <gtest/gtest.h>

TEST(MyHeaderTest, Testing_Something) {
    EXPECT_EQ(1, myheader::GetValue()); // This is okay

    // I want to test that in the future is the value of my_const_value changes to something else then 
    // myheader::GetValue returns the expected result. But of course, I cannot change my_const_value because it is a const.
    // Is there a way to hack around this for a unit test? Is there a way that I could still hack and change the value of my_const_value?
    myheader::my_const_value = 25;
    EXPECT_EQ(5, myheader::GetValue());
}

I know that I could const_cast my_const_value to a non_const variable. But that wouldn't help here. If there is some hack to change the value of my_const_value by using a pointer or something, that would answer my question.

TheWaterProgrammer
  • 7,055
  • 12
  • 70
  • 159
  • 1
    Nothing that has any guarantee of working, since the compiler is quite likely to replace all uses of `my_const_value` with a "real constant", which cannot be modified. Any attempt to modify it will invoke UB – UnholySheep Aug 26 '20 at 08:10
  • A way more manageable way would be to make `my_const_value` non-`const` when you are running tests, e.g.: by having a preprocessor define – UnholySheep Aug 26 '20 at 08:12
  • Make a local variable of type `const int*`, initialize with the address of the global, use `const_cast` to convert the pointer to non-const, write new value using the pointer. – Soonts Aug 26 '20 at 08:13
  • Why do you want to test it with different values if it can only have a single value? The normal flow is that you update the only value *and* the test when you need to change it. – molbdnilo Aug 26 '20 at 08:17
  • 1
    @Soonts That has undefined behaviour. – molbdnilo Aug 26 '20 at 08:21
  • If you want to test the calculation with different values, refactor so you have a function which takes the input as an argument and test that, then you call that function from `GetValue`. – molbdnilo Aug 26 '20 at 08:24
  • 2
    Move the logic that is in `GetValue()` to another function (say, for sake of discussion, `GetValueInternal()`) that accepts an argument of type `int`. Then reimplement `GetValue()` so it calls `GetValueInternal(my_const_value)` and returns the result. Then write your unit tests as calls of `GetValueInternal()`, passing whatever values you seek. Optionally, you may also want to write a unit test to verify that `GetValue()` and `GetValueInternal(5)` produce the same result, under whatever condtions are appropriate. – Peter Aug 26 '20 at 08:25
  • Might I ask, why *would* you want to test getValue() in this way, given that the `my_const_value` never changes? There's no point in testing stuff that can't happen is there? Folk overdo this unit testing stuff you know. – Bathsheba Aug 26 '20 at 08:27
  • Well. I have given my reason. I am testing the logic in `GetValue`. I have declared `my_const_value` as a `const` but that can be changed from 5 to something else in future when someone changes the value in future. That is surely possible and thats why I want `GetValue` to get tested for that change. Imagine that this is some version identifier which is declared and placed in a common header used across 2 peer applications. The version can change in future for sure but not at run time. Hence declared const. You guys get my reasoning here? – TheWaterProgrammer Aug 26 '20 at 08:35
  • 2
    @molbdnilo I know, but the OP has specifically asked for a hack. – Soonts Aug 26 '20 at 08:38
  • I called it a hack because I know that the moment I ask this, people would come back rightly calling it to be wrong. I understand that point and I agree as well. But I have a valid reason here which I have pointed out in above comment. – TheWaterProgrammer Aug 26 '20 at 08:40
  • 3
    Given that the value of `my_const_value` may change in future, my previous comment is applicable. Implement your functions so they are testable i.e. break out the logic in a way that allows testing as you wish. Any "hackish" solution to change `my_const_value` will involve undefined behaviour. Unit tests that rely on (some incantation of) undefined behaviour are not a good idea. Unit tests need to predictably produce a pass/fail result and reliance on undefined behaviour compromises that predictability (e.g. results of testing changing with versions of a compiler, settings, etc) – Peter Aug 26 '20 at 08:44
  • @Soonts Even if you do that, the compiler can (and will) still replace uses of the variable with its known value. It's a bad hack if it doesn't work. – molbdnilo Aug 26 '20 at 08:55
  • 2
    @molbdnilo Disable optimizations (e.g. by switching to Debug configuration) and it will probably work fine. Tests don’t always need optimizations because slows down compilation. – Soonts Aug 26 '20 at 08:58
  • @Soonts I dont see the point in writing a test that only maybe works reliable in Debug, that is testing a situation that never occurs in production so why even write a test for it? – 463035818_is_not_an_ai Aug 26 '20 at 11:38
  • 2
    @idclev463035818 The OP has already wrote why. They want to test a situation that never occurs in production, but may occur after changing source code. – Soonts Aug 26 '20 at 12:34

4 Answers4

9

No.

Changing the value of something that is declared as const invokes undefined beahviour. For illustration, consider that this code

const int x = 4;
modify_const_somehow(x,42);   // "magically" assigns 42 to x
std::cout << x;

may print anything. You could see 4 on the console or 42, but "Hey you broke the rules, const cannot be modified" would be a valid output as well. No matter how you modfiy x, the code has undefined behavior. Compilers are not required to issue an error or warning, the code is simply invalid and compilers are not mandated to do anything meaningful to it.

The only situation where you are allowed to remove constness is when the object actually is not const. Sounds weird no? See this example:

const int x = 42;
int y = 100;

void foo(const int& a) {
    const_cast<int&>(a) = 4;
}

foo(x);  // undefined behavior !!
foo(y);  // OK !!

The solution to your problem is to write testable code. For example:

int GetValue(int value = my_const_value) {
    // In real world, lets say below line of code is a complex logic that needs to be tested by a unit test
    return (value * 5) / 25;
}

If you like to keep the original signature you can also wrap it (as suggested in a comment):

int GetValue_impl(int value) {
    return (value * 5) / 25;
}
int GetValue() {
    return GetValue_impl(my_const_value);
}

Now you can test GetValue_impl while GetValue uses the constant. However, I really wonder why you want to test a case that cannot happen.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • The call `foo(x)` in your example is not illegal in the sense that it will compile, no diagnostics required. It gives undefined behaviour. (Equating "undefined behaviour" with "illegal" is misleading, since it encourages novices to expect a diagnostic [which is not required] - or a runtime error [which is not actually guaranteed]). – Peter Aug 26 '20 at 08:49
  • @Peter I don't agree completely, but to avoid such misunderstanding i simply changed it – 463035818_is_not_an_ai Aug 26 '20 at 08:51
  • Ok. I get your point guys. I was thinking there might be a way around but it seems impossible. – TheWaterProgrammer Aug 26 '20 at 10:09
2

I know you are looking for a way how to cast the const away, but I probably would go a different way.

You say in your comment:

Well. I have given my reason. I am testing the logic in GetValue. I have declared my_const_value as a const but that can be changed from 5 to something else in future when someone changes the value in future.

If a variable is const and particiaptes in a expresion within a function without being passed to it, then those changes normally shouldn't happen at a regular basis, and should not be expected. If you consider the myheader::my_const_value a config value, and as of that might change anytime, then it should be passed to the function in which it is used in an expresion.

So from the perspective of testing I agree with what idclev 463035818 suggest in the answer, to split the function in two parts in one testable part with a prameter and one that uses a constant.

One test that tests how the code currently should behave (what constant it should have)

TEST(MyHeaderTest, Testing_Something1) {
    EXPECT_EQ(5, myheader::my_const_value)
    EXPECT_EQ(1, myheader::GetValue());
    
}

And one for the generic test:

TEST(MyHeaderTest, Testing_Something2) {
    EXPECT_EQ(1, myheader::GetValue_impl(5));
    EXPECT_EQ(5, myheader::GetValue_impl(25));
    // …
}

That way you have the generic test if the caluclation used by GetValue works. And one if for the current verion of you code the value of myheader::GetValue() is the expected one.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • 1
    This is a good suggestion even though it does not answer my question directly – TheWaterProgrammer Aug 26 '20 at 10:09
  • 3
    @DarkMatter_Programmer in general if you struggle to get something tested in unit tests, it indicates that you might have a design flaw in your application. For constants you always need to distinguish between _"truly"_ constants (something like PI, or other constants that are fixed and will never change, except if something fundamental changes), and _"mostly"_ constants, that are constant for one build/deployment of your application (something like config values, version numbers, …). These _"mostly"_ constant values should normally be passed as argument, and not directly be used. – t.niese Aug 26 '20 at 10:37
0

Hi Hope you are doing good.

Just try #define const /* myComment */ in above the stubbed function

Esi
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 07 '22 at 00:09
-1

You asked for hackish, here it is :

// define the string const to be replaced by empty string
#define const 

#include "header.hh"
#undefine const

...tests...


The only issue I see is that 1.all the const modifiers fall out, which may or not be a problem. 2. it is kind of intrusive, the compiler as others mentioned treats constants in a particular way, so your test is not quite testing the same code that will run in your real use case.

There is a similar trick that starts with #define private public then including the header, for accessing private fields of another class from a library. Nice thing is it does not even break when you link on the library.

Note that none of these things are recommended, they are hackish, but leveraging the preprocessor to bias included files is fine. It will clear all const modifiers, and transitively in other included headers so pretty intrusive.

Less hackish is to have a macro TEST and put in your header #ifdef TEST /*non const decl*/ #else /*const decl*/. Then you obviously do #define TEST before including the header which is cleaner than redefining keywords.

Yann TM
  • 1,942
  • 13
  • 22
  • The behaviour of redefining a keyword like this is undefined. You may as well attempt to change the variable by casting the const away. (If you want to access `private` members you can do so using a reasonably standard template trick - access specifiers are not checked when specialising templates.) – Bathsheba Aug 26 '20 at 08:26
  • 1
    naively one might expect this to work and the rule about not defining keywords being a unnecessary safety net, but I actually saw cases where defining a keyword lead to the most unexpected bugs. Dont do this – 463035818_is_not_an_ai Aug 26 '20 at 08:28
  • preprocessor directives are agnostic to keywords, and are run before actual compilation. I don't see why this would be undefined behavior, please provide a reference. – Yann TM Aug 26 '20 at 08:29
  • 3
    the preprocessor is agnostic but everything after that not. Consider what happens when `header.hh` includes other (library) headers – 463035818_is_not_an_ai Aug 26 '20 at 08:30
  • `It will clear all const modifiers, and transitively in other included headers so pretty intrusive.` it still can be a problem. If `header.hh` includes header of a library that is not header only, and the compiled part uses variables that have been defined const. Or if you have two compilation units, that both include the same headers, in one it is inlcuded within `header.hh` and in the other it is included somewhere else where `const` was not redefined. In the best case you get a linker error. – t.niese Aug 26 '20 at 08:44