行为驱动开发(BDD) - 深入了解

行为驱动开发(BDD) - 一个快速的描述和示例

BDD表示乙 ehavior e里文e才有发展。用于描述行为的语法是Gherkin。

这个想法是尽可能自然地描述一种语言应该发生什么。

如果你熟悉单元测试,并且很容易编写单元测试,那么你熟悉它们的阅读方式。根据测试需要覆盖的程度,可以很难弄清楚它的作用,因为毕竟只是代码。

只有开发人员才能真正了解那里发生了什么。

BDD以不同的方式解决问题。

让我们来隐藏代码,开始一个对话,所以现在任何人都可以阅读一个场景并了解它的测试。

举一个例子:

给定第一个数字4

和第二个数字3

当添加两个数字时

那么结果是7

这里没有代码。这种情况可以像故事一样阅读。我们可以将它交给业务分析师,以确保我们正在处理正确的事情,或者将其提供给测试人员,或者稍后重新审视,并重新记住事情需要如何工作,为什么我们建立一定的事情办法。

您是否跟上新的开发人员技术?通过我们的免费开发者杂志,涵盖C#,模式,.NET Core,MVC,Azure,Angular,React等等,提升您的IT职业生涯。免费订阅我们的杂志,并下载所有以前的,即将到来的版本。

我们在这里描述一些行为,在这种情况下,它可能是一个数学运算子系统,我们已经明确定义了该系统的一个行为。当然,更多的测试将被写入以覆盖整个行为并处理边缘情况。

如果这一切都开始听起来像写单元测试,那就是一件好事。

BDD和单元测试在某些方面是相似的,不妨碍开发人员使用这两者,如果这是合适的。

使用Gherkin语法可以很容易地解释什么是以自然语言进行测试,甚至非开发人员可以阅读和理解。

例如,质量保证人员或业务分析师可以复制和粘贴此类测试,更改数字并提供自己的测试用例,而无需编写任何代码,甚至不会看到代码。

如果您对细节感兴趣,这是一个非常好的写在小黄瓜:https//github.com/cucumber/cucumber/wiki/Gherkin

现在我们进行了测试,从这里开始如何工作?

测试中的每一行都称为步骤,每一步都将成为一个单独的方法,每个方法都按照写入的顺序进行调用。

在我们的示例中,前两行(给定And)将设置初始数据,“ 什么时候”将会调用我们要测试的方法,然后Then将发生assert。

由于每个步骤是一个单独的方法,希望现在很明显,我们需要能够在步骤之间共享一些状态

不要担心,这不是你想象的状态,它不会破坏任何测试原则,特别是说测试不应该改变状态或者应该依赖于另一个测试创建的状态。

这只是意味着每个测试需要能够拥有自己的状态,并且该测试中的每个步骤需要该状态。

Specflow给了我们一个ScenarioContext,它只是一个字典,用于存储执行测试所需的数据。此上下文在测试结束时被清除,并在下一次测试运行时再次为空。

以每个步骤为单独的方法,这里要考虑的最后一点是可以在多个测试之间重复使用该步骤。看看我们的测试示例中的前两个步骤。如果我们将该数字作为输入参数传递给这个步骤方法,我们可以重用它,无论我们重新使用这些步骤。

测试看起来更像这样:

给定第一个数字{parameter1}

第二个数字{parameter2}

当添加两个数字时

那么结果是{预期结果}

现在这是更通用的,希望能够清楚地显示出每个步骤的可重用性。我们不必在每次测试中使用相同的步骤,甚至不需要按照相同的顺序!稍后我们来看一下这个。

随着我们不断添加测试,我们编写的实际代码变得越来越小,因为对于我们正在测试的每个系统行为,我们将了解到我们只是重新使用已经编码的现有步骤。

所以即使我们花了一点时间最初写测试代码; 随着我们的进步,最终花费在写入额外步骤的时间减少到零。

用于BDD的软件

我们需要看看哪些工具可以帮助我们充分利用BDD的全部功能。本文是从后端的角度来看的,但是还有纯粹的前端工作的替代方法,但是在本文中将不再讨论。

我会用:

  • Visual Studio 2017(bit.ly/dnc-vs-download
  • Specflow - Visual Studio扩展 - 这将有助于Gherkin语法和测试与步骤代码之间的链接。
  • NUnit - 用于断言。你可以在这里使用别的东西, FluentAssertions也是一样。

有一个NuGet软件包安装了Specflow和NUnit,我会使用一个,因为它使事情变得更容易。

所以,首先安装Visual Studio Specflow扩展。这将给我们提供文件模板和语法着色。

Specflow

Specflow Visual Studio扩展将允许您创建功能文件。这些文件是测试场景的占位符。

该扩展还为功能文件添加了语法着色,这是一个很好的视觉指示器,您所做的工作以及您仍然需要做什么。它在每个测试场景的步骤和它们之后的测试方法之间创建一个连接,这是非常方便的,特别是当你有很多功能文件和大量的测试。

一旦创建了一个特征文件,它将如下所示:

功能文本描述了问题。

该场景基本上是一个测试,我们可以在一个功能文件中有多个场景。

标签在测试资源管理器窗口中使用,它允许我们以合乎逻辑的方式对测试进行分组。我们的初步测试可能如下所示:

请注意如何删除对UI元素的引用。这可以追溯到最初所说的 - 专注于功能,以及在做某些事情的核心部分; 不是如何显示事物和在哪里。

在Visual Studio解决方案中,我们仍然需要使用NuGet软件包SpecFlow.NUnit安装Specflow和NUnit:

我创建了一个MathLib类库并添加了这个NuGet包。

一旦我们安装了所有这些软件包,打开测试资源管理器窗口,构建解决方案,你应该看到以下内容:

我被Traits过滤,然后显示我们创建的标签。我使用了两个,MathLib显示库中的所有测试(Add,Divide等),但是我可以通过Math操作以及Add标签下的组合来看到它们。这只是个人喜好。标签可以是一种非常有效的方法,以对您有意义的方式对测试进行分组。

所以现在我们有一个功能文件,还有一个测试,但是我们还没有写任何测试代码。

我们接下来需要的是一个步骤代码文件,我们所有的测试步骤都可以进行。我们将从一个文件开始,但是我们可以将步骤分成多个步骤文件,以避免在一个文件中存在太多的代码。

如果你再看看我们的测试,你会看到这些步骤是紫色的。这是一个视觉指标,没有代码。

我们创建一个步骤代码文件,它只是一个标准的C#文件。

代码如下所示:

using TechTalk.SpecFlow;

namespace MathLibTests

{

    [Binding]

    public sealed class Steps

    {

    }

}

我们唯一添加的是Binding属性在类的顶部。这是一个Specflow属性,它使此文件中的所有步骤都可用于此项目中的任何功能文件,无论它们位于何处。

现在,返回功能文件,右键单击任何步骤,您将在上下文菜单中看到“ 生成步骤定义”选项:

单击生成步骤定义选项,然后将方法复制到剪贴板:

注意四个步骤如何显示在窗口中。将为其中每一个生成代码。

现在只需将代码粘贴到前面创建的步骤文件中:

using TechTalk.SpecFlow;

namespace MathLibTests

{

    [Binding]

    public sealed class Steps

    {

        [Given(@"a first number (.*)")]

        public void GivenAFirstNumber(int p0)

        {

            ScenarioContext.Current.Pending();

        }

        [Given(@"a second number (.*)")]

        public void GivenASecondNumber(int p0)

        {

            ScenarioContext.Current.Pending();

        }

        [When(@"the two numbers are added")]

        public void WhenTheTwoNumbersAreAdded()

        {

            ScenarioContext.Current.Pending();

        }

        [Then(@"the result should be (.*)")]

        public void ThenTheResultShouldBe(int p0)

        {

            ScenarioContext.Current.Pending();

        }

    }

}

保存文件,然后再次查看功能文件。我们最初的情景,其中有紫色的所有步骤,现在看起来像这样:

注意颜色如何变成黑色,数字是斜体的,这意味着它们被视为参数。为了使代码更清晰一些,我们来改一下一下:

using TechTalk.SpecFlow;

namespace MathLibTests

{

    [Binding]

    public sealed class Steps

    {

        [Given(@"a first number (.*)")]

        public void GivenAFirstNumber(int firstNumber)

        {

            ScenarioContext.Current.Pending();

        }

        [Given(@"a second number (.*)")]

        public void GivenASecondNumber(int secondNumber)

        {

            ScenarioContext.Current.Pending();

        }

        [When(@"the two numbers are added")]

        public void WhenTheTwoNumbersAreAdded()

        {

            ScenarioContext.Current.Pending();

        }

        [Then(@"the result should be (.*)")]

        public void ThenTheResultShouldBe(int expectedResult)

        {

            ScenarioContext.Current.Pending();

        }

    }

}

在这一点上,我们有步骤,我们有起点,我们可以添加一些有意义的代码。

我们添加实际的数学库,这是我们实际测试的数学库。

创建一个类库,使用Add()方法添加一个MathLibOps类:

using System;

namespace MathLib

{

    public sealed class MathLibOps

    {

        public int Add(int firstNumber, int secondNumber)

        {

            throw new NotImplementedException();

        }

    }

}

现在让我们写出足够的测试代码来进行测试。

我们再来看一下“步骤”文件。注意所有这些ScenarioContext.Current.Pending()行在每一步?这是我们以前谈论的语境。这就是我们所有需要的数据。将其视为字典,带有键/值对。关键将用于检索正确的数据,所以我们将给出一些有意义的价值观,使我们的生活更轻松。

using MathLib;

using NUnit.Framework;

using TechTalk.SpecFlow;

namespace MathLibTests

{

    [Binding]

    public sealed class Steps

    {

        [Given(@"a first number (.*)")]

        public void GivenAFirstNumber(int firstNumber)

        {

            ScenarioContext.Current.Add("FirstNumber", firstNumber);

        }

        [Given(@"a second number (.*)")]

        public void GivenASecondNumber(int secondNumber)

        {

            ScenarioContext.Current.Add("SecondNumber", secondNumber);

        }

        [When(@"the two numbers are added")]

        public void WhenTheTwoNumbersAreAdded()

        {

            var firstNumber = (int)ScenarioContext.Current["FirstNumber"];

            var secondNumber = (int)ScenarioContext.Current["SecondNumber"];

            var mathLibOps = new MathLibOps();

            var addResult = mathLibOps.Add(firstNumber, secondNumber);

            ScenarioContext.Current.Add("AddResult", addResult);

        }

        [Then(@"the result should be (.*)")]

        public void ThenTheResultShouldBe(int expectedResult)

        {

            var addResult = (int)ScenarioContext.Current["AddResult"];

            Assert.AreEqual(expectedResult, addResult);

        }

    }

}

看看前两个给定方法,注意我们如何将参数传递给方法,然后将其添加到具有清除键的上下文中,以便我们知道它们代表什么。

步骤从上下文使用这两个值,实例化Math类和与这两个数调用Add()方法,然后将其结果存储回的上下文。

最后,Then步骤从特征文件获取预期结果,并将其与存储在上下文中的结果进行比较。当然,当我们运行测试时,我们会失败,因为我们没有正确的代码。

要运行测试,请在“测试资源管理器”窗口中右键单击该测试,并使用“运行所选测试”选项:

结果将如下所示:

结果是如预期的,所以现在让我们修复lib代码并使其通过:

namespace MathLib

{

    public sealed class MathLibOps

    {

        public int Add(int firstNumber, int secondNumber)

        {

            return firstNumber + secondNumber;

        }

    }

}

现在让我们再试一次,我们应该看到一些更开朗的东西:

很酷,所以在这一点上,我们应该相当熟悉它们如何挂在一起。

现在最大的问题是:

好的,这是非常好的,但是这与单元测试有什么不同,它实际提供了什么价值?我得到什么

我有一个功能文件,这很好,我想,但我可以很容易地写一个单元测试,并完成它。业务分析师不会关心我的基本添加两个数字的事情。

所以,我们来看看我们如何实现一些更复杂的东西。

Specflow有更多的功能,我们只是碰到了几个。一个非常好的功能是能够处理数据表。当数据不像数字那么简单时,这很重要。例如,假设你有一个具有五个属性的对象,这将使它更难处理,因为我们现在需要五个参数,而不是一个。

所以让我们来一个比较严肃的项目,让我们为一个网站实现一个Access框架,而这个Access Framework会告诉我们一个用户是否可以在我们的网站上执行各种操作。

一个复杂的问题描述

我们有一个网站,人们可以访问,然后搜索和申请工作。限制将根据其成员类型适用。

会员类型(白金,金,银,免费)

铂金可以搜索50次/天,每天50次。

黄金可以每天搜索15次,每天可以申请15个工作

银可以每天搜索10次,每天可以申请10个工作

免费可以搜索5次/天,并适用于1个工作/天。

我们需要的

我们需要定义用户

2.我们需要定义会员类型

3.我们需要定义每个会员类型的限制

4.我们需要一种方法来检索用户每天所做的搜索和应用程序。

前三个是配置,最后一个是用户数据。我们可以用它来定义与系统交互的方式。

实际代码

我们创建一个类来表示成员资格类型。它可能看起来像这样:

namespace Models

{

    public sealed class MembershipTypeModel

    {

        public string MembershipTypeName { get; set; }

        public RestrictionModel Restriction { get; set; }

    }

}

该RestrictionModel类包含每天最大搜索,每天最大的应用:

namespace Models

{

    public sealed class RestrictionModel

    {

        public int MaxSearchesPerDay { get; set; }

        public int MaxApplicationsPerDay { get; set; }

    }

}

接下来,我们要一个UserModel,它将保存用户需要的数据:

namespace Models

{

    public sealed class UserModel

    {

        public int ID { get; set; }

        public string Username { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string MembershipTypeName { get;set; }

        public UserUsageModel CurrentUsage { get; set; }

    }

}

该UserUsageModel将告诉我们有多少搜索和应用的用户已经完成的那一天:

namespace Models

{

    public sealed class UserUsageModel

    {

        public int CurrentSearchesCount { get; set; }

        public int CurrentApplicationsCount { get; set; }

    }

}

最后,我们需要一个能够保存AccessFramework调用结果的类:

namespace Models

{

    public sealed class AccessResultModel

    {

        public bool CanSearch { get; set; }

        public bool CanApply { get; set; }

    }

}

正如你所看到的,我保持这样很简单,我们不想迷失实施细节。

我们确实希望了解BDD如何帮助我们解决不仅仅是Hello World应用程序的问题。

所以现在我们有了我们的模型,我们来创建几个接口,这些将负责数据检索部分。

首先,处理通用配置数据的一个:

using Models;

using System.Collections.Generic;

namespace Core

{

    public interface IConfigurationRetrieval

    {

        List<MembershipTypeModel> RetrieveMembershipTypes();

    }

}

第二个处理用户特定的数据:

using Models;

namespace Core

{

    public interface IUserDataRetrieval

    {

        UserModel RetrieveUserDetails(string username);

    }

}

这两个接口将成为AccessFrameworkAnalyser类的参数,它们将允许我们模拟测试所需的数据:

using Core;

using Models;

using System;

using System.Linq;

namespace AccessFramework

{

    public sealed class AccessFrameworkAnalyser

    {

        IConfigurationRetrieval _configurationRetrieval;

        IUserDataRetrieval _userDataRetrieval;      

        public AccessFrameworkAnalyser(IConfigurationRetrieval configurationRetrieval, IUserDataRetrieval userDataRetrieval)

        {

            if ( configurationRetrieval == null || userDataRetrieval == null)

            {

                throw new ArgumentNullException();

            }

            this._configurationRetrieval = configurationRetrieval;

            this._userDataRetrieval = userDataRetrieval;

        }

        public AccessResultModel DetermineAccessResults(string username)

        {           

            if ( string.IsNullOrWhiteSpace(username))

            {

                throw new ArgumentNullException();

            }

            var userData = this._userDataRetrieval.RetrieveUserDetails(username);

            var membershipTypes = this._configurationRetrieval.RetrieveMembershipTypes();

            var userMembership = membershipTypes.FirstOrDefault(p => p.MembershipTypeName.Equals(userData.MembershipTypeName, StringComparison.OrdinalIgnoreCase));           

            var result = new AccessResultModel();

            if (userMembership != null)

            {

                result.CanApply = userData.CurrentUsage.CurrentApplicationsCount < userMembership.Restriction.MaxApplicationsPerDay ? true : false;

                result.CanSearch = userData.CurrentUsage.CurrentSearchesCount < userMembership.Restriction.MaxSearchesPerDay ? true : false;

            }

            return result;

        }

    }

}

我们这里做的不多 我们简单地在我们的两个接口中使用依赖注入,然后根据当前的搜索和应用程序,比较有多少个搜索和应用程序可用于所选用户的成员资格类型。

请注意,我们并不关心这个数据是如何实际加载的,通常会有一个实现到每个接口到一个数据库,但是在这个例子中,我们并不在乎。

我们需要知道的是,我们将有一种获取数据的方法,并且可能会在实际的UI项目中使用某种类型的IOC来连接实际的实现,这需要真正的数据。既然我们真的不在乎这一点,我们就不会实现它,我们将简单的展示一些所需的测试。

我们的功能文件可能如下所示:

管道表示处理表格数据的Specflow方法。

第一行包含标题,后面的行包含数据。重要的是要注意我们设置了多少数据,以及它们的可读性。事情变得更简单,因为这里没有代码,没有隐藏实际的数据。在这一点上,我们可以简单地复制和粘贴测试,更改数据,并再次准备就绪。

关键是非开发人员也可以做到这一点。

我们来看看第一种情况。

您可以看到,首先我们设置我们要使用的会员类型。记住我们不关心真实数据,我们关心这里的功能和业务规则,这就是我们正在测试的。这使得我们很容易以任何我们喜欢的方式设置数据。

第二步设置用户及其现有的搜索和应用程序数量。

最后,当使用AccessFrameworkAnalyser类时,我们期待一定的结果。

这里有一些重要的事情要提及。

我们如何在步骤代码中加载表格数据?

以下是加载成员资格数据的示例:

private List<MembershipTypeModel> GetMembershipTypeModelsFromTable(Table table)

{

    var results = new List<MembershipTypeModel>();

    foreach ( var row in table.Rows)

    {

        var model = new MembershipTypeModel();

        model.Restriction = new RestrictionModel();

        model.MembershipTypeName = row.ContainsKey("MembershipTypeName") ? row["MembershipTypeName"] : string.Empty;

        if (row.ContainsKey("MaxSearchesPerDay"))

        {

            int maxSearchesPerDay = 0;

            if (int.TryParse(row["MaxSearchesPerDay"], out maxSearchesPerDay))

            {

                model.Restriction.MaxSearchesPerDay = maxSearchesPerDay;

            }

        }

        if (row.ContainsKey("MaxApplicationsPerDay"))

        {

            int maxApplicationsPerDay = 0;

            if (int.TryParse(row["MaxApplicationsPerDay"], out maxApplicationsPerDay))

            {

                model.Restriction.MaxApplicationsPerDay = maxApplicationsPerDay;

            }

        }

        results.Add(model);

    }

    return results;

}

在尝试加载任何东西之前,始终检查一个标题是否存在是个好主意。这是非常有用的,因为根据您正在构建的内容,您并不总是同时需要所有的属性和对象。您可能只需要几个属性进行一些特定测试,在这种情况下,您不需要充满数据的表。你只需使用你需要的,忽略其余的,一切仍然有效。

现在加载会员类型的实际步骤变得非常简单:

[Given(@"the membership types")]

public void GivenTheMembershipTypes(Table table)

{

    var membershipTypes = this.GetMembershipTypeModelsFromTable(table);

    ScenarioContext.Current.Add("MembershipTypes", membershipTypes);

}

这就像以前一样 - 在上下文>作业完成后加载数据>存储。

另一个有趣的一点是我们如何模拟我们所需要的。

我用的是NSubstitute,代码很简单:

[When(@"access result is required")]

public void WhenAccessResultIsRequired()

{

    //data from context

    var membershipTypes = (List<MembershipTypeModel>)ScenarioContext.Current["MembershipTypes"];

    var user = (UserModel)ScenarioContext.Current["User"];

    //setup the mocks

    var configurationRetrieval = Substitute.For<IConfigurationRetrieval>();

    configurationRetrieval.RetrieveMembershipTypes().Returns(membershipTypes);

    var userDataRetrieval = Substitute.For<IUserDataRetrieval>();

    userDataRetrieval.RetrieveUserDetails(Arg.Any<string>()).Returns(user);

    //call to AccessFrameworkAnalyser

    var accessResult = new AccessFrameworkAnalyser(configurationRetrieval, userDataRetrieval).DetermineAccessResults(user.Username);

    ScenarioContext.Current.Add("AccessResult", accessResult);

}

初始数据来自在此之前运行的步骤,然后我们设置mocks,最后调用AccessFramework并将结果存储在上下文中。

最后一步,实际的断言如下所示:

[Then(@"access result should be")]

public void ThenAccessResultShouldBe(Table table)

{

    var expectedAccessResult = this.GetAccessResultFromTable(table);

    var accessResult = (AccessResultModel)ScenarioContext.Current["AccessResult"];

    expectedAccessResult.ShouldBeEquivalentTo(accessResult);

}

在这里我使用了另一个NuGet软件包FluentAssertions。这可以让我比较对象,而不用担心每个属性将需要多少个断言。我仍然可以只有一个断言。

附上完整的代码,请看看,在Visual Studio中跟踪事情要容易得多。注意解决方案的结构,一切都在一个单独的项目中,一切都引用了它所需要的,没有什么更多:

希望现在你开始看到使用BDD的优点。对我而言的要点是,一旦实际需求清楚,我们就不需要看代码来解决它的功能。我们需要做的就是查看功能文件。

用票号标记场景是一个好主意,以便您了解每个测试涵盖的要求。这提供了业务的可见性方面,我们已经涵盖了多少和剩下的事情。

遇到错误时,编写一个复制错误然后修复错误的测试是个好主意。这样一来,您可以确定某个bug一旦修复,它就会保持固定。

如果您需要调试BDD测试场景,您可以简单地在一个步骤上设置一个断点,然后右键单击“测试资源管理器”窗口,选择“调试所选测试”并关闭您。

BDD Downsides

所以,你向我们展示了蛋糕,这种做法的缺点是什么?

只有一个我发现到目前为止,这不是BDD问题具体,而是一个工具问题。

一旦你有几个功能文件和健康的测试数量,你可能会有不少的步骤。没有简单的方法可以告诉任何功能文件不使用步骤方法。Codelens不会在这里帮忙。你不能确定这个特定步骤是否被十个场景调用。在任何地方都不算任何地方,这可能意味着你可以使用孤儿步法。

当然,您可以随时删除一步法,然后检查任何功能文件是否受到影响,但可能需要一段时间,具体取决于您拥有的功能文件数量。

正如我所说,这不是一个BDD问题,它是一个Specflow问题,很可能只有更好的时间过去。

对我来说,使用BDD的好处大大超过了Specflow的问题。

下载本文的全部源代码(Github)。

时间: 2024-10-01 02:54:44

行为驱动开发(BDD) - 深入了解的相关文章

python的行为驱动开发(BDD)

教程一: 基于Python的行为驱动开发实战: http://python.jobbole.com/81303/ 基于Python的行为驱动开发实战 英语原文地址: http://code.tutsplus.com/tutorials/behavior-driven-development-in-python--net-26547 Python BDD自动化测试框架初探: http://lovesoo.org/python-bdd-exploration-of-the-automated-tes

行为驱动开发BDD精粹

BDD脱胎于TDD 行为驱动开发(Behavior-Driven Development,简称BDD),是在测试驱动开发(Test-Driven Development,TDD)基础上发展而来的一种软件开发方法.TDD最大的弊端,是面对一大堆的功能需求和用例时,往往会感到无从下手.另一方面,由于TDD更侧重于测试本身,因此容易忽视对业务需求的表达,最终沉溺于琐碎细节而无法自拔. BDD避免了信息丢失 与传统的软件开发方法相比,BDD的本质在于尽可能避免在需求描述.用例撰写.代码实现.测试等各环节

行为驱动开发(BDD)

行为驱动开发(BDD) 引言 BDD是对TDD理念的扩展.BDD强调有利害关系的技术团体和非技术团队都要参与到软件开发过程中.可以把它看成一种强调团体间合作的敏捷方法.大多数采用某种敏捷方法的团队最终都会遵循BDD的许多原则.在编写单元测试的时候,尽量使用BDD风格为测试命名.BDD风格强调使用完整的.描述性的.便于业务用户理解的名称或术语,避免使用技术术语.BDD希望类.方法和变量的名称尽可能地反映日常用语. 一.开发环境和工具  1.1   Visual Studio 2013 1.2   

BDD(行为驱动开发)

BDD的重点是通过与利益相关者的讨论取得对预期的软件行为的清醒认识.它通过用自然语言书写非程序员可读的测试用例扩展了测试驱动开发方法.行为驱动开发人员使用混合了领域中统一的语言的母语语言来描述他们的代码的目的.这让开发者得以把精力集中在代码应该怎么写,而不是技术细节上,而且也最大程度的减少了将代码编写者的技术语言与商业客户.用户.利益相关者.项目管理者等的领域语言之间来回翻译的代价. 关于如何处理需求说明与测试,不同的组织使用不同的名称,或者说是不同的定义,但他们都有一套共同的核心原则与思想,而

Windows驱动开发基础(八)内存管理

Windows驱动开发基础系列,转载请标明出处:http://blog.csdn.net/ikerpeng/article/details/38826159 就32位的计算机来说,他有4G的真实的物理内存.但是这样是不够的,于是引入了虚拟内存的概念.使得每一个进程都有4G的虚拟内存. 虚拟内存实际上就是采用了一种映射的方式.4G的内存实际上被分页.一般来说一个页的大小是4K.也是说它被分为了1M个页.在这么多的页里面,有一部分是对应于物理内存的(可以是多对一的):有一部分是对应于磁盘上的空间,但

如何成为优秀的驱动开发工程师

或许这样的标题,应该是由像Linus或Greg KH这样的大师级的高手才有资格写的吧.但是作为我来说,也许我更想把这个标题作为一个疑问句来使用,整理一下自己的认识,用来勉励自己,和大家一起努力实现这个目标.认识肤浅的地方,还请大家见谅. 何谓优秀的驱动开发工程师 首先要定义,我所认为的一个优秀的驱动开发工程师,应该具备什么样的能力,这里列一下按照从易到难的顺序,个人认为应该会有几个方面的要求吧: 能够独立完成驱动的功能开发任务 能够分析和优化驱动的性能,针对特定硬件扬长避短 能够充分了解模块相关

TDD测试驱动开发

TDD测试驱动开发 一.概念 TDD故名思意就是用测试的方法驱动开发,简单说就是先写测试代码,再写开发代码.传统的方式是先写代码,再测试,它的开发方式与之正好相反. TDD是极限编程的一个最重要的设计工具之一,使得我们编码的目的更加明确.而极限编程的另一个最重要的工具—重构.重构改变的是代码的内部结构,而不会改变外部接口功能.一整套完备的测试用例可以保证我们的程序更加健壮,功能更加完善. 二.作用 站在用户使用的角度去思考如何完成产品设计,强迫开发人员事先思考完善的测试用例并提供不考虑细节的外部

书籍--嵌入式Linux驱动开发

<UNIX环境高级编程>(第2版),史蒂文斯著 <深入理解 Linux 内核>(第三版) ,博韦等著 Linux设备驱动开发详解:基于最新的Linux 4.0内核    宋宝华

Android系统移植与驱动开发概述读书心得

第一章是Android系统移植与驱动开发概述,包含了Android系统架构.Android系统移植的主要工作.查看Linux内核版本.Linux内核版本号的定义规则.如何学习Linux驱动开发以及Linux设备驱动等六方面内容 Android系统架构分为四层,分别是Linux内核.C/C++代码库.Android SDK API和应用程序.Linux内核这一层主要包括Linux的驱动程序以及内存管理.进程管理.电源管理等程序. Android移植可分为两部分:应用移植和系统移植.应用移植是指将应