Unreal Engine has a pretty extensible suit of automation features. For instance, we run unit tests in our continuous integration system upon merge of new code.
For these unit tests we utilize UE's IMPLEMENT_SIMPLE_AUTOMATION_TEST
or potentially IMPLEMENT_COMPLEX_AUTOMATION_TEST
. Here is a minimal test from Unreal's documentation:
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FPlaceholderTest, "TestGroup.TestSubgroup.Placeholder Test", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FPlaceholderTest::RunTest(const FString& Parameters)
{
// Make the test pass by returning true, or fail by returning false.
return true;
}
This is all well and good but pretty soon after implementing your first test you realize that you will want to reach private
functionality in the class that you are testing.
After pondering different more or less ugly solutions I talked to my colleague Tomas Hübner and he showed me this old trick that does not make this pretty but at least not terrible. And after pre-compiler is done the class we are testing is actually unaffected.
We do not want to make this private functionality public just for testing and we do not want to pollute the tested class with for instance a friend class that the original class should know nothing about. So, here is the trick:
We add an empty macro UNIT_TEST_FRIEND
to the class we want to test:
#pragma once
#include "CoreMinimal.h"
#ifndef UNIT_TEST_FRIEND
#define UNIT_TEST_FRIEND
#endif
class FMyTestedClass : public FCoolUnrealClass
{
public:
FMyTestedClass();
private:
float PrivateFloat;
void PrivateMethod();
UNIT_TEST_FRIEND;
};
And then in the test class, we make sure to define the UNIT_TEST_FRIEND
macro. The test class from above then becomes:
#include "Misc/AutomationTest.h"
#if WITH_AUTOMATION_TESTS
#ifdef UNIT_TEST_FRIEND
#undef UNIT_TEST_FRIEND
#endif
#define UNIT_TEST_FRIEND friend class FPlaceholderTest
#include "MyTestedClass.h"
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FPlaceholderTest, "TestGroup.TestSubgroup.Placeholder Test", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FPlaceholderTest::RunTest(const FString& Parameters)
{
FMyTestedClass TestedClass;
// Using private member here
float MyValue = TestedClass.PrivateFloat;
// Calling private function here
TestedClass.PrivateMethod();
return true;
}
Note how we include MyTestedClass.h
after defining the macro! Otherwise the UNIT_TEST_FRIEND
will be empty when we parse the MyTestedClass.h
header.
Tada!
Top comments (1)
It is quite common that each test file contains multiple test cases, and with the UHT macros that would result in several classes needing to be friends with the test subject.
While it would be totally possible to put several class friends in the macro, you could instead introduce a new wrapper or decorator class as the friend class that all the generated test classes can use. That would also allow for the ickyness of the macro-business to be put in a separate file, out of sight of those more faint of heart.