В NUnit, TestCase - это атрибут, однако в Gallio это не так. Т.о. в каждой из обозначенных платформ своя реализация поведения обозначенного элемента. В этой заметке показан пример исходного кода, который успешно компилируется и работает как в случае использования Gallio, так и в случае использования NUnit.
Хорошую книгу о том, как грамотно создавать автономные и интеграционные тесты, в т.ч. и под такие закрытые системы как AutoCAD, я указывал здесь, в п.21. Кроме того, Gallio имеет неплохую offline документацию, доступную в меню Пуск -> Все программы -> Gallio -> Offline Documentation, а документация по NUnit присутствует online.
Gallio может выдавать отчёты о результатах теста как в формате XML, так и в формате HTML. В то же время NUnit может генерировать только XML. Для получения HTML на основе XML отчётов NUnit я рекомендую пользоваться утилитой NUnitOrange.
Полагаю, что лучше всего различие продемонстрирует исходный код:
/* © Andrey Bushman, 2015 * Tests.cs * Recommended format of naming of tests: * UnitOfWorkName_Scenario_ExpectedBehavior */ #if !ENTRY_POINT using System; using System.Collections.Generic; 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 Gallio.Model; 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.ExtensionSample.UnitTests; namespace Bushman.CAD.Extensions.ExtensionSample.UnitTests { [Fw.TestFixture, #if NUNIT Fw.Apartment(ApartmentState.STA) #endif ] public class Tests { const String blockName = "TEMP_BLOCK"; // *********************************************************************** [Fw.Ignore("Sample of ignored test.")] [Fw.Test] [Fw.Category("Autodesk API")] [Fw.Description("Some description")] 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.Test] [Fw.Category("Autodesk API")] [Fw.Description("This test shows AutoCAD .NET API bug. It exists in " + "AutoCAD 2009-2016.")] 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")] [Fw.Description("I am some description 1 :)")] 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")] [Fw.Description("I am some description 2 :)")] 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; } // ********************************************************************** // This test template reads the Database from the DWG file for own work. [Fw.Test] [Fw.Category("TestCase using samples")] public void CircleIsExists() { const String dwgFileName = @"..\NUnit\data-for-testing\data_01.dwg"; Boolean result = false; // Read Database from the DWG file using (Db.Database db = new Db.Database(true, true)) { db.ReadDwgFile(dwgFileName, Db.FileOpenMode.OpenForReadAndWriteNoShare, false, ""); using (new WorkingDatabaseSwitcher(db)) { using (Db.Transaction tr = db.TransactionManager.StartTransaction()) { Db.ObjectId msId = Us.GetBlockModelSpaceId(db); Db.BlockTableRecord ms = tr.GetObject(msId, Db.OpenMode.ForRead) as Db.BlockTableRecord; Rt.RXClass rxc = Rt.RXClass.GetClass(typeof (Db.Circle)); Db.ObjectId id = ms.Cast<Db.ObjectId>().FirstOrDefault(); result = Db.ObjectId.Null != id; tr.Commit(); } Fw.Assert.IsTrue(result); } } // close and discard changes } // ********************************************************************** #if GALLIO [Fw.StaticTestFactory] public static IEnumerable<Fw.Test> TestSuite_RenameMe() { yield return new Fw.TestSuite("Gallio tests some suite") { Description = "An example test suite.", Metadata = { { MetadataKeys.Category, "TestCase using samples" }, { MetadataKeys.Description, "I am some description 3 :)" } }, Timeout = TimeSpan.FromMinutes(2), Children = { new Fw.TestCase("CircleMustToBeExisting", () => { __CheckingOfCircleExisting(@"..\NUnit\data-for-testing\data_01.dwg", true); }), new Fw.TestCase("CircleMustNotToBeExisting", () => { __CheckingOfCircleExisting(@"..\NUnit\data-for-testing\data_02.dwg", false); }) } }; } #elif NUNIT [Fw.Test] [Fw.Category("TestCase using samples")] [Fw.TestCase(@"..\NUnit\data-for-testing\data_01.dwg", true)] [Fw.TestCase(@"..\NUnit\data-for-testing\data_02.dwg", false)] #endif public void CheckingOfCircleExisting(String dwgFileName, Boolean expectedResult) { __CheckingOfCircleExisting(dwgFileName, expectedResult); } static void __CheckingOfCircleExisting(String dwgFileName, Boolean expectedResult) { // Read Database from the DWG file using (Db.Database db = new Db.Database(true, true)) { db.ReadDwgFile(dwgFileName, Db.FileOpenMode.OpenForReadAndWriteNoShare, false, ""); Boolean result = false; using (new WorkingDatabaseSwitcher(db)) { using (Db.Transaction tr = db.TransactionManager.StartTransaction()) { Db.ObjectId msId = Us.GetBlockModelSpaceId(db); Db.BlockTableRecord ms = tr.GetObject(msId, Db.OpenMode.ForRead) as Db.BlockTableRecord; Rt.RXClass rxc = Rt.RXClass.GetClass(typeof(Db.Circle)); result = ms.Cast<Db.ObjectId>().Any(n=>n.ObjectClass.IsDerivedFrom(rxc)); tr.Commit(); } Fw.Assert.AreEqual(expectedResult, result); } } // close and discard changes } // ********************************************************************** } } #endif
Результаты тестов, в пакетном режиме автоматически произведённых в AutoCAD 2009-2015 и представленных в формате HTML можно скачать и посмотреть отсюда. Пакетное тестирование происходило с использованием acad.exe для AutoCAD 2009-2012 и accoreconsole.exe для AutoCAD 2013-2015. Тесты для AutoCAD 2009 и 2010 скомпилированы с использованием платформы Gallio. Тесты для AutoCAD 2011-2015 собраны с использованием NUnit. Внешнее представление отчётов в HTML-формате у этих платформ несколько отличается, но оба варианта достаточно удобны для использования.
В коде тестов я активно использую один из своих вспомогательных классов: WorkingDatabaseSwitcher. Вот его исходный код:
/* © Andrey Bushman, 2015 * WorkingDatabaseSwitcher.cs */ #if !ENTRY_POINT using System; using System.Collections.Generic; 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.ExtensionSample.UnitTests; namespace Bushman.CAD.Extensions.ExtensionSample.UnitTests { /// <summary> /// This class switches the WorkingDatabase. It was created for using in the /// tests. /// </summary> internal sealed class WorkingDatabaseSwitcher : IDisposable { Db.Database oldDb = null; /// <summary> /// Constructor. /// </summary> /// <param name="db">Target database.</param> public WorkingDatabaseSwitcher(Db.Database db) { oldDb = Hs.WorkingDatabase; Hs.WorkingDatabase = db; } public void Dispose() { Hs.WorkingDatabase = oldDb; } } } #endif
В обозначенных выше тестах я тестирую в том числе и некоторый метод HasAttDefs(), вот его исходный код:
/* © Andrey Bushman, 2015 * ExtensionMethods.cs */ #if !ENTRY_POINT using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Text; using System.Windows.Controls; using System.Windows.Converters; using System.Windows.Forms; using System.Windows; #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_2009 using In = Autodesk.AutoCAD.Internal; #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.ExtensionSample; namespace Bushman.CAD.Extensions.ExtensionSample { public static class ExtensionMethods { /// <summary> /// This method is a replace for the /// <c>BlockTableRecord.HasAttributeDefinitions</c> method. Implementation /// by Autodesk works wrong: it returns <c>True</c> after the /// <c>AttributeDefinition</c> instance was deleted from the /// <c>BlockTableRecord</c>. Info source: /// http://bushman-andrey.blogspot.ru/2014/03/blocktablerecordhasattributedefinitions.html /// </summary> /// <param name="btr">Target instance of the <c>BlockTableRecord</c> class. /// </param> /// <returns>returns true or false.</returns> public static Boolean HasAttDefs(this Db.BlockTableRecord btr) { String name = Rt.RXClass.GetClass(typeof(Db.AttributeDefinition)).Name; return btr.Cast<Db.ObjectId>().Any(n => !n.IsNull && n.IsValid && !n.IsErased && !n.IsEffectivelyErased && String.Equals( n.ObjectClass.Name, name, StringComparison.InvariantCulture)); } } } #endif
Комментариев нет:
Отправить комментарий