依赖于 File class 和其他方法的测试方法
Testing method with dependency on File class and other methods
所以我创建了一个 CsvParser 来读取文件的内容并解析该文件的每个 line/row。我有以下逻辑:
public interface ICsvReader {
List<string> ReadFromFile(string path);
}
public class CsvReader : ICsvReader
{
private readonly ICsvParser _csvParser;
public CsvReader(ICsvParser csvParser)
{
_csvParser = csvParser;
}
public List<string> ReadFromFile(string path)
{
if (!File.Exists(path)) return null;
var content = File.ReadAllLines(path);
return ParseContent(content);
}
private List<string> ParseContent(StreamReader input)
{
List<string> rows = new List<string>();
foreach (var line in lines) {
var updatedRow = _csvParser.ParseRow(line);
lines.Add(updatedRow);
}
return rows;
}
}
public interface ICsvParser
{
string ParseRow(string row);
}
public CsvParser : ICsvParser
{
public string ParseRow(string row)
{
// Performing parsing here returning parsed row
}
}
这可行,但需要为其编写测试。这是我遇到问题并且对如何处理这个问题感到困惑的地方。首先,我的代码耦合/依赖于文件 class,因此在测试中它肯定会尝试使用不理想的真实数据。我正在考虑使用模拟来传递虚假数据,但不确定它是如何工作的。我不太熟悉最小起订量,但我假设我想创建一个模拟以将测试数据传递到 ReadFromFile 并检查预期结果是否正确?
其次,我使用依赖注入来消除 CsvReader 和 CsvParser 之间的依赖,特别是 ParseRow 方法。我知道我可以使用最小起订量来模拟接口 + 方法,但是由于在 foreach 循环中调用了 PraseRow,这将如何影响设置?换句话说,每次调用时 return 值都会不同?
Firstly my code is coupled/ dependant on the File class
作为一般规则,尝试编写可以将 stream 作为输入的 API。你可以有一个额外的方法来获取文件路径,打开文件并将流转发到另一个方法。但这主要是为了方便。当您有内存流或其他数据源并被迫将其保存到临时文件时,仅采用文件路径的 API 非常烦人。
如果采用文件路径的方法包含的逻辑很少,那么对其进行单元测试的好处就会减少。如果它只打开一个文件,我只需要代码审查就可以满足。并非每一行代码都需要进行单元测试。或者更确切地说,benefit/cost 单元测试的比率根据它是什么类型的代码而变化很大。
surely in testing it will try to use real data which is not ideal
我不认为使用真实数据的单元测试一定不好。当然,如果数据量很小,那么简单地使用硬编码字符串或字节数组作为测试数据的来源可能会更好。但在您的测试项目中添加一个更大更复杂的文件作为内容文件并使用该文件编写测试也可能很有用。这会稍微减慢单元测试的速度。如果这是个问题,解决方案可以是将使用 IO 的测试放在 运行 频率较低的特殊类别中。
I know I can use moq to mock the interface + method however since PraseRow is being called in a foreach loop how will this affect the setup?
如果需要,您仍然可以同时测试这两个 类。对于像这样的 类,我可能会认为它们的耦合度如此之高,以至于单独测试它们不会增加太多好处。 IE。您应该考虑单独测试它们的好处是否超过额外成本。
In other words the return value will be different each time it is called?
由你决定。编写一个每次 return 具有相同值的模拟更容易。但是如果你愿意,应该完全可以从列表中 return 行。
所以我创建了一个 CsvParser 来读取文件的内容并解析该文件的每个 line/row。我有以下逻辑:
public interface ICsvReader {
List<string> ReadFromFile(string path);
}
public class CsvReader : ICsvReader
{
private readonly ICsvParser _csvParser;
public CsvReader(ICsvParser csvParser)
{
_csvParser = csvParser;
}
public List<string> ReadFromFile(string path)
{
if (!File.Exists(path)) return null;
var content = File.ReadAllLines(path);
return ParseContent(content);
}
private List<string> ParseContent(StreamReader input)
{
List<string> rows = new List<string>();
foreach (var line in lines) {
var updatedRow = _csvParser.ParseRow(line);
lines.Add(updatedRow);
}
return rows;
}
}
public interface ICsvParser
{
string ParseRow(string row);
}
public CsvParser : ICsvParser
{
public string ParseRow(string row)
{
// Performing parsing here returning parsed row
}
}
这可行,但需要为其编写测试。这是我遇到问题并且对如何处理这个问题感到困惑的地方。首先,我的代码耦合/依赖于文件 class,因此在测试中它肯定会尝试使用不理想的真实数据。我正在考虑使用模拟来传递虚假数据,但不确定它是如何工作的。我不太熟悉最小起订量,但我假设我想创建一个模拟以将测试数据传递到 ReadFromFile 并检查预期结果是否正确?
其次,我使用依赖注入来消除 CsvReader 和 CsvParser 之间的依赖,特别是 ParseRow 方法。我知道我可以使用最小起订量来模拟接口 + 方法,但是由于在 foreach 循环中调用了 PraseRow,这将如何影响设置?换句话说,每次调用时 return 值都会不同?
Firstly my code is coupled/ dependant on the File class
作为一般规则,尝试编写可以将 stream 作为输入的 API。你可以有一个额外的方法来获取文件路径,打开文件并将流转发到另一个方法。但这主要是为了方便。当您有内存流或其他数据源并被迫将其保存到临时文件时,仅采用文件路径的 API 非常烦人。
如果采用文件路径的方法包含的逻辑很少,那么对其进行单元测试的好处就会减少。如果它只打开一个文件,我只需要代码审查就可以满足。并非每一行代码都需要进行单元测试。或者更确切地说,benefit/cost 单元测试的比率根据它是什么类型的代码而变化很大。
surely in testing it will try to use real data which is not ideal
我不认为使用真实数据的单元测试一定不好。当然,如果数据量很小,那么简单地使用硬编码字符串或字节数组作为测试数据的来源可能会更好。但在您的测试项目中添加一个更大更复杂的文件作为内容文件并使用该文件编写测试也可能很有用。这会稍微减慢单元测试的速度。如果这是个问题,解决方案可以是将使用 IO 的测试放在 运行 频率较低的特殊类别中。
I know I can use moq to mock the interface + method however since PraseRow is being called in a foreach loop how will this affect the setup?
如果需要,您仍然可以同时测试这两个 类。对于像这样的 类,我可能会认为它们的耦合度如此之高,以至于单独测试它们不会增加太多好处。 IE。您应该考虑单独测试它们的好处是否超过额外成本。
In other words the return value will be different each time it is called?
由你决定。编写一个每次 return 具有相同值的模拟更容易。但是如果你愿意,应该完全可以从列表中 return 行。