目录
介绍
起源
目的
开发
调试
遗留代码
示例
假装!
三角测量
多个翻译
反向翻译
文件加载
DictionaryDataSourceTest
DictionaryParserTest
DictionaryLoaderTest
输入依赖关系图
检测结果
覆盖
如何运行源代码?
其他特点
结论
- 下载源代码 - 13.6 KB
编写单元测试的传统方法包括编写测试以检查代码的有效性。首先,编写代码然后编写测试。这与测试驱动开发相反。
测试驱动开发(TDD)包括在编写代码之前编写测试,如上面的工作流程所示。
首先,测试是编写的,必须在开始时失败。然后,编写代码以便测试通过。然后,必须执行测试并且必须成功。然后,代码被重构。然后,必须再次执行测试以确保代码正确。
总结一下,这是通过五个步骤完成的:
- 写一个测试。
- 测试必须在开始时失败。
- 编写代码以便测试通过。
- 执行测试并确保它通过。
- 重构代码。
我们可以注意到,在上面说明的工作流程中,测试是在重构代码之后执行的。这确保了重构后代码仍然正确。
本文将通过一个简单的例子(我们将创建一个双语词典)在C#中显示高级TDD。无论您是新手还是经验丰富的开发人员,本文都将通过一个非常简单的示例向您展示TDD的每一步。
起源TDD基于极限编程的原理之一,也称为XP。这是一种适用于减少团队的计算机项目管理方法。
目的TDD是一种开发方法,其中测试的编写是自动的。这是一种使用一套回归测试来交付软件的非常有效的技术。
TDD在开发快速且频繁生成软件组件的敏捷软件方法中发挥着重要作用。
开发开发以迭代方式完成。
首先,编写测试。第一次测试必须失败。然后,编写代码以便测试通过。然后,再次执行测试并且必须通过。然后,代码被重构。然后,执行测试以确保代码的重构是正确的。这是针对一个单元完成的。然后,对于每个单元,以迭代方式执行相同的过程。
调试
调试对于修复单元或错误很有用。TDD用于纠正错误如下:
- 分析问题。
- 修复错误。
- 运行测试以验证错误已得到修复。
如果仍然出现错误,则必须继续更正,直到所有测试都有效。
遗留代码
它经常发生使用或继续开发现有代码。可以通过以下方式了解现有代码:
- 写下你想要理解的单元的测试。
- 执行测试并确保它失败。
- 调整测试代码直到它通过。
此示例的目的是通过一个简单的例子来描述TDD的每个步骤。
该示例将在C#中开发,使用的测试框架是MSTest。
我们将使用Moq进行模拟,使用JetBrains dotCover进行代码覆盖。
我们将通过TDD创建一个多语言词典。
在编写代码时,我们会尝试尊重SOLID原则。
我们还将尝试达到100%的代码覆盖率。
假装!使用TDD时要实现的第一个任务很重要:它必须如此简单,以便快速完成循环red-green-refactor。
我们首先创建一个名为的DictionaryTest测试类:
[TestClass]
public class DictionaryTest
{
}
然后,我们将在这个类中创建第一个单元测试,我们初始化一个具有"en-fr"名称的Dictionary类型的对象,我们将检查名称是否正确:
[TestClass]
public class DictionaryTest
{
[TestMethod]
public void TestDictionaryName()
{
var dict = new Dictionary("en-fr");
Assert.AreEqual(dict.Name, "en-fr");
}
}
测试将失败,这就是我们想要的。
现在我们得到了红色条,我们将编写代码以便测试通过。要做到这一点,有很多方法。我们将使用“假装”方法。具体而言,它包括通过测试所需的最低限度。在我们的例子中,编写一个返回"en-fr"的属性Name的Dictionary类就足够了:
public class Dictionary
{
public string Name {get{return "en-fr";}}
public Dictionary(string name)
{
}
}
我们将通过在每个步骤中使用简单快速的方法逐步创建代码。
现在,如果我们再次运行我们的单元测试,它将通过。
但是等等,代码没有重构。有一个重复。确实,"en-fr"重复了两次。
我们将重构代码:
public class Dictionary
{
public string Name { get; }
public Dictionary(string name)
{
Name = name;
}
}
在重构代码之后,我们必须再次运行测试以确保代码正确。
代码重构是一种修改代码的形式,它保留了现有测试的执行,并且可以获得具有最少缺陷的软件体系结构。一些例子:
- 删除重复的代码/移动代码。
- 调整私有/公共属性/方法。
我们注意到我们已经完成了TDD工作流程的循环。现在,我们可以通过新测试重新开始新的循环。
已完成的单元测试对已编写的代码提供了一些信心,并允许考虑未来的变化。
三角测量在TDD中,我们首先编写测试,在编码此需求之前生成功能需求。为了完善测试,我们将应用三角测量方法。
让我们编写一个测试,检查翻译是否已添加到字典中(AddTranslation)。
验证将通过GetTranslation方法完成。
[TestMethod]
public void TestOneTranslation()
{
var dict = new Dictionary("en-fr");
dict.AddTranslation("against", "contre");
Assert.AreEqual(dict.GetTranslation("against"), "contre");
}
如果我们运行测试,我们会注意到它会失败。好的,这就是我们在这一步寻找的东西。
首先,我们将使用“假装”方法来通过测试:
public class Dictionary
{
public string Name { get; }
public Dictionary(string name)
{
Name = name;
}
public void AddTranslation(string word1, string word2)
{
}
public string GetTranslation(string word)
{
return "contre";
}
}
运行测试TestOneTranslation后,我们会注意到它会通过。
但等等,有代码重复。关键字"contre"在代码中重复两次。
我们将更改代码以删除此重复:
public class Dictionary
{
private Dictionary _translations;
public string Name { get; }
public Dictionary(string name)
{
_translations = new Dictionary();
Name = name;
}
public void AddTranslation(string word1, string word2){
_translations.Add(word1, word2);
}
public string GetTranslation(string word){
return _translations[word];
}
}
在重构代码之后,我们必须再次运行测试以确保代码正确。
让我们添加一个测试来检查字典是否为空:
[TestMethod]
public void TestIsEmpty1()
{
var dict = new Dictionary.Dictionary("en-fr");
Assert.IsTrue(dict.IsEmpty());
}
如果我们运行测试,我们会注意到它们会失败。好的,这就是我们在这一步寻找的东西。
让我们使用“假装”方法并编写一些代码来通过测试:
public class Dictionary
{
[...]
public bool IsEmpty()
{
return true;
}
}
如果我们进行测试,我们会注意到它会通过。但是等等,代码中有重复。确实,让我们解决这个问题:
public class Dictionary
{
[...]
public bool IsEmpty()
{
return _translations.Count == 0;
}
}
如果我们再次运行测试,我们会注意到它会通过。
我们可以添加另一个单元测试来检查IsEmpty是否正确:
[TestMethod]
public void TestIsEmpty2()
{
var dict = new Dictionary.Dictionary("en-fr");
dict.AddTranslation("against", "contre");
Assert.IsFalse(dict.IsEmpty());
}
如果我们进行测试,我们会注意到它会通过。
多个翻译字典的一个特点是能够操纵多个翻译。这个用例最初并未在我们的架构中进行规划。
我们先写下测试:
[TestMethod]
public void TestMultipleTranslations()
{
var dict = new Dictionary("en-fr");
dict.AddTranslation("against", "contre");
dict.AddTranslation("against", "versus");
CollectionAssert.AreEqual(dict.GetMultipleTranslations("against"),
string[]{"contre", "versus"});
}
如果我们运行测试,我们会注意到它会失败。好的,这就是我们在这一步寻找的东西。
首先,我们将使用“假装”方法通过修改方法AddTranslation和添加GetMultipleTranslations方法来传递测试:
public class Dictionary
{
private readonly Dictionary _translations;
public string Name { get; }
public Dictionary(string name)
{
_translations = new Dictionary();
Name = name;
}
public void AddTranslation(string word1, string word2)
{
_translations.Add(word1, new Dictionary {{word2, word1}});
}
public string[] GetMultipleTranslations(string word)
{
return new string[]{"contre", "versus"};
}
[...]
}
运行测试TestMultipleTranslations后,我们会注意到它会通过。
但等等,有代码重复。字符串数组new string[]{"contre", "versus"}在代码中重复两次。
我们将更改代码以删除此重复:
public class Dictionary
{
private readonly Dictionary _translations;
public string Name { get; }
public Dictionary(string name)
{
_translations = new Dictionary();
Name = name;
}
public void AddTranslation(string word1, string word2)
{
_translations.Add(word1, new Dictionary {{word2, word1}});
}
public string[] GetMultipleTranslations(string word)
{
return _translations[word].Keys.ToArray();
}
public string GetTranslation(string word)
{
return _translations[word][0];
}
public bool IsEmpty(){
return _translations.Count == 0;
}
}
如果我们再次运行测试,我们会注意到它会通过。
我们来做一些重构。让我们通过GetTranslation重命名GetMultipleTranslations:
public class Dictionary
{
private readonly Dictionary _translations;
public string Name { get; }
public Dictionary(string name)
{
_translations = new Dictionary();
Name = name;
}
public void AddTranslation(string word1, string word2)
{
if (!_translations.ContainsKey(word1))
{
_translations.Add(word1, new Dictionary {{word2, word1}});
}
else
{
_translations[word1].Add(word2, word1);
}
}
public string[] GetTranslation(string word)
{
return _translations[word].Keys.ToArray();
}
public bool IsEmpty(){
return _translations.Count == 0;
}
}
我们还必须改变我们的测试:
[TestClass]
public class DictionaryTest{
[TestMethod]
public void TestDictionaryName()
{
var dict = new Dictionary("en-fr");
Assert.AreEqual(dict.Name, "en-fr");
}
[TestMethod]
public void TestOneTranslation()
{
var dict = new Dictionary("en-fr");
dict.AddTranslation("against", "contre");
Assert.AreEqual(dict.GetTranslation("against")[0], "contre");
}
[TestMethod]
public void TestIsEmpty1()
{
var dict = new Dictionary("en-fr");
Assert.IsTrue(dict.IsEmpty());
}
[TestMethod]
public void TestIsEmpty2()
{
var dict = new Dictionary.Dictionary("en-fr");
dict.AddTranslation("against", "contre");
Assert.IsFalse(dict.IsEmpty());
}
[TestMethod]
public void TestMultipleTranslations(){
var dict = new Dictionary("en-fr");
dict.AddTranslation("against", "contre");
dict.AddTranslation("against", "versus");
CollectionAssert.AreEqual(dict.GetTranslation("against"),
new string[]{"contre", "versus"});
}
}
反向翻译
现在假设我们想要考虑两个方向的翻译,例如双语词典。
让我们首先创建测试:
[TestMethod]
public void TestReverseTranslation()
{
var dict = new Dictionary("en-fr");
dict.AddTranslation("against", "contre");
Assert.AreEqual(dict.GetTranslation("contre")[0], "against");
}
如果我们运行测试,我们会注意到它会失败。好的,这就是我们在这一步寻找的东西。
现在,让我们编写代码,以便通过使用“假装”方法传递测试:
public string[] GetTranslation(string word)
{
if(_translations.ContainsKey(word))
{
return _translations[word];
}
else // try reverse translation
{
return "against";
}
}
测试将通过。但是有一个代码重复。确实,"against"重复了两次。所以让我们重构一下代码:
public string[] GetTranslation(string word)
{
if (_translations.ContainsKey(word)) return _translations[word].Keys.ToArray();
return (from t in _translations
from v in t.Value.Values
where t.Value.ContainsKey(word)
select v).Distinct().ToArray();
}
行测试,我们会注意到它会通过。
文件加载现在,我们来处理从数据源(例如外部文本文件)加载翻译。
让我们暂时专注于外部文本文件。输入格式将是一个文本文件,其中第一行包含字典的名称,其他行包含由" = "分隔的单词。
以下是一个例子:
en-fr
against = contre
against = versus
以下是我们将要执行的测试列表:
- 空的文件。
- 仅包含字典名称的文件。
- 带翻译的文件。
- 错误的文件。
首先,我们将使用模拟来编写测试。然后,我们将在此过程中编写代码。然后,我们将重构代码。最后,我们将测试代码以确保重构正确并且一切正常。
我们将创建三个新的测试类:
- DictionaryDataSourceTest:我们将测试从外部数据源加载的字典。
- DictionaryParserTest:我们将测试加载的字典数据的解析。
- DictionaryLoaderTest:我们将测试从外部数据源加载的字典数据的加载。
我们将使用Moq编写测试。
空文件名
首先,让我们编写测试:
[TestMethod]
public void TestEmptyFileName()
{
var mockDictionaryParser = new Mock();
mockDictionaryParser
.Setup(dp => dp.GetName())
.Returns(string.Empty);
var dict = new Dictionary.Dictionary(mockDictionaryParser.Object);
Assert.AreEqual(dict.Name, string.Empty);
}
测试将失败。好的,这就是我们在这一步寻找的东西。
我们将使用接口(IDictionaryParser)来解析从外部数据源加载的字典数据。
以下是接口IDictionaryParser:
public interface IDictionaryParser
{
string GetName();
}
让我们使用“假装”方法修改Dictionary类以通过测试:
public class Dictionary
{
private readonly Dictionary _translations;
public Dictionary(string name)
{
Name = name;
_translations = new Dictionary();
}
public Dictionary(IDictionaryParser dictionaryParser)
{
Name = string.Empty;
}
[...]
}
如果我们再次运行测试,我们会注意到它会通过。但是等等,代码中有一个重复的东西。确实,string.Empty重复了两次。那么,让我们做一些重构:
public class Dictionary
{
private readonly Dictionary _translations;
public Dictionary(string name)
{
Name = name;
_translations = new Dictionary();
}
public Dictionary(IDictionaryParser dictionaryParser)
{
Name = dictionaryParser.GetName();
}
[...]
}
如果我们再次运行测试,我们会注意到它会通过。
没有翻译
首先,让我们从编写测试开始:
[TestMethod]
public void TestEmptyFileTranslations()
{
var mockDictionaryParser = new Mock();
mockDictionaryParser
.Setup(dp => dp.GetTranslations())
.Returns(new Dictionary());
var dict = new Dictionary.Dictionary(mockDictionaryParser.Object);
CollectionAssert.AreEqual(dict.GetTranslation("against"), new string[] { });
}
我们会注意到测试会失败。好的,这就是我们在这一步寻找的东西。
首先,让我们修改接口IDictionaryParser:
public interface IDictionaryParser
{
string GetName();
Dictionary GetTranslations();
}
然后,让我们使用“假装”方法编写一些代码来传递测试:
public class Dictionary
{
private readonly Dictionary _translations;
public Dictionary(string name)
{
Name = name;
_translations = new Dictionary();
}
public Dictionary(IDictionaryParser dictionaryParser)
{
Name = dictionaryParser.GetName();
_translations = new Dictionary();
}
[...]
}
如果我们再次运行测试,我们会注意到它会通过。但是等等,代码中有一个重复的东西。实际上,字典初始化重复两次。那么,让我们做一些重构:
public class Dictionary
{
private readonly Dictionary _translations;
public Dictionary(string name)
{
Name = name;
_translations = new Dictionary();
}
public Dictionary(IDictionaryParser dictionaryParser)
{
Name = dictionaryParser.GetName();
_translations = dictionaryParser.GetTranslations();
}
[...]
}
如果我们再次运行测试,我们会注意到它会通过。
只有字典名称的文件
首先,让我们从编写测试开始:
[TestMethod]
public void TestDictionaryName()
{
var mockDictionaryParser = new Mock();
mockDictionaryParser
.Setup(dp => dp.GetName())
.Returns("en-fr");
var dict = new Dictionary.Dictionary(mockDictionaryParser.Object);
Assert.AreEqual(dict.Name, "en-fr");
}
我们会注意到测试将会通过,因为我们已经编写了接口IDictionaryParser并更改了Dictionary类。目前,该单元不需要重构。
多个翻译
首先,让我们从编写测试开始:
[TestMethod]
public void TestMultipleTranslations()
{
var mockDictionaryParser = new Mock();
mockDictionaryParser
.Setup(dp => dp.GetTranslations())
.Returns(new Dictionary
{
{ "against", new Dictionary{{ "contre", "against" },
{ "versus", "against" } } }
});
var dict = new Dictionary.Dictionary(mockDictionaryParser.Object);
CollectionAssert.AreEqual(dict.GetTranslation("against"), new[] { "contre", "versus" });
}
我们会注意到测试将会通过,因为我们已经编写了接口IDictionaryParser并更改了Dictionary类。目前,该单元不需要重构。
错误的文件
首先,让我们从编写测试开始:
[TestMethod]
public void TestErroneousFile()
{
var mockDictionaryParser = new Mock();
mockDictionaryParser
.Setup(dp => dp.GetTranslations())
.Throws(new DictionaryException("The file is erroneous."));
Assert.ThrowsException(() => new Dictionary.Dictionary(mockDictionaryParser.Object));
}
我们会注意到测试将会通过,因为我们已经编写了接口IDictionaryParser并更改了Dictionary类。目前,该单元不需要重构。
DictionaryParserTest现在让我们通过IDictionaryLoader创建一个类来解析加载的字典数据,该类加载来自外部数据源的字典数据。
空文件名
首先,让我们从编写测试开始:
[TestMethod]
public void TestEmptyFileName()
{
var mockDictionaryLoader = new Mock();
mockDictionaryLoader
.Setup(dl => dl.GetLines())
.Returns(new string[] { });
var dictionaryParser = new DictionaryParser(mockDictionaryLoader.Object);
Assert.AreEqual(dictionaryParser.GetName(), string.Empty);
}
测试将失败。好的,这就是我们在这一步寻找的东西。
我们将使用一个接口从外部数据源(IDictionaryLoader)加载字典数据。
以下是接口IDictionaryLoader:
public interface IDictionaryLoader
{
string[] GetLines();
}
让我们使用“假装”方法编写一些代码来传递测试:
public class DictionaryParser : IDictionaryParser
{
public DictionaryParser(IDictionaryLoader dictionaryLoader)
{
}
public string GetName()
{
return string.Empty;
}
public Dictionary GetTranslations()
{
return new Dictionary();
}
}
测试将通过。让我们转移到其他单元。
没有翻译
让我们首先编写测试:
[TestMethod]
public void TestEmptyFileTranslations()
{
var mockDictionaryLoader = new Mock();
mockDictionaryLoader
.Setup(dl => dl.GetLines())
.Returns(new string[] { });
var dictionaryParser = new DictionaryParser(mockDictionaryLoader.Object);
CollectionAssert.AreEqual(dictionaryParser.GetTranslations(), new Dictionary());
}
测试将通过。让我们转移到其他单元。
字典名称
让我们首先编写测试:
[TestMethod]
public void TestDictionaryName()
{
var mockDictionaryLoader = new Mock();
mockDictionaryLoader
.Setup(dl => dl.GetLines())
.Returns(new string[] { "en-fr" });
var dictionaryParser = new DictionaryParser(mockDictionaryLoader.Object);
Assert.AreEqual(dictionaryParser.GetName(), "en-fr");
}
测试将失败。好的,这就是我们在这一步寻找的东西。
让我们使用“假装”方法编写一些代码以通过测试:
public class DictionaryParser : IDictionaryParser
{
public DictionaryParser(IDictionaryLoader dictionaryLoader)
{
}
public string GetName()
{
return "en-fr";
}
public Dictionary GetTranslations()
{
return new Dictionary();
}
}
测试将通过。但等等,代码中存在重复,测试TestEmptyFileName失败。所以让我们解决这个问题:
public class DictionaryParser : IDictionaryParser
{
private readonly string[] _lines;
public DictionaryParser(IDictionaryLoader dictionaryLoader)
{
_lines = dictionaryLoader.GetLines();
}
public string GetName()
{
if (_lines.Length > 0) return _lines[0];
return string.Empty;
}
public Dictionary GetTranslations()
{
return new Dictionary();
}
}
现在测试将通过。
多个翻译
让我们首先编写测试:
[TestMethod]
public void TestMultipleTranslations()
{
var mockDictionaryLoader = new Mock();
mockDictionaryLoader
.Setup(dl => dl.GetLines())
.Returns(new[] { "en-fr", "against = contre", "against = versus" });
var dictionaryParser = new DictionaryParser(mockDictionaryLoader.Object);
var expected = new Dictionary
{
{"against", new Dictionary {{"contre", "against"},
{"versus", "against"}}}
};
Assert.IsTrue(dictionaryParser.GetTranslations()
.All(kvp1 =>
expected.ContainsKey(kvp1.Key)
&& kvp1.Value.All(kvp2 => kvp1.Value.ContainsValue(kvp2.Value))));
}
测试将失败。好的,这就是我们在这一步寻找的东西。
让我们使用“假装”方法编写一些代码以通过测试:
public class DictionaryParser : IDictionaryParser
{
private readonly string[] _lines;
public DictionaryParser(IDictionaryLoader dictionaryLoader)
{
_lines = dictionaryLoader.GetLines();
}
public string GetName()
{
if (_lines.Length > 0) return _lines[0];
return string.Empty;
}
public Dictionary GetTranslations()
{
return new Dictionary
{
{"against", new Dictionary {{"contre", "against"},
{"versus", "against"}}}
};
}
}
测试将通过。但等等,代码中存在重复,测试TestEmptyFileTranslations失败。所以让我们解决这个问题:
public class DictionaryParser : IDictionaryParser
{
private readonly string[] _lines;
public DictionaryParser(IDictionaryLoader dictionaryLoader)
{
_lines = dictionaryLoader.GetLines();
}
public string GetName()
{
if (_lines.Length > 0) return _lines[0];
return string.Empty;
}
public Dictionary GetTranslations()
{
var dict = new Dictionary();
if (_lines.Length > 1)
{
for (var i = 1; i < _lines.Length; i++)
{
var l = _lines[i];
var regex = new Regex(@"^(?\w+) \= (?\w+)$");
var match = regex.Match(l);
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
if (!dict.ContainsKey(key))
{
dict.Add(key, new Dictionary { { val, key } });
}
else
{
dict[key].Add(val, key);
}
}
}
return dict;
}
}
现在测试将通过。方法GetTranslations只是解析IDictionaryLoader加载的行。
错误的文件
我们尚未实现的功能之一是处理错误文件的加载。这个用例最初并未在我们的架构中进行规划。
让我们首先编写测试:
[TestMethod]
public void TestErroneousFile()
{
var mockDictionaryLoader = new Mock();
mockDictionaryLoader
.Setup(dl => dl.GetLines())
.Returns(new[] { "en-fr", "against = ", "against = " });
var dictionaryParser = new DictionaryParser(mockDictionaryLoader.Object);
Assert.ThrowsException(() => dictionaryParser.GetTranslations());
}
测试将失败。好的,这就是我们在这一步寻找的东西。
让我们编辑代码来通过测试:
public class DictionaryParser : IDictionaryParser
{
private readonly string[] _lines;
public DictionaryParser(IDictionaryLoader dictionaryLoader)
{
_lines = dictionaryLoader.GetLines();
}
public string GetName()
{
if (_lines.Length > 0) return _lines[0];
return string.Empty;
}
public Dictionary GetTranslations()
{
var dict = new Dictionary();
if (_lines.Length > 1)
{
for (var i = 1; i < _lines.Length; i++)
{
var l = _lines[i];
var regex = new Regex(@"^(?\w+) \= (?\w+)$");
var match = regex.Match(l);
if (!match.Success)
{
throw new DictionaryException("The file is erroneous.");
}
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
if (!dict.ContainsKey(key))
{
dict.Add(key, new Dictionary { { val, key } });
}
else
{
dict[key].Add(val, key);
}
}
}
return dict;
}
}
现在,我们会注意到测试将通过。
DictionaryLoaderTest在本节中,我们将创建一个从外部文件加载字典数据的类。
空的文件
让我们从创建测试空文件的第一个测试开始:
[TestMethod]
public void TestEmptyFile()
{
var dictionaryLoader = new DictionaryLoader("dict-empty.txt");
CollectionAssert.AreEqual(dictionaryLoader.GetLines(), new string[] { });
}
测试将失败。好的,这就是我们在这一步寻找的东西。
现在,让我们使用“假装”方法编写一些代码来传递测试:
public class DictionaryLoader : IDictionaryLoader
{
private readonly string _dictionaryPath;
public DictionaryLoader(string dictionaryPath)
{
_dictionaryPath = dictionaryPath;
}
public string[] GetLines()
{
return new string[] { };
}
}
现在测试将通过。但是等等,有代码重复。实际上,new string[] { }在代码中重复了两次。那么,让我们做一些重构:
public class DictionaryLoader : IDictionaryLoader
{
private readonly string _dictionaryPath;
public DictionaryLoader(string dictionaryPath)
{
_dictionaryPath = dictionaryPath;
}
public string[] GetLines()
{
return File.ReadAllLines(_dictionaryPath);
}
}
现在,如果我们再次运行测试,我们会发现它会通过。
只有字典名称的文件
现在,让我们使用以下文本文件(dict-name.txt):
en-fr
让我们从编写测试开始:
[TestMethod]
public void TestDictionaryName()
{
var dictionaryLoader = new DictionaryLoader("dict-name.txt");
CollectionAssert.AreEqual(dictionaryLoader.GetLines(), new[] { "en-fr" });
}
测试将通过,因为我们在上一个测试中实现了类DictionaryLoader。
让我们转移到其他单元。
带翻译的文件
现在,让我们使用以下字典文件(dict.txt):
en-fr
against = contre
against = versus
首先,让我们从编写测试开始:
[TestMethod]
public void TestMultipleTranslations()
{
var dictionaryLoader = new DictionaryLoader("dict.txt");
CollectionAssert.AreEqual(dictionaryLoader.GetLines(), new[] { "en-fr", "against = contre", "against = versus" });
}
同样,测试将通过,因为我们在之前的测试中实现了DictionaryLoader类。
错误的文件
现在,让我们使用以下字典文件(dict-erroneous.txt):
en-fr
against =
against =
让我们首先编写测试:
[TestMethod]
public void TestErroneousFile()
{
var dictionaryLoader = new DictionaryLoader("dict-erroneous.txt");
CollectionAssert.AreEqual(dictionaryLoader.GetLines(), new[] { "en-fr", "against = ", "against = " });
}
同样,测试将通过,因为我们在之前的测试中实现了DictionaryLoader类。
我们完成了测试并创建了负责从外部文件加载字典数据的DictionaryLoader类。
输入依赖关系图下面是类型依赖关系图:
以下是类图:
我们将注意到所有SOLID原则都得到了尊重,并且代码是可维护的,灵活的和可扩展的。
检测结果如果我们运行所有测试,我们会注意到它们都通过了:
我们会注意到所有的单元测试都已经编写完成。这是TDD的优势之一。
覆盖以下是使用JetBrains dotCover的代码覆盖率:
我们会注意到我们达到了100%的代码覆盖率。这是TDD的优势之一。
如何运行源代码?要运行源代码,请执行以下操作:
- 从本文顶部下载源代码。
- 通过以下命令恢复nuget包:nuget restore tdd.sln
- 在Visual Studio 2019中打开tdd.sln。
- 从Visual Studio 2019构建解决方案。
- 运行解决方案中的所有单元测试。
- 要获得代码覆盖率,请下载并安装JetBrains dotCover,然后在Visual Studio 2019中打开解决方案tdd.sln,然后右键单击DictionaryTest项目,最后单击“Cover Unit Tests”。
另一个特点是在Dictionary类中的修改方法AddTranslation,它为了更新字典的外部文本文件。
最后,另一个特点是从数据库中读取翻译并将翻译写入数据库。
结论本文通过一个简单的例子展示了C#中的高级TDD。
我们可以通过TDD注意到:
- 单元测试是编写的。
- 我们达到了100%的代码覆盖率。
- 我们花在调试上的时间更少。
- 该代码尊重SOLID原则。
- 代码是可维护的、灵活的和可扩展的。
- 代码更加连贯。
- 行为清晰。
- 代码更强壮。
- 没有回归。
当代码不断改进时,TDD非常有用。
TDD提供了其他好处,因为开发人员从小单元的角度考虑软件,这些小单元可以独立地编写和测试,并在以后集成在一起。