Подтвердить что ты не робот

NUnit DeploymentItem

В MsTest, если мне нужен какой-то файл из другого проекта для моего теста, я могу указать атрибут DeploymentItem. Есть ли что-то подобное в NUnit?

4b9b3361

Ответ 1

Вы должны проверить другой поток, который сравнивает возможности NUnit и MSTest.

Принятый ответ здесь вводит в заблуждение. NUnit не предлагает атрибут [DeploymentItem ("")] во всем, что для @Idsa требуется эквивалентное решение для NUnit.

Я предполагаю, что этот тип атрибута будет нарушать область NUnit как "единичную" структуру тестирования, так как требуется, чтобы элемент, который был скопирован на выход, перед запуском теста подразумевает, что он имеет зависимость от этого ресурса, доступного.

Я использую настраиваемый атрибут для копирования через экземпляр localdb для запуска "единичных" тестов с некоторыми важными тестовыми данными, которые я бы не генерировал с кодом каждый раз.

Теперь, используя атрибут [DeploymentItem ( "some/project/file" )], он будет копировать этот ресурс из файловой системы в корзину снова, эффективно обновляя мои исходные данные для каждого тестового метода:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, 
    AllowMultiple = false, 
    Inherited = false)]
public class DeploymentItem : System.Attribute {
    private readonly string _itemPath;
    private readonly string _filePath;
    private readonly string _binFolderPath;
    private readonly string _itemPathInBin;
    private readonly DirectoryInfo _environmentDir;
    private readonly Uri _itemPathUri;
    private readonly Uri _itemPathInBinUri;

    public DeploymentItem(string fileProjectRelativePath) {
        _filePath = fileProjectRelativePath.Replace("/", @"\");

        _environmentDir = new DirectoryInfo(Environment.CurrentDirectory);
        _itemPathUri = new Uri(Path.Combine(_environmentDir.Parent.Parent.FullName
            , _filePath));

        _itemPath = _itemPathUri.LocalPath;
        _binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

        _itemPathInBinUri = new Uri(Path.Combine(_binFolderPath, _filePath));
        _itemPathInBin = _itemPathInBinUri.LocalPath;

        if (File.Exists(_itemPathInBin)) {
            File.Delete(_itemPathInBin);
        }

        if (File.Exists(_itemPath)) {
            File.Copy(_itemPath, _itemPathInBin);
        }
    }
}

Тогда мы можем использовать так:

[Test]
[DeploymentItem("Data/localdb.mdf")]
public void Test_ReturnsTrue() 
{
    Assert.IsTrue(true);
}

Ответ 2

Я сделал несколько улучшений для решения Alexander Pasha: я дал атрибуту ту же подпись, что и MSTest, так что первый параметр - это абсолютный или относительный файл или папка для развертывания, а необязательный второй параметр является абсолютным или относительным путем, к которому он должен быть развернут. В обоих случаях "относительный" означает исполняемую программу. Кроме того, я удалил любой атрибут только для чтения из развернутого файла. Это важно - если ранее нераспределенный файл не может быть перезаписан, атрибут будет выдавать. Также стоит отметить, что MSTest и NUnit имеют очень разные стратегии, когда дело доходит до развертывания файлов, которые будут использоваться во время тестирования. MSTest может или не может копировать файлы в папку развертывания - см. здесь. NUnit использует свойство ShadowCopyFiles для AppDomain, которое развертывается в очень неясном месте в временной папке пользователя. Хотя теневое копирование может быть включено и выключено в самом NUnit, я не знаю, как манипулировать им при использовании Test Explorer в Visual Studio. В связи с этим важно отметить, что тестовый адаптер Visual Studio NUnit до версии 2 имеет теневое копирование, но в версии 2 он выключен. Это может оказать серьезное влияние на тесты, в которых используются элементы развертывания, и о них стоит знать. Вот моя версия DeploymentItemAttribute: -

namespace NUnitDeploymentItem
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]
    public class DeploymentItemAttribute : Attribute
    {
        /// <summary>
        /// NUnit replacement for Microsoft.VisualStudio.TestTools.UnitTesting.DeploymentItemAttribute
        /// Marks an item to be relevant for a unit-test and copies it to deployment-directory for this unit-test.
        /// </summary>
        /// <param name="path">The relative or absolute path to the file or directory to deploy. The path is relative to the build output directory.</param>
        /// <param name="outputDirectory">The path of the directory to which the items are to be copied. It can be either absolute or relative to the deployment directory.</param>
        public DeploymentItemAttribute(string path, string outputDirectory = null)
        {
            // Escape input-path to correct back-slashes for Windows
            string filePath = path.Replace("/", "\\");

            // Look up where we are right now
            DirectoryInfo environmentDir = new DirectoryInfo(Environment.CurrentDirectory);

            // Get the full path and name of the deployment item
            string itemPath = new Uri(Path.Combine(environmentDir.FullName, filePath)).LocalPath;
            string itemName = Path.GetFileName(itemPath);

            // Get the target-path where to copy the deployment item to
            string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            // NUnit uses an obscure ShadowCopyCache directory which can be hard to find, so let output it so the poor developer can get at it more easily
            Debug.WriteLine("DeploymentItem: Copying " + itemPath + " to " + binFolderPath);

            // Assemble the target path
            string itemPathInBin;
            if (string.IsNullOrEmpty(outputDirectory))
            {
                itemPathInBin = new Uri(Path.Combine(binFolderPath, itemName)).LocalPath;
            }
            else if (!string.IsNullOrEmpty(Path.GetPathRoot(outputDirectory)))
            {
                itemPathInBin = new Uri(Path.Combine(outputDirectory, itemName)).LocalPath;
            }
            else
            {
                itemPathInBin = new Uri(Path.Combine(binFolderPath, outputDirectory, itemName)).LocalPath;
            }

            // Decide whether it a file or a folder
            if (File.Exists(itemPath)) // It a file
            {
                // Assemble the parent folder path (because the item might be in multiple sub-folders.
                string parentFolderPathInBin = new DirectoryInfo(itemPathInBin).Parent.FullName;

                // If the target directory does not exist, create it
                if (!Directory.Exists(parentFolderPathInBin))
                {
                    Directory.CreateDirectory(parentFolderPathInBin);
                }

                // copy source-file to the destination
                File.Copy(itemPath, itemPathInBin, true);

                // We must allow the destination file to be deletable
                FileAttributes fileAttributes = File.GetAttributes(itemPathInBin);
                if ((fileAttributes & FileAttributes.ReadOnly) != 0)
                {
                    File.SetAttributes(itemPathInBin, fileAttributes & ~FileAttributes.ReadOnly);
                }
            }
            else if (Directory.Exists(itemPath)) // It a folder
            {
                // If it already exists, remove it
                if (Directory.Exists(itemPathInBin))
                {
                    Directory.Delete(itemPathInBin, true);
                }

                // Create target directory
                Directory.CreateDirectory(itemPathInBin);

                // Now Create all of the sub-directories
                foreach (string dirPath in Directory.GetDirectories(itemPath, "*", SearchOption.AllDirectories))
                {
                    Directory.CreateDirectory(dirPath.Replace(itemPath, itemPathInBin));
                }

                //Copy all the files & Replace any files with the same name
                foreach (string sourcePath in Directory.GetFiles(itemPath, "*.*", SearchOption.AllDirectories))
                {
                    string destinationPath = sourcePath.Replace(itemPath, itemPathInBin);
                    File.Copy(sourcePath, destinationPath, true);

                    // We must allow the destination file to be deletable
                    FileAttributes fileAttributes = File.GetAttributes(destinationPath);
                    if ((fileAttributes & FileAttributes.ReadOnly) != 0)
                    {
                        File.SetAttributes(destinationPath, fileAttributes & ~FileAttributes.ReadOnly);
                    }
                }
            }
            else
            {
                Debug.WriteLine("Warning: Deployment item does not exist - \"" + itemPath + "\"");
            }
        }
    }
}

Ответ 3

Я опробовал решение реализации DeploymentItemAttribute, но обнаружил, что это проблематично в том, что класс будет создан при загрузке тестов. Это привело к тому, что логика развертывания пыталась выполнить, поскольку тестовый адаптер Visual Studio NUnit загружал тестовые классы в фазе обнаружения. Это не такая замечательная идея.

Вместо этого я решил реализовать статический метод ItemDeployment.DeployItems для развертывания элементов, которые вы можете вызвать при настройке своего тестового оборудования:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

/// <summary>
/// Logic for deploying items for tests.
/// </summary>
internal static class ItemDeployment
{
    /// <summary>
    /// Call in subclass to deploy items before testing.
    /// </summary>
    /// <param name="items">Items to deploy, relative to project root.</param>
    /// <param name="retainDirectories">Retain directory structure of source items?</param>
    /// <exception cref="FileNotFoundException">A source item was not found.</exception>
    /// <exception cref="DirectoryNotFoundException">The target deployment directory was not found</exception>
    public static void DeployItems(IEnumerable<string> items, bool retainDirectories=false)
    {
        var environmentDir = new DirectoryInfo(Environment.CurrentDirectory);
        var binFolderPath = GetDeploymentDirectory();

        foreach (var item in items)
        {
            if (string.IsNullOrWhiteSpace(item))
            {
                continue;
            }

            string dirPath = retainDirectories ? Path.GetDirectoryName(item) : "";
            var filePath = item.Replace("/", @"\");
            var itemPath = new Uri(Path.Combine(environmentDir.Parent.Parent.FullName,
                filePath)).LocalPath;
            if (!File.Exists(itemPath))
            {
                throw new FileNotFoundException(string.Format("Can't find deployment source item '{0}'", itemPath));
            }

            if (!Directory.Exists(binFolderPath))
                throw new DirectoryNotFoundException(string.Format("Deployment target directory doesn't exist: '{0}'", binFolderPath));
            var dirPathInBin = Path.Combine(binFolderPath, dirPath);
            if (!Directory.Exists(dirPathInBin))
                Directory.CreateDirectory(dirPathInBin);
            var itemPathInBin = new Uri(Path.Combine(binFolderPath, dirPath, Path.GetFileName(filePath))).LocalPath;
            if (File.Exists(itemPathInBin))
            {
                File.Delete(itemPathInBin);
            }
            File.Copy(itemPath, itemPathInBin);
        }
    }

    /// <summary>
    /// Get directory test is deployed in.
    /// </summary>
    /// <returns></returns>
    public static string GetDeploymentDirectory()
    {
        return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    }
}

Затем в вашем тестовом устройстве вы можете развернуть элементы для своих тестов следующим образом:

[TestFixture]
public class TestDatabaseService
{
    /// <summary>
    /// This is run once before any tests in this fixture.
    /// </summary>
    [TestFixtureSetUp]
    public void SetUpFixture()
    {
        ItemDeployment.DeployItems(new[] { @"App_Data\database.mdf" });
    }
}

Ответ 4

Я взял решение от @Matthew, немного его очистил и расширил, чтобы поддерживать несколько атрибутов для одного теста, и целые каталоги, которые можно использовать как DeploymentItems (включая каталоги, содержащие подкаталоги).

namespace NUnitDeploymentItem
{
    using System;
    using System.IO;
    using System.Reflection;

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]
    public class DeploymentItem : Attribute
    {
        /// <summary>
        /// Marks an item to be relevant for a unit-test and copies it to deployment-directory for this unit-test.
        /// </summary>
        /// <param name="fileProjectRelativePath">The project-relative path to a file or a folder that will be copied into the deployment-directory of this unit-test.</param>
        public DeploymentItem(string fileProjectRelativePath)
        {
            // Escape input-path to correct back-slashes for Windows
            string filePath = fileProjectRelativePath.Replace("/", "\\");

            // Look up, where we are right now
            DirectoryInfo environmentDir = new DirectoryInfo(Environment.CurrentDirectory);

            // Get the full item-path of the deployment item
            string itemPath = new Uri(Path.Combine(environmentDir.Parent.Parent.FullName, filePath)).LocalPath;

            // Get the target-path where to copy the deployment item to
            string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            // Assemble the target path
            string itemPathInBin = new Uri(Path.Combine(binFolderPath, filePath)).LocalPath;

            // Decide whether it a file or a folder
            if (File.Exists(itemPath)) // It a file
            {
                // If it already exists, remove it
                if (File.Exists(itemPathInBin))
                {
                    File.Delete(itemPathInBin);
                }

                // Assemble the parent folder path (because the item might be in multiple sub-folders.
                string parentFolderPathInBin = new DirectoryInfo(itemPathInBin).Parent.FullName;

                // If the target directory does not exist, create it
                if (!Directory.Exists(parentFolderPathInBin))
                {
                    Directory.CreateDirectory(parentFolderPathInBin);
                }

                // If the source-file exists, copy it to the destination
                if (File.Exists(itemPath))
                {
                    File.Copy(itemPath, itemPathInBin);
                }
            }
            else if (Directory.Exists(itemPath)) // It a folder
            {
                // If it already exists, remove it
                if (Directory.Exists(itemPathInBin))
                {
                    Directory.Delete(itemPathInBin, true);
                }

                // If the source-directory exists, copy it to the destination
                if (Directory.Exists(itemPath))
                {
                    // Create target directory
                    Directory.CreateDirectory(itemPathInBin);

                    // Now Create all of the sub-directories
                    foreach (string dirPath in Directory.GetDirectories(itemPath, "*", SearchOption.AllDirectories))
                    {
                        Directory.CreateDirectory(dirPath.Replace(itemPath, itemPathInBin));
                    }

                    //Copy all the files & Replaces any files with the same name
                    foreach (string newPath in Directory.GetFiles(itemPath, "*.*", SearchOption.AllDirectories))
                    {
                        File.Copy(newPath, newPath.Replace(itemPath, itemPathInBin), true);
                    }
                }
            }
        }
    }
}

На самом деле это решение, которое было построено на основе ответов на эти вопросы: Проверить, является ли Путь файлом или каталогом, Скопировать весь контент в каталог и Создать файл, если целевая папка не существует.