您当前的位置: 首页 >  驱动开发

C#中的高级测试驱动开发

发布时间:2019-09-10 20:36:39 ,浏览量:6

目录

介绍

起源

目的

开发

调试

遗留代码

示例

假装!

三角测量

多个翻译

反向翻译

文件加载

DictionaryDataSourceTest

DictionaryParserTest

DictionaryLoaderTest

输入依赖关系图

检测结果

覆盖

如何运行源代码?

其他特点

结论

  • 下载源代码 - 13.6 KB
介绍

编写单元测试的传统方法包括编写测试以检查代码的有效性。首先,编写代码然后编写测试。这与测试驱动开发相反。

测试驱动开发(TDD)包括在编写代码之前编写测试,如上面的工作流程所示。

首先,测试是编写的,必须在开始时失败。然后,编写代码以便测试通过。然后,必须执行测试并且必须成功。然后,代码被重构。然后,必须再次执行测试以确保代码正确。

总结一下,这是通过五个步骤完成的:

  1. 写一个测试。
  2. 测试必须在开始时失败。
  3. 编写代码以便测试通过。
  4. 执行测试并确保它通过。
  5. 重构代码。

我们可以注意到,在上面说明的工作流程中,测试是在重构代码之后执行的。这确保了重构后代码仍然正确。

本文将通过一个简单的例子(我们将创建一个双语词典)在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"}}}
        };
    }
}            
关注
打赏
1688896170
查看更多评论

暂无认证

  • 6浏览

    0关注

    105695博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0547s