I'm fairly rusty when it comes to C#. I've been poking my nose around internet trying to find a solution to my question without success.
I created a test project using MSTest. Some tests use files, that I added to my project test under the folder TestData, and they are copied when executing the test by using the attribute DeploymentItem.
Example: [DeploymentItem(#"TestData\test.txt")]
This copies test.txt at the execution folder and it works. However, when I want to use this file in the test, I then have to work on "test.txt" instead of #"TestData\test.txt". Thus, if I want to factorize my code, I have to have two variables:
const string testFileName = "test.txt";
const string testFilePath = #"TestData\test.txt";
and then use them as
[DeploymentItem(testFilePath)]
public void TestFunction()
{
[...]testFileName[...]
}
Ideally, I want instead to write:
[DeploymentItem(testFilePath)]
public void TestFunction()
{
[...]testFilePath[...]
}
This way I would only need one variable.
It would work if I use the second argument of DeploymentItem as such:
const string testFilesFolder = "TestData";
const string testFilePath = #"TestData\test.txt";
[DeploymentItem(testFilePath, testFilesFolder)]
public void TestFunction()
{
[...]testFilePath[...]
}
However, that forces me and everyone to think about passing the second argument every time we use DeploymentItem. But it has the merit of working.
Here are the different things I tried to do to address the issue:
Inheriting from DeploymentItem to simply add my own constructor: DeploymentItem is sealed so this is not possible.
Creating my own attribute, by copying the code of DeploymentItem. The file is not copied at all:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
class DeployFileAttribute : Attribute
{
public DeployFileAttribute(string path)
{
Path = path;
OutputDirectory = System.IO.Path.GetDirectoryName(path);
}
public string Path { get; }
public string OutputDirectory { get; }
}
[DeployFile(testFilePath)] // testFilePath is not copied at all, even though the constructor is correctly executed.
Creating a method that would return the attribute. It does not seem like it is possible to use the result of a method as an attribute:
public static DeploymentItemAttribute DeployFile(string path)
{
return new DeploymentItemAttribute(path, System.IO.Path.GetDirectoryName(path));
} // No compilation error
[DeployFile(testFilePath)] // DeployFileAttribute type does not exist
Creating something like a C++ style using statement or C style macro, I can't seem to find a syntax that works
using DeployFile(string toto) = DeploymentItemAttribute(toto, System.IO.Path.GetDirectoryName(path)); // Syntax is wrong, could not find one that works
Any hindsight would be welcome!
From my point of view, there are only two possibilities:
You use DeploymentItem in the way it was created by Microsoft.
[DeploymentItem(testFilePath, testFilesFolder)] as you manshioned in your post
You can combine source path:
const string testFileName = "test.txt";
[DeploymentItem(#"TestData\" + testFileName)]
public void TestFunction()
{
[...]testFileName[...]
}
In this case, you'll have just one variable :)
You can write your own extension for MSTest and create an attribute you need. But this is not the easy way. As key words for this approach, you could google for TestExtensionExecution, ITestMethodInvoker and TestClassExtensionAttribute
On the other hand, this is very understandable, why DeploymentItem is implemented as it is. Do not forget, that the source folder can be an absolute path as well. So assume, that you have the following attribute [DeploymentItem(#"S:\Shared\TestFiles\AAA\BBB\test.txt")] What should be the destination folder? But even with relative paths: [DeploymentItem(#"..\..\..\TestFiles\AAA\BBB\test.txt")] - can say the name of the destination folder in this case?
Related
I have a .NET assembly that is built by me but would like to be able rewrite the .DLL with some minor but arbitrary attribute change file at runtime. Specifically I would like to be able to change a property of an attribute of a class so that I can customize the binary depending on the situation.
To illustrate, I want to achieve the effect of editing the assembly being generated from the code
[SomeAttribute("Name")]
public class MyClass{
...
such that the new assembly is functionally the same as
[SomeAttribute("Custom Name")]
public class MyClass{
...
And this "Custom Name" could be anything (determined at runtime). Is this possible to do at runtime?
The reason why the actual .DLL needs to be modified is because it will get loaded up by a seperate process which cannot determine the runtime information (I do not control this process).
Experimentation so far has shown that it seems to work if the new "Custom Name" is the same length as the original, but not otherwise (even if you edit the preceding byte that specifies the length; presumably there are offsets stored in the file somewhere).
EDIT: Forgot to mention, solution needs to be under the .NET 2 framework as well.
Unclear what you really want to do (XY problem?)
Still, if you want to modify an assembly, you normally use Mono.Cecil that self-describes as: you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly. .
Note that an attribute can contain extra data on top of the data that is passed as a parameter:
public class MyAttribute : Attribute
{
public MyAttribute(string str)
{
argument = str;
}
private string argument;
public string Argument { get; }
public string AssemblyName
{
get
{
return Assembly.GetEntryAssembly().FullName;
}
}
}
[MyAttribute("Hello")]
class Program
{
static void Main(string[] args)
{
var attr = typeof(Program).GetCustomAttribute<MyAttribute>();
Console.WriteLine(attr.Argument);
Console.WriteLine(attr.AssemblyName);
}
}
Using the extremely helpful suggestion from #xanatos I have made this solution:
Under .NET 2, you can install package Mono.Cecil 0.9.6.1.
The code then is as follows:
AssemblyDefinition assbDef = AssemblyDefinition.ReadAssembly("x.dll");
TypeDefinition type = assbDef.MainModule.GetType("NameSpace.MyClass").Resolve();
foreach (CustomAttribute attr in type.CustomAttributes)
{
TypeReference argTypeRef = null;
int? index = null;
for (int i = 0; i < attr.ConstructorArguments.Count; i++)
{
CustomAttributeArgument arg = attr.ConstructorArguments[i];
string stringValue = arg.Value as string;
if (stringValue == "Name")
{
argTypeRef = arg.Type;
index = i;
}
}
if (index != null)
{
attr.ConstructorArguments[(int)index] = new CustomAttributeArgument(argTypeRef, newName);
}
}
assbDef.Write("y.dll");
Which will search an assembly for any attribute arguments with value "Name" and replace their value with newName.
Rather than modifying the DLL, you can add attributes at run-time using the TypeDescriptor class; e.g.
TypeDescriptor.AddAttributes(typeof(MyClass), new SomeAttribute("Custom Name"));
The only caveat with this approach is that code which relies purely on reflection will not be able to read the added attribute(s) - you must use TypeDescriptor.GetAttributes(). However, most of the built-in .NET Framework methods that operate on attributes are aware of metadata added at run-time.
https://msdn.microsoft.com/en-us/library/system.componentmodel.typedescriptor_methods(v=vs.110).aspx
Using MSTest in a .Net Core Unit test project. I am attempting to use a csv datasource to provide the data for a test method.
Previously, I would use something like below in a .Net Framework test project:
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", #"data.csv", "data#csv", DataAccessMethod.Sequential),
DeploymentItem("data.csv"),
TestMethod]
public void ValuesController_Post()
{
_controller.Post(TestContext.DataRow["body"]);
_valuesRepository.Verify(_ => _.Post(It.IsAny<string>()), Times.Once);
}
The key here being the DataRow property found in TestContext. This doesn't appear to exist in the .Net Core version of the TestContext.
How would I go about doing this in .Net Core?
Since moving to aspnet core, I've never been able to use the same [Datasource(...)] attribute to iterate through test data, my data-driven tests are always skipped.
Have you considered switching to another approach with [DataTestMethod] and [DynamicData] with a custom source that reads you file ?
Here's a good article on this :
https://www.meziantou.net/2018/02/05/mstest-v2-data-tests
Maybe another way would be to read the whole file at the begining of the test and then iterate through the dataset as One single unit test?
Hope this helps.
It took me an afternoon to fiddle with things, but I finally found a solution. Since you don't specify your test or CSV file, here is a quick example I could get working.
Long story short, I installed the CsvHelper NuGet package, because parsing CSV is dead easy right up to the point it is not. As Carl Verret pointed out, you need to use the [DynamicData(...)] attribute above your test method, and then parse the CSV using CsvHelper.
The CSV File (Example.csv)
A,B,IsLessThanZero
1,2,FALSE
3,-5,TRUE
Important: Make sure this CSV file is included in your test project and "Copy To Output Directory" is set to "Always" in the properties for the CSV file in Solution Explorer.
Data Transfer Object Used By CsvHelper
public class AdditionData
{
public int A { get; set; }
public int B { get; set; }
public bool IsLessThanZero { get; set; }
}
The Test Class
[TestClass]
public class ExampleTests
{
// HINT: Look in {Your Test Project Folder}\bin\{Configuration}\netcore3.1\FolderYourCsvFileIsIn for the CSV file.
// Change this path to work with your test project folder structure.
private static readonly string DataFilePath = Path.GetDirectoryName(typeof(ExampleTests).Assembly.Location) + #"\FolderYourCsvFileIsIn\Example.csv";
[TestMethod]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method)]
public void AddingTwoNumbers(AdditionData data)
{
bool isLessThanZero = data.A + data.B < 0;
Assert.AreEqual(data.IsLessThanZero, isLessThanZero);
}
private static IEnumerable<object[]> GetData()
{
using var stream = new StreamReader(DataFilePath);
using var reader = new CsvReader(stream, new CsvConfiguration(CultureInfo.CurrentCulture));
var rows = reader.GetRecords<AdditionData>();
foreach (var row in rows)
{
yield return new object[] { row };
}
}
}
After building your solution, you will see a single test in Test Explorer. Running this single test runs all variants defined in your CSV file:
I have the following code (sample1.evol - file attached to my unit test project):
[Test]
public void LexicalTest1()
{
var codePath = Path.GetFullPath(#"\EvolutionSamples\sample1.evol");
//.....
}
I found that the working directory of test execution is not the assembly directory: (in my case codepath variable assigned to d:\EvolutionSamples\sample1.evol).
So, how can I change the execution working directory (without hardcode)? What will be the best practice to load any files attached to test case?
You can use following to get the directory of assembly running the code something like
var AssemblyDirectory = TestContext.CurrentContext.TestDirectory
I use this for integration tests that need to access data files.
On any machine the test needs to run create a system environment variable named TestDataDirectory that points to the root of where your test data is.
Then have a static method that gets the file path for you..
public static class TestHelper
{
const string EnvironmentVariable = "TestDataDirectory";
static string testDataDir = Environment.GetEnvironmentVariable(EnvironmentVariable);
public static string GetTestFile(string partialPath)
{
return Path.Combine(testDataDir, partialPath);
}
}
...
[Test]
public void LexicalTest1()
{
var codePath = TestHelper.GetTestFile(#"\EvolutionSamples\sample1.evol");
//.....
}
I am using this code:
var str = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if (str.StartsWith(#"file:\")){
str = str.Substring(6);
}
Getting in str variable the assembly directory.
We were having a problem where tests run using ReSharper and NCrunch would work, but the native VS Test Runner would not be able to find the files, when given just a relative file path for the test to use. I solved it by creating a function that you pass the relative test file path into, and it will give you the absolute file path.
private static string _basePath = Path.GetDirectoryName(typeof(NameOfYourTestClassGoesHere).Assembly.Location);
private string GetAbsoluteTestFilePath(string relativePath) => Path.Combine(_basePath, relativePath);
You would then use the function like so:
var input = File.ReadAllLines(GetAbsoluteTestFilePath(#"TestData/YourTestDataFile.txt"));
Is there any way to retrieve the current source filename and linenumber in C# code and print that value in the console output? Like LINE and FILE in C?
Please advise.
Many thanks
Anders Hejlsberg presented new API for that in BUILD keynote:
Print current file name, method name and line number
private static void Log(string text,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine("{0}_{1}({2}): {3}", Path.GetFileName(file), member, line, text);
}
Test:
Log(".NET rocks!");
Output:
Program.cs_Main(11): .NET rocks!
What's going on here?
You define a method with optional parameters and decorate them with special attributes. If you call method without passing actual arguments (leave defaults) - the Framework populates them for you.
This answer is outdated! See #taras' answer for more recent information.
No constant :(
What you can do is a lot uglier :
string currentFile = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileName();
int currentLine = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileLineNumber();
Works only when PDB files are available.
You can use the StackTrace object from the System.Diagnostics namespace but the information will only be available if the PDB files are there.
PDB files are generated by default for both the Debug and Release builds the only difference is that Debug is setup to generate a full debug info where as the Release build is setup to only generate a pdb (full/pdb-only).
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileName());
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileLineNumber());
There are no constants defined for that as of now.
The .NET way of doing it is using StackTrace class.
It however works only for Debug builds. So in case you use it, you can have the code using StackTrace between
#if DEBUG
//your StackTrace code here
#endif
You can read about using #if preprocessors for your DEBUG vs. RELEASE builds in the following Stackoverflow thread.
C# if/then directives for debug vs release
EDIT: Just in case you still need this debugging information in release builds, read the following answer on Stackoverflow:
Display lines number in Stack Trace for .NET assembly in Release mode
If you want some more internal detail, but you don't specifically need filename and line number, you can do something like this:
System.Diagnostics.Debug.Print(this.GetType().ToString() + " My Message");
This has an advantage over printing out the filename in that if you put this in a parent class, it will print out the child class name that is actually running the code.
If you wanted to write your own version of Debug.Assert, then here's a more complete answer:
// CC0, Public Domain
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System;
public static class Logger {
[Conditional("DEBUG")]
public static void Assert(bool condition, string msg,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0
)
{
// Debug.Assert opens a msg box and Trace only appears in
// a debugger, so implement our own.
if (!condition)
{
// Roughly follow style of C# error messages:
// > ideone.cs(14,11): error CS1585: Member modifier 'static' must precede the member type and name
Console.WriteLine($"{file}({line}): assert: in {member}: {msg}");
// Or more precisely match style with a fake error so error-parsing tools will detect it:
// Console.WriteLine($"{file}({line}): warning CS0: {msg}");
}
}
}
class Program {
static void Main(string[] args) {
Logger.Assert(1+1 == 4, "Why not!");
}
}
Try it online.
I want to load an external XML file in a unit test to test some processing code on that XML. How do I get the path of the file?
Usually in a web app I would do:
XDocument.Load(Server.MapPath("/myFile.xml"));
But obviously in my unit test I have no reference to Server or HttpContext so how can I map a path so that I don't have to specify the full path?
UPDATE:
I just want to make it clear that the code I'm actually testing is for an XML parser class, something like:
public static class CustomerXmlParser {
public static Customer ParseXml(XDocument xdoc) {
//...
}
}
So to test this I need to parse a valid XDocument. The method being tested does not access the file system itself. I could create the XDocument from a String directly in the test code but I thought it would be easier to just load it from a file.
Another idea would be to utilize dependency injection.
public interface IPathMapper {
string MapPath(string relativePath);
}
And then simply use 2 implementations
public class ServerPathMapper : IPathMapper {
public string MapPath(string relativePath){
return HttpContext.Current.Server.MapPath(relativePath);
}
}
And then you also need your mock implementation
public class DummyPathMapper : IPathMapper {
public string MapPath(string relativePath){
return "C:/Basedir/" + relativePath;
}
}
And then all your functions that needs to map path's would simply need to have access to an instance of IPathMapper - in your web app it needs to be the ServerPathMapper and in your unit tests the DummyPathMapper - basic DI (Dependency Injection).
Personally, I'd be very wary about having any code that relies on a back-end
resource store, be that a file system or a database - you are introducing a dependency into your unit test that is likely to lead to false negatives i.e tests failing not because of your specific test code but because the file isn't there or the server is unavailable etc.
See this link for IMO a good definition of what a unit test is and more importantly is not
Your unit test should be testing an atomic, well-defined piece of functionality not testing whether a file can load.
One solution is to 'mock' the file load - there are various approaches to this however, I'd personally only mock the interface to the file system your are using and not try and do any full filesystem mocking - here's a good SO post and here's a good SO discussion on file system mocking
Hope that helps
Usually for unit tests I add the xml files as embedded resources to the project and load them using a method like this:
public static string LoadResource(string name)
{
Type thisType = MethodBase.GetCurrentMethod().DeclaringType;
string fullName = thisType.Namespace + "." + name + ".xml";
using (Stream stream = thisType.Module.Assembly.GetManifestResourceStream(fullName))
{
if(stream==null)
{
throw new ArgumentException("Resource "+name+" not found.");
}
StreamReader sr = new StreamReader(stream);
return sr.ReadToEnd();
}
}
Edit: I'm starting from scratch since I guess I interpreted your question the wrong way initially.
The best way to load an XML file in your unit test for injecting it then to some of your classes is to use the DeploymentItem attribute in MS unit tests.
This will look like the following:
[TestMethod]
[DeploymentItem(#"DataXmlFiles\MyTestFile.xml", "DataFiles")]
public void LoadXMLFileTest()
{
//instead of "object" use your returning type (i.e. string, XDocument or whatever)
//LoadXmlFile could be a method in the unit test that actually loads an XML file from the File system
object myLoadedFile = LoadXmlFile(Path.Combine(TestContext.TestDeploymentDir, "DataFiles\\MyTestFile.xml"));
//do some unit test assertions to verify the outcome
}
I didn't test the code now on a debugger, but it should work.
Edit:
Btw, when you use DeploymentItem consider this post here.
Classes:
internal class FakeHttpContext : HttpContextBase
{
public override HttpRequestBase Request { get { return new FakeHttpRequest(); } }
}
internal class FakeHttpRequest : HttpRequestBase
{
public override string MapPath(string virtualPath)
{
return /* your mock */
}
}
Usage:
[TestMethod]
public void TestMethod()
{
var context = new FakeHttpContext();
string pathToFile = context.Request.MapPath("~/static/all.js");
}
This may be helpful to someone. I had a related issue. Wanted to use an Excel file from a root-level folder within my c# Unit Test project.
I had a root-leve folder named "TestFiles". Inside I had "Test.xlsx".
What i did was:
Right-click on the "Test.xlsx", go to Properties and set "Copy To Output Directory" = "Copy Always"
Now the file and its containing folder "TestFiles" always get copied into the bin folder of the Unit Test project. So that I was able to use it like so:
var filePath = "TestFiles/Test.xlsx";
var strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filePath + ";Extended Properties=\"Excel 12.0;HDR=Yes;IMEX=0\"";
using (var conn = new OleDbConnection(strConn))
{
conn.Open();
...
}