2012-02-21 23 views
21

En MsTest si necesito algún archivo de otro proyecto para mi prueba, puedo especificar el atributo DeploymentItem. ¿Hay algo similar en NUnit?NUnit DeploymentItem

Respuesta

22

Debería echar un vistazo a otro hilo que contrasts the capabilities of NUnit and MSTest.

La respuesta aceptada aquí es engañosa. NUnit no ofrece el atributo [DeploymentItem ("")] en absoluto, para lo cual @Idsa quería una solución equivalente para NUnit.

Supongo que este tipo de atributo violaría el alcance de NUnit como un marco de prueba "unidad" que requiere que un elemento se copie al resultado antes de ejecutar una prueba implica que tiene una dependencia de que este recurso esté disponible.

Estoy usando un atributo personalizado para copiar sobre una instancia de localdb para ejecutar pruebas de "unidad" en comparación con algunos datos de prueba importantes que prefiero no generar con código cada vez.

Ahora, utilizando el atributo [DeploymentItem ("some/project/archivo")] copiará este recurso del sistema de archivos en la papelera de nuevo con eficacia refrescante mis datos de origen por el método de ensayo:

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

entonces podemos utilizar de este modo:

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

Sería muy bueno si allowMultiple = true en caso de tener que copiar varios elementos. –

+0

@Matthew He corregido el resaltado de sintaxis en su respuesta. Consulte [¿Qué es el resaltado de sintaxis y cómo funciona?] (Http://meta.stackexchange.com/questions/184108/what-is-syntax-highlighting-and-how-does-it-work) para obtener más información. –

+0

¡Gracias por esto, y gracias por permitir múltiples atributos! –

2

probé la solución de implementar DeploymentItemAttribute, pero le resultó problemático, ya que la clase se crea una instancia cuando se cargaron las pruebas. Esto dio como resultado una lógica de implementación que intentaba ejecutarse cuando el Adaptador de prueba NUnit de Visual Studio estaba cargando clases de prueba, en su fase de descubrimiento. Esta no es una gran idea.

he optado en lugar de implementar un método estático, ItemDeployment.DeployItems, para el despliegue de elementos, que se puede llamar al configurar su dispositivo de prueba:

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

Luego, en el dispositivo de prueba, puede implementar artículos para las pruebas de este modo:

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

Tengo un problema al usar este código porque mi archivo en la ruta Bin está siendo utilizado por otro proceso (Test host o Visual Studio host, etc.) por lo que no puedo hacer que File.Delete implemente una versión nueva del archivo. –

2

que he recogido de la solución @Matthew lo limpió un poco y lo amplió para soportar múltiples usos de atributo para una prueba y directorios completos que se pueden utilizar como DeploymentItems (incluyendo directorios que contiene subdirectorios).

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

Esto es realmente una solución que se construye a partir de las respuestas a estas preguntas: Check if Path is a file or directory, Copy entire content of a directory y Create file if target folder does not exist.

4

He realizado algunas mejoras en la solución de Alexander Pasha: le he dado al atributo la misma firma que el MSTest, de modo que el primer parámetro es el archivo o carpeta absoluta o relativa a implementar, y el segundo opcional parámetro es la ruta absoluta o relativa a la que se implementará. En ambos casos, "relativo" significa para el programa en ejecución. También eliminé cualquier atributo de solo lectura del archivo implementado. Esto es importante: si un archivo implementado anteriormente no se puede sobrescribir, el atributo arrojará. También vale la pena señalar que MSTest y NUnit tienen estrategias muy diferentes a la hora de implementar los archivos que se utilizarán durante las pruebas.MSTest puede o no puede copiar archivos en una carpeta de implementación - consulte here. NUnit usa la propiedad ShadowCopyFiles del AppDomain, que se implementa en una ubicación muy oscura en la carpeta temporal del usuario. Mientras que la copia oculta se puede activar y desactivar en NUnit, no sé cómo manipularla cuando se usa Test Explorer en Visual Studio. A este respecto, es importante tener en cuenta que el Visual Studio NUnit Test Adapter anterior a la Versión 2 tiene la copia oculta activada, pero en la versión 2 en adelante está desactivada. Esto puede tener un gran impacto en las pruebas que usan elementos de implementación y vale la pena tenerlo en cuenta. aquí está mi versión de la 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 + "\""); 
      } 
     } 
    } 
} 
+0

Parece, esto se llama para cada prueba. Esto es, por supuesto, malo para el rendimiento. En mi versión agregué una prueba si la combinación (ruta, directorio de salida) ya estaba manejada, usando un HashSet estático . – Piper