In the Creating custom attributes with internal NUnit features article, I mentioned the ITest
interface that gives us access to test properties. In this post, I'd like to talk a little bit more about the TestContext class and the opportunities that it brings us. First of all, let's define some kind of challenge that we want to solve at the end using the above-mentioned TestContext
class and especially its static CurrentContext
property.
Challenge: To provide infrastructure for saving logs for each failed NUnit test with splitting by sub-folders.
First of all, we need to understand where we'd like to save our logs. Working with the file system is painful sometimes because we need to be careful with absolute paths. In order to solve this problem, we need to recall that NUnit tests are always run from a DLL file, doesn't matter what way of running tests we use (Visual Studio, NUnit GUI, command line or continuous integration system). This similarity allows us to utilize the first property of the CurrentContext
– TestDirectory
. Let's see an example:
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System.IO;
namespace TestContextExamples
{
[TestFixture]
public class FoldersAndNamesExamples
{
private string CurrentTestFolder = TestContext.CurrentContext.TestDirectory;
private DirectoryInfo LogsFolder;
private DirectoryInfo SubTestFolder;
[OneTimeSetUp]
public void OneTimeSetUp()
{
LogsFolder = Directory.CreateDirectory(Path.Combine(CurrentTestFolder, "Logs"));
}
[SetUp]
public void SetUp(){ }
[Test]
public void MyTest()
{
}
[TearDown]
public void TearDown(){ }
}
}
The private CurrentTestFolder string
points us to the directory where the DLL file with NUnit tests is located. For example, if we run tests from Visual studio, it's usually the [solution]\[project]\bin\Debug\ or [solution]\[project]\bin\Release\ folders. According to our initial goal, I added a structure of NUnit methods and a couple of private
members of the class which corresponds to the common logs folder and sub-folders for each test: LogsFolder
and SubTestFolder
.
In the OneTimeSetUp()
method (which is executed only once), we create the “Logs” folder inside the CurrentTestFolder
. So, before any test is started, we have the place where we can save our logs. But what about splitting logs by sub-folders corresponding to the test names? It's time to appeal to the CurrentContext
again! Let's see how we can modify the SetUp()
method:
[SetUp]
public void SetUp()
{
var currentTestName = TestContext.CurrentContext.Test.Name;
SubTestFolder = Directory.CreateDirectory(Path.Combine(LogsFolder.FullName, currentTestName));
}
As we see from the method's body above, CurrentContext
allows us to get a current test name by accessing its Test.Name
property. Then, we just create an appropriate folder inside the “Logs” folder. Due to the purpose of the method followed after the [SetUp]
attribute, it's executed before each test, i.e., we create a new folder for each test according to its name.
Except for a test name, TestContext.CurrentContext.Test
property gives us access to the following data:
ClassName
FullName
- test ID
MethodName
- Properties bag (see later)
Only one task remains – to provide saving logs for failed tests only. First of all, we need to understand what we mean under the “failed tests” because a test in the NUnit world can have different states: passed, failed, failed with an error, skipped, inconclusive, etc. Let's decide that we'd like to save our logs for those tests which are failed because of assertions and unexpected exceptions. All the rest we don't care. Another question is where we should perform this check (if a test is failed or not)? The answer is in the method marked with the [TearDown]
attribute because it's executed after each test and this is the correct place where we know the test result already. Here is the final code of our infrastructure:
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System.IO;
namespace TestContextExamples
{
[TestFixture]
public class FoldersAndNamesExamples
{
private string CurrentTestFolder = TestContext.CurrentContext.TestDirectory;
private DirectoryInfo LogsFolder;
private DirectoryInfo SubTestFolder;
[OneTimeSetUp]
public void OneTimeSetUp()
{
LogsFolder = Directory.CreateDirectory(Path.Combine(CurrentTestFolder, "Logs"));
}
[SetUp]
public void SetUp()
{
var currentTestName = TestContext.CurrentContext.Test.Name;
SubTestFolder = Directory.CreateDirectory
(Path.Combine(LogsFolder.FullName, currentTestName));
}
[Test]
public void MyTest()
{
}
[TearDown]
public void TearDown()
{
var testResult = TestContext.CurrentContext.Result.Outcome;
if (Equals(testResult, ResultState.Failure) ||
Equals(testResult == ResultState.Error))
{
}
}
}
}
As you can see from the TearDown()
method, we get the TestContext.CurrentContext.Result.Outcome
property of the current test. Then, we compare it with known values of the ResultState enum
and make a decision: to save or not to save our logs for this specific test.
What else can the CurrentContext
give us? For example, Random numbers generator:
[Test]
public void RandomGeneratorTest()
{
Console.WriteLine(TestContext.CurrentContext.Random.Next());
Console.WriteLine(TestContext.CurrentContext.Random.NextDecimal());
Console.WriteLine(TestContext.CurrentContext.Random.NextBool());
}
The Random
property of the Randomizer
class provides lots of methods for obtaining random numbers of different types (bool
, integer
, float
, long
, decimal
, etc.). This is very useful in some cases if we want to make our unit tests more robust and test more different combinations. Another interesting example of the CurrentContext
usage is shown below:
[Test]
[Category("Functional"), Category("Release")]
public void CategoriesPropertiesTest()
{
foreach (var category in TestContext.CurrentContext.Test.Properties["Category"])
{
Console.WriteLine(category);
}
}
I've mentioned that CurrentContext
gives us a way to get the Property
bag of a test. The example above shows exactly that case when we can get access to different NUnit test properties. As you might know, an NUnit test can have a lot of different attributes: Description
, Order
, Repeat
, Category
, Bug
, etc. If we know what we're looking for (the name of the property), we're able to get it, check its value and adjust some logic inside the test or SetUp
/TearDown
methods.
Summary
Internal NUnit features have a lot of interesting methods and properties inside. As we've just seen, the TestContext contains many useful properties which can be and should be used by QA Automation engineers in order to make our tests more readable, flexible and maintainable. Explore it, try it and have fun!