Ver código fonte

Added unit testing framework

Marko Pintera 11 anos atrás
pai
commit
e7d926175f

+ 2 - 0
BansheeEditor/BansheeEditor.vcxproj

@@ -274,6 +274,7 @@
     <ClInclude Include="Include\BsEditorApplication.h" />
     <ClInclude Include="Include\BsEditorCommand.h" />
     <ClInclude Include="Include\BsEditorGUI.h" />
+    <ClInclude Include="Include\BsEditorTestSuite.h" />
     <ClInclude Include="Include\BsEditorWidgetLayout.h" />
     <ClInclude Include="Include\BsEditorWidgetLayoutRTTI.h" />
     <ClInclude Include="Include\BsEditorWidgetManager.h" />
@@ -327,6 +328,7 @@
     <ClCompile Include="Source\BsDockManagerLayout.cpp" />
     <ClCompile Include="Source\BsEditorCommand.cpp" />
     <ClCompile Include="Source\BsEditorGUI.cpp" />
+    <ClCompile Include="Source\BsEditorTestSuite.cpp" />
     <ClCompile Include="Source\BsEditorWidget.cpp" />
     <ClCompile Include="Source\BsEditorWidgetContainer.cpp" />
     <ClCompile Include="Source\BsEditorWidgetLayout.cpp" />

+ 6 - 0
BansheeEditor/BansheeEditor.vcxproj.filters

@@ -198,6 +198,9 @@
     <ClInclude Include="Include\BsCmdRecordSO.h">
       <Filter>Header Files\Editor\Commands</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsEditorTestSuite.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsEditorWidgetContainer.cpp">
@@ -344,5 +347,8 @@
     <ClCompile Include="Source\BsCmdRecordSO.cpp">
       <Filter>Source Files\Editor\Command</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsEditorTestSuite.cpp">
+      <Filter>Source Files\Editor</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 1 - 0
BansheeEditor/Include/BsCmdRecordSO.h

@@ -28,6 +28,7 @@ namespace BansheeEngine
 		friend class UndoRedo;
 
 		CmdRecordSO(const HSceneObject& sceneObject);
+		void recordSO(const HSceneObject& sceneObject);
 		void clear();
 		SceneObjProxy createProxy(const HSceneObject& sceneObject);
 		void restoreIds(const HSceneObject& restored);

+ 16 - 0
BansheeEditor/Include/BsEditorTestSuite.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsTestSuite.h"
+
+namespace BansheeEngine
+{
+	class BS_ED_EXPORT EditorTestSuite : public TestSuite
+	{
+	public:
+		EditorTestSuite();
+
+	private:
+		void SceneObjectRecord_UndoRedo();
+	};
+}

+ 14 - 8
BansheeEditor/Source/BsCmdRecordSO.cpp

@@ -44,14 +44,7 @@ namespace BansheeEngine
 		if (mSceneObject == nullptr || mSceneObject.isDestroyed())
 			return;
 
-		MemorySerializer serializer;
-		mSerializedObject = serializer.encode(mSceneObject.get(), mSerializedObjectSize, &bs_alloc);
-
-		HSceneObject parent = mSceneObject->getParent();
-		if (parent != nullptr)
-			mSerializedObjectParentId = parent->getInstanceId();
-
-		mSceneObjectProxy = createProxy(mSceneObject);
+		recordSO(mSceneObject);
 	}
 
 	void CmdRecordSO::revert()
@@ -72,6 +65,19 @@ namespace BansheeEngine
 		std::shared_ptr<SceneObject> restored = std::static_pointer_cast<SceneObject>(serializer.decode(mSerializedObject, mSerializedObjectSize));
 
 		restoreIds(restored->getHandle());
+		restored->setParent(parent);
+	}
+
+	void CmdRecordSO::recordSO(const HSceneObject& sceneObject)
+	{
+		MemorySerializer serializer;
+		mSerializedObject = serializer.encode(mSceneObject.get(), mSerializedObjectSize, &bs_alloc);
+
+		HSceneObject parent = mSceneObject->getParent();
+		if (parent != nullptr)
+			mSerializedObjectParentId = parent->getInstanceId();
+
+		mSceneObjectProxy = createProxy(mSceneObject);
 	}
 
 	CmdRecordSO::SceneObjProxy CmdRecordSO::createProxy(const HSceneObject& sceneObject)

+ 14 - 0
BansheeEditor/Source/BsEditorTestSuite.cpp

@@ -0,0 +1,14 @@
+#include "BsEditorTestSuite.h"
+
+namespace BansheeEngine
+{
+	EditorTestSuite::EditorTestSuite()
+	{
+		BS_ADD_TEST(EditorTestSuite::SceneObjectRecord_UndoRedo)
+	}
+
+	void EditorTestSuite::SceneObjectRecord_UndoRedo()
+	{
+		BS_TEST_ASSERT_MSG(false, "SUP SUP");
+	}
+}

+ 5 - 0
BansheeEditor/Source/BsMainEditorWindow.cpp

@@ -9,6 +9,8 @@
 #include "BsProfilingManager.h"
 #include "BsGUIArea.h"
 #include "BsGUILayout.h"
+#include "BsEditorTestSuite.h"
+#include "BsTestOutput.h"
 
 // DEBUG ONLY
 #include "BsTestTextSprite.h"
@@ -79,6 +81,9 @@ namespace BansheeEngine
 
 		mProfilerOverlay = mSceneObject->addComponent<ProfilerOverlay>(sceneCamera->getViewport());
 		mProfilerOverlay->show(ProfilerOverlayType::CPUSamples);
+
+		TestSuitePtr testSuite = TestSuite::create<EditorTestSuite>();
+		testSuite->run(ExceptionTestOutput());
 	}
 
 	MainEditorWindow::~MainEditorWindow()

+ 4 - 0
BansheeUtility/BansheeUtility.vcxproj

@@ -248,6 +248,8 @@
     <ClCompile Include="Source\BsBounds.cpp" />
     <ClCompile Include="Source\BsConvexVolume.cpp" />
     <ClCompile Include="Source\BsTaskScheduler.cpp" />
+    <ClCompile Include="Source\BsTestOutput.cpp" />
+    <ClCompile Include="Source\BsTestSuite.cpp" />
     <ClCompile Include="Source\BsThreadPool.cpp" />
     <ClCompile Include="Source\BsAABox.cpp" />
     <ClCompile Include="Source\BsAsyncOp.cpp" />
@@ -275,6 +277,8 @@
     <ClInclude Include="Include\BsEvent.h" />
     <ClInclude Include="Include\BsSpinLock.h" />
     <ClInclude Include="Include\BsTaskScheduler.h" />
+    <ClInclude Include="Include\BsTestOutput.h" />
+    <ClInclude Include="Include\BsTestSuite.h" />
     <ClInclude Include="Include\BsThreadPool.h" />
     <ClInclude Include="Include\BsAsyncOp.h" />
     <ClInclude Include="Include\BsBinarySerializer.h" />

+ 12 - 0
BansheeUtility/BansheeUtility.vcxproj.filters

@@ -246,6 +246,12 @@
     <ClInclude Include="Include\BsConvexVolume.h">
       <Filter>Header Files\Math</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsTestSuite.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsTestOutput.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsThreadPool.cpp">
@@ -389,5 +395,11 @@
     <ClCompile Include="Source\BsConvexVolume.cpp">
       <Filter>Source Files\Math</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsTestOutput.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsTestSuite.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 10 - 0
BansheeUtility/Include/BsException.h

@@ -140,6 +140,16 @@ namespace BansheeEngine
 			: Exception("RenderingAPIException", inDescription, inSource, inFile, inLine) {}
 	};
 
+	/**
+	 * @brief	Exception for signaling an error in an unit test.
+	 */
+	class BS_UTILITY_EXPORT UnitTestException : public Exception
+	{
+	public:
+		UnitTestException(const String& inDescription, const String& inSource, const char* inFile, long inLine)
+			: Exception("UnitTestException", inDescription, inSource, inFile, inLine) {}
+	};
+
 	/**
 	 * @brief Macro for throwing exceptions that will automatically fill out function name, file name and line number of the exception.
 	 */

+ 3 - 0
BansheeUtility/Include/BsFwdDeclUtil.h

@@ -52,6 +52,8 @@ namespace BansheeEngine
 	struct LocalizedStringData;
 	class Path;
 	class HThread;
+	class TestSuite;
+	class TestOutput;
 	// Reflection
 	class IReflectable;
 	class RTTITypeBase;
@@ -68,6 +70,7 @@ namespace BansheeEngine
 	typedef std::shared_ptr<DataStream> DataStreamPtr;
 	typedef std::shared_ptr<MemoryDataStream> MemoryDataStreamPtr;
 	typedef std::shared_ptr<Task> TaskPtr;
+	typedef std::shared_ptr<TestSuite> TestSuitePtr;
 
 	typedef List<DataStreamPtr> DataStreamList;
 	typedef std::shared_ptr<DataStreamList> DataStreamListPtr;

+ 52 - 0
BansheeUtility/Include/BsTestOutput.h

@@ -0,0 +1,52 @@
+#pragma once
+
+#include "BsPrerequisitesUtil.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Abstract interfaced used for outputting unit test results.
+	 */
+	class BS_UTILITY_EXPORT TestOutput
+	{
+	public:
+		virtual ~TestOutput() {}
+
+		/**
+		 * @brief	Triggered when a unit test fails.
+		 *
+		 * @param	desc		Reason why the unit test failed.
+		 * @param	function	Name of the function the test failed in.
+		 * @param	file		File the unit test failed in.
+		 * @param	line		Line of code the unit test failed on.
+		 */
+		virtual void outputFail(const String& desc, const String& function, const String& file, long line) = 0;
+
+		/**
+		 * @brief	Triggered when a unit test succeeds.
+		 *
+		 * @param	desc		Optional message to add.
+		 * @param	function	Name of the function the test failed in.
+		 * @param	file		File the unit test failed in.
+		 * @param	line		Line of code the unit test failed on.
+		 */
+		virtual void outputSuccess(const String& desc, const String& function, const String& file, long line) = 0;
+	};
+
+	/**
+	 * @brief	Outputs unit test results so that failures are reported as exceptions. Success is not reported.
+	 */
+	class BS_UTILITY_EXPORT ExceptionTestOutput : public TestOutput
+	{
+	public:
+		/**
+		 * @copydoc	TestOutput::outputFail
+		 */
+		void outputFail(const String& desc, const String& function, const String& file, long line);
+
+		/**
+		 * @copydoc	TestOutput::outputSuccess
+		 */
+		void outputSuccess(const String& desc, const String& function, const String& file, long line);
+	};
+}

+ 97 - 0
BansheeUtility/Include/BsTestSuite.h

@@ -0,0 +1,97 @@
+#pragma once
+
+#include "BsPrerequisitesUtil.h"
+
+#define BS_TEST_ASSERT(expr) assertment((expr), "", __FILE__, __LINE__); 
+#define BS_TEST_ASSERT_MSG(expr, msg) assertment((expr), msg, __FILE__, __LINE__); 
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Primary class for unit testing. Override and register unit tests
+	 *			in constructor then run the tests using the desired method of output.
+	 */
+	class BS_UTILITY_EXPORT TestSuite
+	{
+	public:
+		typedef void(TestSuite::*Func)();
+
+	private:
+
+		/**
+		 * @brief	Contains data about a single unit test.
+		 */
+		struct TestEntry
+		{
+			TestEntry(Func test, const String& name);
+
+			Func test;
+			String name;
+		};
+
+	public:
+		virtual ~TestSuite() {}
+
+		/**
+		 * @brief	Runs all the tests in the suite (and sub-suites). Tests results
+		 *			are reported to the provided output class.
+		 */
+		void run(TestOutput& output);
+
+		/**
+		 * @brief	Adds a new child suite to this suite. This method allows
+		 *			you to group suites and execute them all at once.
+		 */
+		void add(const TestSuitePtr& suite);
+
+		/**
+		 * @brief	Creates a new suite of a particular type.
+		 */
+		template <class T>
+		static TestSuitePtr create()
+		{
+			static_assert((std::is_base_of<TestSuite, T>::value), "Invalid test suite type. It needs to derive from BansheeEngine::TestSuite.");
+
+			return std::static_pointer_cast<TestSuite>(bs_shared_ptr<T>());
+		}
+
+	protected:
+		TestSuite();
+
+		/**
+		 * @brief	Called right before any tests are ran.
+		 */
+		virtual void startUp() {}
+
+		/**
+		 * @brief	Called after all tests and child suite's tests are ran.
+		 */
+		virtual void shutDown() {}
+
+		/**
+		 * @brief	Register a new unit test.
+		 *
+		 * @param	test	Function to call in order to execute the test.
+		 * @param	name	Name of the test we can use for referencing it later.
+		 */
+		void addTest(Func test, const String& name);
+
+		/**
+		 * @brief	Reports success or failure depending on the result of an expression.
+		 *
+		 * @param	success		If true success is reported, otherwise failure.
+		 * @param	file		Name of the source code file the assertment originates from.
+		 * @param	line		Line number at which the assertment was triggered at.
+		 */
+		void assertment(bool success, const String& desc, const String& file, long line);
+
+		Vector<TestEntry> mTests;
+		Vector<TestSuitePtr> mSuites;
+
+		// Transient
+		TestOutput* mOutput;
+		String mActiveTestName;
+	};
+}
+
+#define BS_ADD_TEST(func) addTest(static_cast<Func>(&func), #func);

+ 15 - 0
BansheeUtility/Source/BsTestOutput.cpp

@@ -0,0 +1,15 @@
+#include "BsTestOutput.h"
+#include "BsException.h"
+
+namespace BansheeEngine
+{
+	void ExceptionTestOutput::outputFail(const String& desc, const String& function, const String& file, long line)
+	{
+		throw UnitTestException(desc, function, file.c_str(), line);
+	}
+
+	void ExceptionTestOutput::outputSuccess(const String& desc, const String& function, const String& file, long line)
+	{
+		// Do nothing
+	}
+}

+ 52 - 0
BansheeUtility/Source/BsTestSuite.cpp

@@ -0,0 +1,52 @@
+#include "BsTestSuite.h"
+#include "BsTestOutput.h"
+
+namespace BansheeEngine
+{
+	TestSuite::TestEntry::TestEntry(Func test, const String& name)
+		:test(test), name(name)
+	{ }
+
+	TestSuite::TestSuite()
+		: mOutput(nullptr)
+	{ }
+
+	void TestSuite::run(TestOutput& output)
+	{
+		mOutput = &output;
+
+		startUp();
+
+		for (auto& testEntry : mTests)
+		{
+			mActiveTestName = testEntry.name;
+			
+			(this->*(testEntry.test))();
+		}
+
+		for (auto& suite : mSuites)
+		{
+			suite->run(output);
+		}
+
+		shutDown();
+	}
+
+	void TestSuite::add(const TestSuitePtr& suite)
+	{
+		mSuites.push_back(suite);
+	}
+
+	void TestSuite::addTest(Func test, const String& name)
+	{
+		mTests.push_back(TestEntry(test, name));
+	}
+
+	void TestSuite::assertment(bool success, const String& desc, const String& file, long line)
+	{
+		if (success)
+			mOutput->outputSuccess(desc, mActiveTestName, file, line);
+		else
+			mOutput->outputFail(desc, mActiveTestName, file, line);
+	}
+}