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?
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);
}
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 + "\"");
}
}
}
}
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" });
}
}
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.