NUnit DeploymentItem

Dans MsTest si j'ai besoin d'un fichier d'un autre projet pour mon test, je peux spécifier L'attribut DeploymentItem. Y a-t-il quelque chose de similaire dans NUnit?

22
demandé sur SiberianGuy 2012-02-21 17:15:51

4 réponses

Vous devriez vérifier un autre thread qui contraste les capacités de NUnit et MSTest .

La réponse acceptée ici est trompeuse. NUnit n'offre pas du tout l'attribut [DeploymentItem("")] qui est ce que @Idsa voulait une solution équivalente dans NUnit.

Ma conjecture est que ce type d'attribut violerait la portée de NUnit en tant que cadre de test "unitaire" car exiger qu'un élément soit copié dans la sortie avant d'exécuter un test implique qu'il a une dépendance sur cette ressource disponible.

J'utilise un attribut personnalisé pour copier sur une instance localdb pour exécuter des tests "unitaires" contre des données de test importantes que je préfère ne pas générer avec du code à chaque fois.

Maintenant, en utilisant l'attribut [DeploymentItem ("some/project/file")] va copier cette ressource du système de fichiers dans le bac à nouveau en actualisant efficacement mes données source par méthode de test:

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

Ensuite, nous pouvons utiliser comme ceci:

[Test]
[DeploymentItem("Data/localdb.mdf")]
public void Test_ReturnsTrue() 
{
    Assert.IsTrue(true);
}
23
répondu Matthew 2017-05-23 10:31:16

J'ai apporté quelques améliorations à la solution D'Alexander Pasha: j'ai donné à l'attribut la même signature que celle de MSTest, de sorte que le premier paramètre soit le fichier ou le dossier absolu ou relatif à déployer, et le second paramètre facultatif est le chemin absolu ou relatif vers lequel il doit être déployé. Dans les deux cas, "relatif" signifie pour le programme d'exécution. J'ai également supprimé tout attribut en lecture seule du fichier déployé. Ceci est important-si un fichier précédemment déployé ne peut pas être écrasé l'attribut jeter. Il est également intéressant de noter que MSTest et NUnit ont des stratégies très différentes quand il s'agit de déployer les fichiers qui doivent être utilisés pendant les tests. MSTest peut ou ne peut pas copier des fichiers vers un dossier de déploiement - voir ici. NUnit utilise la propriété ShadowCopyFiles de L'AppDomain, qui se déploie à un emplacement très obscur dans le dossier temp de l'utilisateur. Alors que la copie d'ombre peut être activée et désactivée dans NUnit lui-même, Je ne sais pas comment manipulez-le lors de L'utilisation de Test Explorer Dans Visual Studio. À cet égard Il est important de noter que l'Adaptateur de test Visual Studio NUnit avant la Version 2 a la copie d'ombre activée, mais dans la version 2 à partir, elle est désactivée. Cela peut avoir un impact majeur sur les tests qui utilisent des éléments de déploiement et mérite d'être pris en compte. Voici ma version du 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 + "\"");
            }
        }
    }
}
4
répondu Dave 2015-04-13 16:17:13

J'ai essayé la solution d'implémentation de DeploymentItemAttribute, mais je l'ai trouvée problématique en ce sens que la classe serait instanciée lorsque les tests étaient chargés. Cela a entraîné une logique de déploiement essayant de s'exécuter pendant que L'Adaptateur de Test Visual Studio NUnit chargeait des classes de test, dans sa phase de découverte. Ce n'est pas une bonne idée.

J'ai plutôt choisi d'implémenter une méthode statique, ItemDeployment.DeployItems, pour déployer des éléments, que vous pouvez appeler lors de la configuration de votre appareil de test:

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

Ensuite, dans votre test fixture, vous pouvez déployer des éléments pour vos tests comme ceci:

[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" });
    }
}
2
répondu aknuds1 2014-04-17 23:00:46

J'ai ramassé la solution de @Matthew l'a nettoyée un peu et l'ai étendue pour prendre en charge plusieurs utilisations d'attributs pour un test, et des répertoires entiers qui peuvent être utilisés comme DeploymentItems (y compris les répertoires qui contiennent des sous-répertoires).

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

C'est en fait une solution qui a été construite à partir des réponses à ces questions: Vérifiez si Path est un fichier ou un répertoire, copier le contenu entier d'un répertoire et Créer un fichier si le dossier cible ne existe.

2
répondu Alexander Pacha 2017-05-23 12:34:41