NUnit DeploymentItem

时间:2012-02-21 13:15:51

标签: .net nunit

在MsTest中,如果我需要来自另一个项目的某个文件进行测试,我可以指定DeploymentItem属性。 NUnit中有类似的内容吗?

5 个答案:

答案 0 :(得分:24)

您应该查看contrasts the capabilities of NUnit and MSTest

的另一个帖子

这里接受的答案具有误导性。 NUnit根本不提供[DeploymentItem(“”)]属性,这是@Idsa想要在NUnit中使用的等效解决方案。

我的猜测是,这种属性会违反NUnit作为“单元”测试框架的范围,因为要求在运行测试之前将项目复制到输出,这意味着它依赖于此资源可用。 / p>

我正在使用自定义属性来复制localdb实例,以便针对某些相当大的测试数据运行“单元”测试,而我每次都不会使用代码生成这些测试数据。

现在使用属性[DeploymentItem(“some / project / file”)]会将此资源从文件系统复制到bin中,再次按照测试方法有效刷新源数据:

[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);
}

答案 1 :(得分:4)

我对Alexander Pasha的解决方案做了一些改进:我给了属性与MSTest属性相同的签名,这样第一个参数就是绝对或相对文件或文件夹部署,可选的第二个参数是要部署它的绝对路径或相对路径。在这两种情况下,相对'意味着执行程序。我还从已部署的文件中删除了任何只读属性。这很重要 - 如果以前部署的文件无法覆盖,则属性将被抛出。 值得注意的是,MSTest和NUnit在部署测试期间使用的文件时采用了截然不同的策略。 MSTest 可能可能将文件复制到部署文件夹 - 请参阅here。 NUnit使用AppDomain的ShadowCopyFiles属性,该属性部署到用户临时文件夹中非常模糊的位置。虽然可以在NUnit中打开和关闭卷影复制,但在Visual Studio中使用Test Explorer时我不知道如何操作它。在这方面需要注意,版本2之前的Visual Studio NUnit测试适配器已启用阴影复制,但在版本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's 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's a file or a folder
            if (File.Exists(itemPath)) // It's 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's 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 + "\"");
            }
        }
    }
}

答案 2 :(得分:3)

我从@Matthew中选择了解决方案并将其扩展为支持多个属性用于一个测试,以及整个目录可用作DeploymentItems(包括包含子目录的目录)。< / p>

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's a file or a folder
            if (File.Exists(itemPath)) // It's 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's 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);
                    }
                }
            }
        }
    }
}

这实际上是根据这些问题的答案构建的解决方案:Check if Path is a file or directoryCopy entire content of a directoryCreate file if target folder does not exist

答案 3 :(得分:2)

我尝试了实现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 :(得分:1)

这是一个使用TestContext类与NUnit 3+一起使用的解决方案。测试

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, 
    AllowMultiple = false, 
    Inherited = false)]
public class DeploymentItem : System.Attribute {

    public DeploymentItem(string relativeFilePathToCopy) {
        relativeFilePathToCopy = EscapeFilePathSlashes(relativeFilePathToCopy);

        var filePathToCopy = GeneratePathForFileToCopy(relativeFilePathToCopy);

        var destinationFilePath  = new Uri(Path.Combine(TestContext.CurrentContext.WorkDirectory, relativeFilePathToCopy)).LocalPath;

        DeleteFileIfItAlreadyExistsInTargetDirectory(destinationFilePath);

        CopyFileFromProjectToTestWorkingDirectory(filePathToCopy, destinationFilePath);
    }

    private static string EscapeFilePathSlashes(string relativeFilePathToCopy)
    {
        relativeFilePathToCopy = relativeFilePathToCopy.Replace("/", @"\");
        return relativeFilePathToCopy;
    }

    private static string GeneratePathForFileToCopy(string relativeFilePathToCopy)
    {
        var rootOfTestProject = new DirectoryInfo(TestContext.CurrentContext.TestDirectory).Parent.Parent.FullName;
        var filePathToCopy = new Uri(Path.Combine(rootOfTestProject, relativeFilePathToCopy)).LocalPath;
        return filePathToCopy;
    }

    private static void CopyFileFromProjectToTestWorkingDirectory(string pathToFileInProject, string targetUriForFile)
    {
        if (File.Exists(pathToFileInProject))
        {
            File.Copy(pathToFileInProject, targetUriForFile);
        }
    }

    private static void DeleteFileIfItAlreadyExistsInTargetDirectory(string targetUriForFile)
    {
        if (File.Exists(targetUriForFile))
        {
            File.Delete(targetUriForFile);
        }
    }
}