方法的每个输出的单独单元测试



我有一个方法,它将一个文件作为输入,然后基于该文件返回N个输出。

我想通过以下方式测试此方法:假设我们有 M 个文件要测试。对于每个文件,我想在测试程序(或单独的文件)中添加一行,由文件路径和 N 个预期输出组成。此数据应产生 N*M 个单独的测试,每对文件和预期输出一个。

有没有实现这一点的好方法?我希望每次测试运行每个文件不超过一次。

下面是一个做我想做的事的例子。如您所见,我必须为每个文件添加单独的测试类。我希望找到一个解决方案,我可以只添加带有测试数据的行(例如testData.Add(("thirdfile", 4), (348, 312));) 以测试新文件。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
}
}
public static class FileParser
{
private static int n = 0;
public static void Init(int parameter)
{
n = parameter;
}
public static (int output1, int output2) ParseFile(string filename)
{
return (filename[0] * n, filename[1] * n);
}
}
public class Tests
{
private Dictionary<(string, int), (int, int)> testData;
public Tests()
{
testData = new Dictionary<(string, int), (int, int)>();
testData.Add(("somefile", 3), (345, 333));
testData.Add(("anotherfile", 4), (291, 330));
testData.Add(("thirdfile", 4), (348, 312));
}
public void TestOutput1((int, int) result, string filename, int parameter)
{
Assert.AreEqual(testData[(filename, parameter)].Item1, result.Item1);
}
public void TestOutput2((int, int) result, string filename, int parameter)
{
Assert.AreEqual(testData[(filename, parameter)].Item2, result.Item2);
}
}
[TestClass]
public class Somefile
{
protected static (int, int) fileParseResult;
[ClassInitialize]
public static void ClassInit(TestContext context)
{
FileParser.Init(3);
fileParseResult = FileParser.ParseFile("somefile");
}
[TestMethod]
public void SomefileOutput1() { var tests = new Tests(); tests.TestOutput1(fileParseResult, "somefile", 3); }
[TestMethod]
public void SomefileOutput2() { var tests = new Tests(); tests.TestOutput2(fileParseResult, "somefile", 3); }
}
[TestClass]
public class Anotherfile
{
protected static (int, int) fileParseResult;
[ClassInitialize]
public static void ClassInit(TestContext context)
{
FileParser.Init(3);
fileParseResult = FileParser.ParseFile("anotherfile");
}
[TestMethod]
public void AnotherfileOutput1() { var tests = new Tests(); tests.TestOutput1(fileParseResult, "anotherfile", 4); }
[TestMethod]
public void AnotherfileOutput2() { var tests = new Tests(); tests.TestOutput2(fileParseResult, "anotherfile", 4); }
}
[TestClass]
public class Thirdfile
{
protected static (int, int) fileParseResult;
[ClassInitialize]
public static void ClassInit(TestContext context)
{
FileParser.Init(3);
fileParseResult = FileParser.ParseFile("thirdfile");
}
[TestMethod]
public void ThirdfileOutput1() { var tests = new Tests(); tests.TestOutput1(fileParseResult, "thirdfile", 4); }
[TestMethod]
public void ThirdfileOutput2() { var tests = new Tests(); tests.TestOutput2(fileParseResult, "thirdfile", 4); }
}
}

您实际上可以简化此操作,以便对该库的新测试不一定需要对测试库本身进行代码更改。

可在此处找到数据驱动单元测试的 MS 文档。

我见过人们在 csv 文件中使用类似的东西,然后当需要新的测试时,他们只需在 csv 文件中添加一行。

或者,我个人希望MSTest中提供的数据行功能。 示例 MS 文档可以在这里找到。我更喜欢这个选项,尽管新的测试用例确实需要一行新的代码。

它应该减少整体代码量。有点像这样。

[TestClass]
public class FileClass
{
[TestMethod]
[DataRow("somefile", 3, 345, 333)]
[DataRow("anotherfile", 4, 291, 330)]
public void Output1IsValid(string fileName, int parameter, int resultX, int resultY) 
{ 
var fileParseResult = FileParser.ParseFile(fileName);
Assert.AreEqual(fileParseResult.Item1, resultX);         
}
}

如果您开放超过 MSTest 或 xUnit,您可以查看 Nuclear.Test。

创建一个数据驱动的测试方法,该方法负责同时分析和检查两个结果项。

[TestMethod]
[TestParamters("someFile", 3, (345, 333))]
[TestParamters("anotherfile", 4, (291, 330))]
[TestParamters("thirdfile", 4, (348, 312))]
void TestFile(String someFile, Int32 parameter, (Int32, Int32) expected) {
(Int32, Int32) result = null;

Test.Note("Parsing '" + someFile + "'");
Test.IfNot.Action.ThrowsException(() => FileParser.Init(parameter), out Exception ex);
Test.IfNot.Action.ThrowsException(() => result = FileParser.ParseFile(someFile), out ex);
Test.IfNot.Object.IsNull(result);

Test.Note("Checking results for '" + someFile + "'");
Test.If.Value.IsEqual(result.Item1, expected.Item1);
Test.If.Value.IsEqual(result.Item2, expected.Item2);

}

有关使用 Nuclear.Test 编写数据驱动测试的更多信息,请参见此处。

请注意,这至少需要 。目前是NETStandard 2.0。 我确实意识到您可能不对不同的单元测试平台开放,但是由于您确实说过您对MSTest或xUnit持开放态度,我想您还没有完全决定。

这种方法也适用于MSTest和xUnit,但是在这些情况下会破坏OAPT,而Nuclear.Test不受这些限制的影响。

请注意,由于我是通过手机编写的,因此我无法测试此代码。其中可能有错别字或错误。

更新:

这将是使用 MSTest 的合法方法:

[TestMethod]
[DataRow("someFile", 3, (345, 333))]
[DataRow("anotherfile", 4, (291, 330))]
[DataRow("thirdfile", 4, (348, 312))]
public void TestFileParser(String fileName, Int32 parameter, (Int32, Int32) expected) {
FileParser.Init(parameter);
var result = FileParser.ParseFile(fileName);
Assert.AreEqual(result, expected);
}

事实证明,ValueTuple实现了IComparable<(T1,T2)>并且可以在一个断言中进行比较。

嗯,有一种方法可以用更少的代码来实现@pwrigshihanomoronimos方法。但是,您很可能需要单元测试来确保测试正常工作,因此我建议不要这样做。

我知道,编写这样的测试可能非常乏味,但是对于单元测试,首先有一个规则。

使它们尽可能简单。

只需使用最小的复杂性,这对于使测试成为可能是绝对必要的。

越不复杂,越不容易出错。

最好有一个单独的文件,其中包含所有参数和输出以进行比较并预先读取它。

我同意@Andreas保持单元测试简单。因此,不建议读取配置文件(配置单元测试功能的文件)。以下示例代码通过确保只读取一次 evey 文件来扩展@James Pusateri 良好的答案。

[TestClass]
public class UnitTest1
{
// Use a static Lazy<T> instance to read your file just once. 
// Replace <object> with your type. 
// Use multiple of these Lazy variables by using a Dictionary<string, Lazy<YourResultType>>
private static Lazy<object> fileParseResult = new Lazy<object>(() => FileParser.ParseFile("somefile"));
[ClassCleanup]
public static void ClassCleanup()
{
// in case you need to clean up something, do it here
// fileParseResult.Value.Dispose() if applicable
fileParseResult = null;
}
[TestMethod]
[DataRow("somefile", 3, 345, 333)]
[DataRow("anotherfile", 4, 291, 330)]
// add additional DataRow-lines here as required
public void OutputIsValid(string fileName, int parameter, int resultX, int resultY)
{
// make sure to only read 'fileParseResult.Value' and not change it.
Assert.AreEqual(fileParseResult.Value, fileName);
}
// dummy implementation for testing this code. Use your implemenation instead.
private class FileParser
{
internal static object ParseFile(string v) => v;
}
}

MSTest这是一个很好的备忘单 https://www.automatetheplanet.com/mstest-cheat-sheet/

xUnit具有类似的方法,但[Theory][InlineData]使用不同的属性。此外,xUnit 具有更复杂(和复杂)的上下文共享 https://xunit.net/docs/shared-context 的可能性。到目前为止,我一直设法以不需要这些高级上下文共享功能的方式简化测试场景。

经验和个人意见实际上,我不对单元测试使用任何上下文共享。原因是重构。考虑是否需要添加另一种需要稍微不同的共享上下文的测试方法。因此,您可以继续更改共享上下文,并无意中破坏许多现有测试。此外,我避免读取单元测试中的任何文件,而是使用返回预定义和可预测结果的模拟。

使用 xUnit 和 System Linq

使用包含两个测试结果的内联数据(或 MemberData,如果您希望将其分开)可以满足您添加一行数据以运行多个检查的要求,但是我不确定

每个文件只能解析一次,而不是每次测试一次。

无需某种方法记录您在以前的测试运行中解析了哪些文件并将其插入if()语句

即可
public class ExampleTest
{
[Theory]
[InlineData ("somefile", 3, 332, 354)]
[InlineData ("anotherfile", 3, 290, 337)]
[InlineData ("thirdfile", 4, 310, 304)]
public void FileParseOutputIsCorrect ( string fileName, int parameter, int resultA, int resultB )
{
//conditional check only necessary if you want to stop parsing in future test runs
if ( !fileName.Parsed )
{
var fileParseResult = FileParser.ParseFile ( fileName, parameter );
Assert.Equal ( fileParseResult[0], resultA );
Assert.Equal ( fileParseResult[1], resultB );
}
else
{
Console.WriteLine ( $"Already parsed {fileName}" );
}
}
}

由于 Output1 和 Output2 方法非常相似,因此可以使用继承方法。然后,如果需要,您可以应用测试参数插入

public class BaseTest
{
private readonly string fileName;
public BaseTest(string fileName)
{
this.fileName = fileName;
}
[ClassInitialize]
public void Initialize()
{
// do your work with fileName
}
[TestCase]
public void TestOutput1()
{
// test body
}
[TestCase]
public void TestOutput2()
{
// test body
}
}
[TestClass]
public class TestFile1 : BaseTest
{
public TestFile1() : base("file1")
{
}
}
[TestClass]
public class TestFile2 : BaseTest
{
public TestFile2() : base("file2")
{
}
}

最新更新