目录
介绍
起源
目的
开发
调试
遗留代码
示例
假装!
三角测量
多个翻译
反向翻译
文件加载
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{word2, word1}}); } public string[] GetMultipleTranslations(string word) { return new string[]{"contre", "versus"}; } [...] }{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; } }{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; } }{ "contre", "against" }, { "versus", "against" } } } }); var dict = new Dictionary.Dictionary(mockDictionaryParser.Object); CollectionAssert.AreEqual(dict.GetTranslation("against"), new[] { "contre", "versus" }); }{"contre", "against"}, {"versus", "against"}}} }; Assert.IsTrue(dictionaryParser.GetTranslations() .All(kvp1 => expected.ContainsKey(kvp1.Key) && kvp1.Value.All(kvp2 => kvp1.Value.ContainsValue(kvp2.Value)))); }{"contre", "against"}, {"versus", "against"}}} }; } }关注打赏