Ранее я уже приводил пример создания общего шаблона для .NET плагина под любую версию AutoCAD не старше чем 2009-я. Аналогичный шаблон можно создать и для модульных тестов под эти плагины.
В качестве платформы тестирования для управляемых расширений [плагинов] AutoCAD можно использовать Gallio или NUnit.
Gallio благополучно работает с любой версией AutoCAD новее чем 2008 (я не проверял для версий старее чем AutoCAD 2009-й). Однако разработка Gallio на сегодняшний день приостановлена. Тем не менее его можно успешно продолжать использовать. Исходники Gallio опубликованы на GitHub и доступны для изучения\изменения. Однако Gallio работает только с acad.exe - использовать accoreconsole.exe не удастся.
NUnit успешно работает начиная с AutoCAD 2011 и во всех более новых версиях. Версии AutoCAD 2011 - 2014 требуют предварительной установки переменной NEXTFIBERWORLD в значение 0 с последующим перезапуском AutoCAD. По завершению работы тестов, не забудьте переменной NEXTFIBERWORLD снова назначить в качестве значения 1.
Начиная с AutoCAD 2015 компания Autodesk уходит от использования фиберов, поэтому версии AutoCAD, новее чем 2014-я не требуют предварительного изменения обозначенной переменной для успешной работы тестов. Поскольку версии AutoCAD 2010 и все более старые не имеют переменной NEXTFIBERWORLD, то использовать в них тесты NUnit не представляется возможным.
Начиная с AutoCAD 2015 компания Autodesk уходит от использования фиберов, поэтому версии AutoCAD, новее чем 2014-я не требуют предварительного изменения обозначенной переменной для успешной работы тестов. Поскольку версии AutoCAD 2010 и все более старые не имеют переменной NEXTFIBERWORLD, то использовать в них тесты NUnit не представляется возможным.
В отличие от Gallio, NUnit может работать как с acad.exe, так и с accoreconsole.exe.
Т.о. в AutoCAD 2009 и 2010 следует использовать тестовую платформу Gallio, в то время как начиная с AutoCAD 2011 можно использовать либо Gallio, либо NUnit по вашему желанию (я предпочитаю NUnit).
Один и тот же исходный код модульных тестов может компилироваться под разные платформы тестирования. По аналогии с шаблоном плагинов AutoCAD, который я демонстрировал в видео ранее, можно создать шаблон для модульных тестов управляемых расширений AutoCAD. Такой шаблон позволяет пакетно компилировать один и тот же исходный код модульных тестов под разные версии AutoCAD с использованием как платформы Gallio, так и платформы NUnit. В приведённом ниже примере для AutoCAD 2009 и 2010 я генерирую тесты с использованием Gallio, а для AutoCAD 2011-2015 - с использованием NUnit. Оба проекта построены на основе соответствующих шаблонов: первый - на основе шаблона для .NET расширений AutoCAD, а второй - на основе шаблона для создания модульных тестов для .NET расширений AutoCAD.
Дополнительно генерируется набор BAT-файлов, каждый из которых предназначен для запуска тестов в конкретной версии AutoCAD. Результаты тестирования оформляются в виде отчёта в формате HTML. На мой взгляд всё получается достаточно удобно.
Когда-то я писал о баге, присутствующем в AutoCAD .NET API и давал свой вариант обходного решения. Обозначенное ниже видео построено на основе этого кода: тесты выявляют баг в API от Autodesk, а так же проверяют работоспособность моего варианта решения.
UPD:
Добавил генерацию BAT-файла, который последовательно выполняет тесты во всех нужных версиях AutoCAD (в данном случае AutoCAD 2009-2015). Вот видео на эту тему:
Далее привожу код тестов данного видео:
Добавил генерацию BAT-файла, который последовательно выполняет тесты во всех нужных версиях AutoCAD (в данном случае AutoCAD 2009-2015). Вот видео на эту тему:
Далее привожу код тестов данного видео:
/* © Andrey Bushman, 2015
* Tests.cs
*/
#if !ENTRY_POINT
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Text;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Converters;
using System.Windows.Forms;
using System.Windows;
#if NUNIT
using Fw = NUnit.Framework;
#elif GALLIO
using Gallio.Framework;
using Fw = MbUnit.Framework;
#endif
#if TEIGHA_CLASSIC
using Db = Teigha.DatabaseServices;
using Rt = Teigha.Runtime;
using Gm = Teigha.Geometry;
#endif
#if NANOCAD
using cad = HostMgd.ApplicationServices.Application;
using Ap = HostMgd.ApplicationServices;
using Ed = HostMgd.EditorInput;
#elif BRICSCAD
using cad = Bricscad.ApplicationServices.Application;
using Ap = Bricscad.ApplicationServices;
using Ed = Bricscad.EditorInput;
#elif AUTOCAD
using cad = Autodesk.AutoCAD.ApplicationServices.Application;
using Ap = Autodesk.AutoCAD.ApplicationServices;
using Db = Autodesk.AutoCAD.DatabaseServices;
using Ed = Autodesk.AutoCAD.EditorInput;
using Rt = Autodesk.AutoCAD.Runtime;
using Gm = Autodesk.AutoCAD.Geometry;
using Br = Autodesk.AutoCAD.BoundaryRepresentation;
using Hs = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices;
using Us = Autodesk.AutoCAD.DatabaseServices.SymbolUtilityServices;
#endif
#if AUTOCAD_NEWER_THAN_2012
using corecad = Autodesk.AutoCAD.ApplicationServices.Core.Application;
#endif
#if AUTOCAD && (PLATFORM_x64 || PLATFORM_x86)
using In = Autodesk.AutoCAD.Interop;
using Ic = Autodesk.AutoCAD.Interop.Common;
#endif
using Ex = Bushman.CAD.Extensions.CAD.ExtensionMethods.UnitTests;
namespace Bushman.CAD.Extensions.CAD.ExtensionMethods.UnitTests {
[Fw.TestFixture,
#if NUNIT
Fw.Apartment(ApartmentState.STA)
#endif
]
public class Tests {
const String blockName = "TEMP_BLOCK";
// ***********************************************************************
[Fw.Test]
[Fw.Category("Autodesk API")]
public void HasAttributeDefinitions_WhenAttribsExist_IsTrue() {
// Create new temp database
using (Db.Database db = new Db.Database(true, true)) {
using (new WorkingDatabaseSwitcher(db)) {
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
Db.ObjectId id = CreateBlockDefinition(db);
Db.BlockTableRecord btr = tr.GetObject(id, Db.OpenMode.ForRead) as
Db.BlockTableRecord;
Fw.Assert.IsTrue(btr.HasAttributeDefinitions);
tr.Commit();
}
}
} // close and discard changes
}
// [Fw.Ignore("We can't fix this bug, because it is by Autodesk.")]
[Fw.Test]
[Fw.Category("Autodesk API")]
public void HasAttributeDefinitions_WhenAttribsInNotExist_IsFalse() {
Db.ObjectId id = Db.ObjectId.Null;
// Create new temp database
using (Db.Database db = new Db.Database(true, true)) {
using (new WorkingDatabaseSwitcher(db)) {
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
// create new block definition with an attribute definition
id = CreateBlockDefinition(db);
Db.BlockTableRecord btr = tr.GetObject(id, Db.OpenMode.ForWrite) as
Db.BlockTableRecord;
// remove all attribute definitions
String name = Rt.RXClass.GetClass(typeof(Db.AttributeDefinition))
.Name;
foreach (Db.ObjectId itemId in btr) {
if (itemId.ObjectClass.Name == name) {
Db.DBObject obj = tr.GetObject(itemId, Db.OpenMode.ForWrite);
obj.Erase(true);
}
}
tr.Commit();
}
}
// Check attribute definition count
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
Db.BlockTableRecord btr = tr.GetObject(id, Db.OpenMode.ForWrite) as
Db.BlockTableRecord;
Fw.Assert.IsFalse(btr.HasAttributeDefinitions);
tr.Commit();
}
} // close and discard changes
}
// ***********************************************************************
[Fw.Test]
[Fw.Category("Bushman API")]
public void HasAttDefs_WhenAttribsExist_IsTrue() {
// Create new temp database
using (Db.Database db = new Db.Database(true, true)) {
using (new WorkingDatabaseSwitcher(db)) {
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
Db.ObjectId id = CreateBlockDefinition(db);
Db.BlockTableRecord btr = tr.GetObject(id, Db.OpenMode.ForRead) as
Db.BlockTableRecord;
Fw.Assert.IsTrue(btr.HasAttDefs());
tr.Commit();
}
}
} // close and discard changes
}
[Fw.Test]
[Fw.Category("Bushman API")]
public void HasAttDefs_WhenAttribsIsNotExist_IsFalse() {
Db.ObjectId id = Db.ObjectId.Null;
// Create new temp database
using (Db.Database db = new Db.Database(true, true)) {
using (new WorkingDatabaseSwitcher(db)) {
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
id = CreateBlockDefinition(db);
Db.BlockTableRecord btr = tr.GetObject(id, Db.OpenMode.ForWrite) as
Db.BlockTableRecord;
// remove all attribute definitions
String name = Rt.RXClass.GetClass(typeof(Db.AttributeDefinition))
.Name;
foreach (Db.ObjectId itemId in btr) {
if (itemId.ObjectClass.Name == name) {
Db.DBObject obj = tr.GetObject(itemId, Db.OpenMode.ForWrite);
obj.Erase(true);
}
}
tr.Commit();
}
// Check attribute definition count
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
Db.BlockTableRecord btr = tr.GetObject(id, Db.OpenMode.ForWrite) as
Db.BlockTableRecord;
Fw.Assert.IsFalse(btr.HasAttDefs());
tr.Commit();
}
}
} // close and discard changes
}
// **********************************************************************
// Creating of the temp block with an instance of AttributeDefinition
internal static Db.ObjectId CreateBlockDefinition(Db.Database db) {
if (null == db || db.IsDisposed) {
throw new ArgumentException("null == db || db.IsDisposed");
}
Db.ObjectId id = Db.ObjectId.Null;
// Create a temp block definition with an AttributeDefinition instance
using (Db.Transaction tr = db.TransactionManager.StartTransaction()) {
Db.BlockTable bt = tr.GetObject(db.BlockTableId, Db.OpenMode.ForWrite
) as Db.BlockTable;
Db.BlockTableRecord btr = new Db.BlockTableRecord();
btr.Name = blockName;
// its content: the circle and attribite definition
Db.Circle circle = new Db.Circle();
circle.SetDatabaseDefaults();
circle.Radius = 20.0;
circle.Center = Gm.Point3d.Origin;
circle.ColorIndex = 50;
btr.AppendEntity(circle);
Db.AttributeDefinition atDef = new Db.AttributeDefinition(
circle.Center, "Hello!", "ATTRIB", "New value",
Us.GetTextStyleStandardId(db));
atDef.SetDatabaseDefaults();
btr.AppendEntity(atDef);
id = bt.Add(btr);
tr.AddNewlyCreatedDBObject(btr, true);
tr.Commit();
}
return id;
}
}
}
#endif