SpecFlow http://blog.csdn.net/yujunwu2525/article/details/7839859
将业务需求与.NET代码结合起来
SpecFlow旨在弥合领域专家与开发人员之间的代沟,通过结合可读性高的行为规格与例子(一些规范)进行底层实现。
我们的目标是提供一个高效实用的方法来完成我们对.NET项目的实例化需求 (①Specification-By-Example ),SpecFlow同样也适用于ATDD与BDD这些与实例化需求类似的概念。
SpecFlow是开源的,你可以在这里找到它的一些信息: BSD license。
作为‘小黄瓜’家族的一部分,SpecFlow使用了Cucumber官方的解析器,同时能集成在.NET framework, Silverlight, Windows Phone and Mono这些环境中,你可以在这些框架上使用它。
下载与入门
- 在这里下载安装 Visual Studio Gallery!
- 在这里可以看到它的新特性 "What's New" guide
- 入门指南 Project Setup Guide!
想要了解的更多:
- 详细的配置指南Detailed project setup guide
- 文档材料Documentation
- SpecFlow的灵感来自于 Cucumber 并使用了 Gherkin
- 推荐书籍: Specification by Example by Gojko Adzic,
The Cucumber Book by Matt Wynne and Aslak Hellesoy
探索SpecFlow家族的更多内容
帮助我们检查 SpecFlow 将使它变的更好
- SpecRun - 一款适用于SpecFlow的只能集成测试工具
- SpecLog - 一款基于SpecFlow测试的日志工具
- 想让你的开源工具显示在这里吗? 联系我们吧:Contact us!
重构:SpecFlow的Logo与网站
随着 SpecFlow 1.9 的更新,我们正在重新设计SpecFlow logo.
我们正在重构我们的网站与logo,这是一个1.9版本的发布网站,新网站出现之前我们将暂时使用这个主页。
(注:
①实例化需求是一种使用基于图表举例说明之类的具象信息来协助项目小组进行需求定义与面向企业级功能测试的方法,而不是依靠一些抽象方法来说明。实例化需求是一组方法,它以一种对开发团队有所帮助的方式(理想情况下表现为可执行的测试)描述计算机系统的功能和行为,让不懂技术的利益相关者也可以理解,即使客户的需求在不断变化,它也具有很好的可维护性,可以保持需求的相关性。)
原文:
Behavior-Driven Development with NBehave
这里模拟了一个“银行账户”的类
- public sealed class Account
- {
- private int balance;
- public int Balance
- {
- get { return balance; }
- set { balance = value; }
- }
- public void Deposit(int amount)
- {
- }
- public void Withdraw(int amount)
- {
- }
- public static void Transfer(Account from, Account to, int amount)
- {
- }
- }
初始化测试类(注:引用了NBehave的dll)
- public class AccountTest : SpecBase
- {
- public Account account;
- public Account account2;
- [SetUp]
- public void Initialize_before_each_test()
- {
- account = new Account();
- account2 = new Account { Balance = 100 };
- }
- }
Stories
(关于BDD)所有的内容都从一个‘故事’(Story)开始。
很早之前,NBehave的开发者们就开始尝试写出一个叫Story的类,这个类被设计出来的意义就是为了能够描述一组特定的应用场景,以用来模拟我们想要具体测试的业务。
举个例子,假设我们把这个story描述成一个存款业务,于是:
- [Story, That, Should("Increase account balance when money is deposited")]
- public void Deposit_should_increase_account_balance()
- {
- Story story = new Story("Deposit");
- story.AsA("User")
- .IWant("The bank account balance to increase by the amount deposited")
- .SoThat("I can deposit money");
- // scenarios here
- }
存款业务应该增加账户余额
作为一个用户,我想要让账户内的余额增加,那么我可以选择通过存款实现
PS:我的语序混乱了,但原文的例子是这样写的)
整段代码其实并没有技术实现上的实际意义,它只不过是利用了一种类似第三方单元测试的语法描述了业务的特性(原文:the attributes that decorate the test method are really using the familiar xUnit testing attributes 而对于上个story,文章使用了NBehave),对于BDD而言,其目的是为了让整个业务的逻辑性和可读性增强,当然我们也可以换成别的语法风格,比如
- using That = MbUnit.Framework.TestAttribute;
- using Describe = MbUnit.Framework.CategoryAttribute;
- using For = MbUnit.Framework.CategoryAttribute;
- using Wrote = MbUnit.Framework.DescriptionAttribute;
- using Should = MbUnit.Framework.DescriptionAttribute;
现在回到那个story里,发现没有,整个story是没有任何test存在的,它仅仅是使用了文字与NBehave提供的接口就完成了一个完整业务的描述。
于是story的定义出来了,他就是为了更清晰的反映出一个特定需求(注意,是一个特定的需求,而不是一个无序的故事,在实际的开发中,这需要业务人员悉心的分离出来),然后当这个story送到了开发人员的手上时,他们就能有针对性的并能更容易决定写出哪些测试。
Scenarios
我们有了一个story,现在可以开始写测试了。我们根据story,然后提供一些叫做scenario的内容-描述一个可能发生的事件(原文:we have to provide something called a scenario – a description of one possible thing that can happen)
就银行存款案例而言,可能发生这些情况:你的账户能正常存款(这最好);你的账户被注销了(这当然就不能存款了);或者你输入了一个负的数字(一般人当然不会);我们要对每一种可能出现的场景做到覆盖率100%的测试,让我们先从简单的开始:
- story.WithScenario("Money deposit")
- .Given("My bank account is empty", () => { account.Balance = 0; })
- .When("I deposit 100 units", () => account.Deposit(100))
- .Then("The account balance should be 100", () => account.Balance.ShouldEqual(100));
这个小片段大概已经告诉我们什么是BDD了,是的,一个前提,一个后置条件,最后是结果
- 首先,
WithScenario()
告诉系统这是一个什么样的scenario。这段信息将被设置到测试工具中,之后的所有(测试)内容都请遵循这个场景的限定。 - 然后, 使用
Given()
来定义一个前提-即初始化一个空的账户。这里有两个参数,一个string
用来描述你将做什么, 另一个Action
将实现你之前的定义(即给定/初始化条件)。 -
When()
方法描述一个行为,当一个行为动作发生时该怎样处理的,这就是我们之后想要测试的内容。 - 最后,
Then()
方法将对我们的方法进行测试。
你可能已经发现了,在上面的测试中,包含了大量了lambda表达式,这是NBehave的特点之一,可以灵活的配置每一个Action。
现在来看看测试结果(注:文章中使用的是MbUnit,写出这个测试的主要目的是让大家可以很清晰的看到NBehave所带来的好处——结构清晰,通俗易懂)
- *** DebugTrace ***
- Story: Deposit
- Narrative:
- As a User
- I want The bank account balance to increase by the amount deposited
- So that I can deposit money
- Scenario 1: Money deposit
- Given My bank account is empty
- When I deposit 100 units
- Then The account balance should be 100 - FAILED
- MbUnit.Core.Exceptions.NotEqualAssertionException:
- Equal assertion failed: [[0]]!=[[100]]
很显然,因为我们根本没有实现Deposit方法,测试当然不能通过,现在我们想写办法(添加一些内容)来让测试通过。
- public void Deposit(int amount)
- {
- balance += amount;
- }
测试通过了,瞧,多简单!通过业务分析,描述场景以及测试,我们实现了一个业务方法了。
再看一个复杂点的,在存款时尝试存一个负的数
- story.WithScenario("Negative amount deposit")
- .Given("My bank account is empty", () => { account.Balance = 0; })
- .When("I try to deposit a negative amount", () => { })
- .Then("I get an exception",
- () => typeof(Exception).ShouldBeThrownBy(() => account.Deposit(-100)))
- .And("My bank account balance is unchanged",
- () => account.Balance.ShouldEqual(0));
(关于为什么使用 When("I try to deposit a negative amount", () => { })这样一个空方法,作者的解释大概是说想要通过这样一个不合理的方法来捕捉到一个异常,具体不翻译了,和NBehave关系不大,是他例子中的一种想法)
跑一遍测试,然后看输出:
- *** DebugTrace ***
- Story: Deposit
- Narrative:
- As a User
- I want The bank account balance to increase by the amount deposited
- So that I can deposit money
- Scenario 1: Money deposit
- Given My bank account is empty
- When I deposit 100 units
- Then The account balance should be 100
- Scenario 2: Negative amount deposit
- Given My bank account is empty
- When I try to deposit a negative amount
- Then I get an exception - FAILED
- public void Deposit(int amount)
- {
- if (amount <= 0)
- throw new Exception();
- balance += amount;
- }
上面那个场景的语法很明显是不易懂的,不但使用了一个扩展方法,并且还没有一个行为发生(空的When()方法),很不合逻辑
更改一下场景:
- story.WithScenario("Valid transfer")
- .Given("I have 100 dollars", () => { account.Balance = 100; })
- .And("You have 100 dollars", () => { account2.Balance = 100; })
- .When("I give you 50 dollars",
- () => Assert.DoesNotThrow(() => Account.Transfer(account, account2, 50)))
- .Then("I have 50 dollars left", () => account.Balance.ShouldEqual(50))
- .And("You have 150 dollars", () => account2.Balance.ShouldEqual(150));
.NET的DTO映射工具AutoMapper
原文:https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
参考:http://www.infoq.com/cn/news/2010/02/automapper-rtw/
说明:这是一款DTO的映射工具,如果你不了解DTO,可能会不理解它到底有什么意义。
概述:
AutoMapper是基于对象到对象约定的映射工具,它可以把复杂的对象模型转为DTO,或者其他的--那些让设计更合理更适于序列化、通信、传递消息的简单对象或者干脆就只是在领域层与应用层之间搭建一个简单的ACL防护层(就像DTO一样,用于代码的显示转换)来增加各自层的相互独立性。
一般用于ViewModel模式和跨服务范畴。
AutoMapper包含以下功能:
- Flattening
- Projection
- Configuration Validation
- Lists and Arrays
- Nested Mappings
- Custom Type Converters
- Custom Value Resolvers
- Custom Value Formatters
- Null Substitution
- Containers
- Mapping Inheritance
简介:
AutoMapper是什么?
AutoMapper是一个将对象映射到对象的映射工具。对象映射的工作机制是:通过输入一个类型的对象然后将其转换成另一个不同类型的对象并输出。在这里AutoMapper要做的(所擅长的)就是提供一些方便的方法,把那些麻烦累人的工作(指对象转换)从类型转换算法中剔除掉。只要需要转换的类型遵循了AutoMapper制定的规则,那么你基本上就不用再写配置算法来实现两个类型之间转换的工作了,AutoMapper会替你自动完成。
//原文:把工作从如将类型A转换成类型B的映射算法中剔除,如果类型B遵循了AutoMapper制定的规则,那么你基本上就可以以零配置来实现两个类型之间转换的工作了。( What makes AutoMapper interesting is that itprovides some interesting conventions to take the dirty work out of figuringout how to map type A to type B.)
为什么使用AutoMapper?
写类型转换的映射代码实在一件枯燥的事情,而对映射做测试则更乏味。AutoMapper为类型转换提供了非常简单的配置算法,同样也易于测试。
现在一个现实的问题是,“为什么使用对象到对象的映射?”
类似于‘映射转换’这样的情况将会发生在项目中非常多的地方,尽管大部分只会发生在层与层之间的边界,例如表示层与领域层之间,或者服务层与领域层之间。如果层与层之间有相关联的话,那么一层的变化会影响到另一层,因此,对象到对象的映射可以隔离这些层的模型,让每一层的变的更加独立,每一层的变化只会影响到自身。
怎样使用AutoMapper?
首先,你需要一个源数据对象和一个目的数据对象。在设计目的数据对象时,会受到它所在层的影响(即在转换对象时你需要getXXX,setXXX一堆方法),但是对于AutoMapper而言,它最大的优势就是只要你在设计目的数据对象时,让其中的成员(按一定规则)去匹配源数据对象成员的命名方法,那么这些问题就能很好解决。
例如:一个源数据对象中有一个属性叫:“FirstName”,那么它就会自动映射到目的数据中另一个叫“FirstName”的属性。
也就是说,你只需要有两个对象,然后引用AutoMapper,那么你将能轻松的创建对象之间的映射
比如:
- Mapper.CreateMap<Order,OrderDto>();
CreateMap方法类型左边的是源数据,右边的是目的数据,在执行完这条映射语句后,就可以使用Map方法了。
- OrderDtodto = Mapper.Map<Order, OrderDto>(order);
AutoMapper也有非泛型的方法,不过对于那些情况,你可能不好判断编译时的类型。
AutoMapper该在什么地方配置?
如果你使用静态的映射方法,配置时只需要在appDomain中配置一次,这意味着你最好的选择就是把配置代码方法在程序启动项中,例如Global.asax。需要说明的是,在bootstrapper(启动加载器)内配置自己,这个bootstrapper也被称作为启动方法。
如何测试一个映射?
创建一个映射测试,你要做两件事
· 调用启动项中创建映射的方法
· 调用Mapper.AssertConfigurationIsValid方法
就像这个例子一样
- AutoMapperConfiguration.Configure();
- Mapper.AssertConfigurationIsValid();