Revit Test Framework: 为Dynamo量身定做的测试系统
RevitTestFramework
RevitTestFramework是 Autodesk 提供的针对 Dynamo For Revit 的开源测试框架:https://github.com/DynamoDS/RevitTestFramework
这个框架除了蕴含了D4R测试机制以外,还包含了 Revit 二次开发测试难得解决方案,很有借鉴意义,但需要专业开发人员自己阅读代码才能找到它的核心所在。
运行方式
它有两种运行方式,命令行和GUI。
命令行
如下面这个例子所示:
RevitTestFrameworkConsole.exe --dir C:\MyTestDir -a MyTest.dll -r MyTestResults.xml -revit:"C:\Program Files\Autodesk\Revit 2019\Revit.exe" --continuous
执行 C:MyTestDir
目录下 Mytest.dll
中所有的测试,并且把所有的测试结果放到同目录夹下的 MyTestResults.xml
文件中。
测试代码里面所有使用了的地方 Console.WriteLine
和 Console.Error.WriteLine
的地方都会显示在命令行里面,同时记录到输出的 XML 文件中,如下面这个例子:
Reading assembly: D:\MyTestDir\MyTest.dll
Loaded test: Name: UnitTest1, Model Path: D:\MyTestDir\MyModel1.rvt (D:\MyTestDir\MyModel1.rvt)
Loaded test: Name: UnitTest2, Model Path: D:\MyTestDir\MyModel2.rvt (D:\MyTestDir\MyModel2.rvt)
Running D:\Projects\UpCodes\upcodes_revit\Tests\2019\bin\Debug\RTF_Batch_Test.txt
Running UnitTest1 in BasicTests
Success: UnitTest1 in BasicTests
Running UnitTest2 in BasicTests
Success: UnitTest2 in BasicTests
Waiting for Revit to terminate
WARNING: One or more journal files could not be deleted.
一个运行单个DynamoRevit自带的测试的例子(需要自己替换掉[]中的内容):
RevitTestFrameworkConsole.exe --dir [DynamoRevit]\test\System -a [DynamoRevit]\bin\AnyCPU\Debug\Revit\RevitSystemTests.dll -r [DynamoRevit]\bin\AnyCPU\Debug\Revit\MyTestResults.xml -revit:"[Revit Install]\Revit.exe" --testName=[Test Name]
GUI
RevitTestFrameworkGUI.exe
UI界面和命令行本质上是一样的,可以互相对照。
原理和逻辑
在跑测试的时候,你会发现,Revit启动了,而且自动在运行。这是怎么发生的呢?
首先,你要了解 Revit 的journal。
然后,你要了解插件的journal 是如何产生的。
最后,你还必须知道如果用C#来启动Revit来运行这个journal。
下面的内容就比较偏向于软件了,得益于这是一个开源的框架,实际上用户可以自行调试来发现其中的逻辑。
准备工作
Revit 从2020 开始,都只确保一个对应版本的Dynamo For Revit,虽然降低了灵活性,但是也简化了开发过程。各个版本之间理论上,可以想办法来进行兼容性处理,但是对于普通开发者过于困难。
在这里,我用的Revit 2020,打开我的Dynamo For Revit,查看版本信息。
Dynamo Core 2.1.0.7500
Dynamo Revit 2.1.0.7733
作为开发者,你需要去 Github
上找到对应的分支:
Dynamo Core 2.1.0.7500 -> https://github.com/DynamoDS/Dynamo/tree/RC2.1.0_master
Dynamo Revit 2.1.0.7733 -> https://github.com/DynamoDS/DynamoRevit/tree/RC2.1.0_Revit2020
只有把对应的分支克隆下来,才能保证你可以正常的进行开发。否则,你需要自己去保证兼容性。在保证你本地有了Dynamo相关的代码,并且顺利编译成功之后,你才能开始进行程序开发和测试的编写。
测试的Journal是如何生成的
选择单独跑一个测试,CurveByPoints
,去掉 Continuous 和 Group by model 选项。设置好断点,得到如下callstack:
这个journal的内容:
'Dim Jrn
Set Jrn = CrsJournalScript
Jrn.Command "StartupPage" , "Open this project , ID_FILE_MRU_FIRST"
Jrn.Data "MRUFileName" , "E:\GitHub\DynamoRevit\test\System\empty.rfa"
Jrn.RibbonEvent "Execute external command:487f9ff0-5b34-4e7e-97bf-70fbff69194f:RTF.Applications.RevitTestFramework"
Jrn.Data "APIStringStringMapJournalData", 6, "testName", "CurveByPoints", "fixtureName", "CurveTests", "testAssembly", "E:\GitHub\DynamoRevit\bin\AnyCPU\Debug\Revit\RevitSystemTests.dll", "resultsPath", "E:\GitHub\DynamoRevit\bin\AnyCPU\Debug\Revit\results.xml", "debug","False","workingDirectory","E:\GitHub\DynamoRevit\test\System"
Jrn.Command "Internal" , "Flush undo and redo stacks , ID_FLUSH_UNDO"
Jrn.Command "SystemMenu" , "Quit the application; prompts to save projects , ID_APP_EXIT"
实际上测试界面也会有显示,只是通常跑完之后会删除掉,如下:
17:05:56 Reading assembly: E:\GitHub\DynamoRevit\bin\AnyCPU\Debug\Revit\RevitSystemTests.dll
17:06:12 Running E:\GitHub\DynamoRevit\test\System\CurveByPoints.txt
17:07:06 ......................................................
17:07:06 Test run completed successfully: 1 passed, 0 skipped, 0 failed
Journal 是如何跑起来的
如下所示,自己起一个process,设置好相应的参数即可:
Journal里面到底做了什么?
下面这个才是 RevitTestFramework 的核心,通过它,可以从journal 里面读取参数,然后让一个测试正常地运行起来。
RevitTestFramework\src\Applications\RTFRevit\RevitTestFramework.cs
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
[Journaling(JournalingMode.UsingCommandData)]
public class RevitTestFramework : IExternalCommand, IConsoleTraceListener
{
public Result Execute(ExternalCommandData cmdData, ref string message, ElementSet elements)
{
using (var consoleInterceptor = new ConsoleOutInterceptor(this))
{
Setup(cmdData);
var exe = new RevitTestExecutive();
return exe.Execute(cmdData, ref message, elements);
}
}
private void Setup(ExternalCommandData cmdData)
{
IDictionary<string, string> dataMap = cmdData.JournalData;
if (dataMap.ContainsKey("debug") && dataMap["debug"].ToLower() == "true")
{
Debugger.Launch();
}
NUnitFrameworkResolver.Setup();
}
public void OnConsoleOutLine(string text)
{
RTFClientStartCmd.SendConsoleMessage(text);
}
public void OnErrorOutLine(string text)
{
RTFClientStartCmd.SendConsoleMessage(text, Framework.ConsoleMessageType.ErrorOut);
}
}