最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

領(lǐng)域驅(qū)動設(shè)計之單元測試最佳實(shí)踐(二)

2023-03-12 09:13 作者:吳小敏63  | 我要投稿

介紹完了DDD案例,我們終于可以進(jìn)入主題了,本方案的測試代碼基于Xunit編寫,斷言組件采用了FluentAssertions,類似的組件還有Shouldly。另外本案例使用了Code Contracts for .NET,如果不安裝此插件,可能有個別測試不能正確Pass。

為了實(shí)現(xiàn)目標(biāo)中的第二點(diǎn):"盡量不Mock,包括數(shù)據(jù)庫讀取部分”,我嘗試過3種方案:

1、測試代碼連接真實(shí)數(shù)據(jù)庫,只需要將測試數(shù)據(jù)庫配置到測試項(xiàng)目中的web.config中,即可達(dá)到這一目標(biāo)。但是該方案畢竟存在很多缺點(diǎn),如:需要將測試庫和正式庫的更改保持同步,單元測試不利于集成在CI中,不利于團(tuán)隊協(xié)作等。

2、使用SQL Lite,但是由于SQL lite本身不支持一些Linq表達(dá)式如:Skip,另外還有一些功能也無法跟Sql server保持一致,最終放棄該方案。

3、使用測試組件Effort,可以很好的配合Entity framework使用,由于Effort內(nèi)部使用了關(guān)系型內(nèi)存數(shù)據(jù)庫nmemory,所以非常適合運(yùn)行單元測試。

當(dāng)然我還是非常期待微軟能夠編寫基于EF的單元測試組件。

我在《我眼中的領(lǐng)域驅(qū)動設(shè)計》一文中提到:不要使用數(shù)據(jù)庫獨(dú)有的技術(shù),如存儲過程和觸發(fā)器等。一方面這些邏輯都應(yīng)該是Domain邏輯,另一方面一旦使用了這些技術(shù)也就意味著我們無法為這些邏輯編寫測試。

一、使用Effort

為了能夠在Castle中使用基于Effort的DbContext,需要在Castle中注冊Effort:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public?class?FakeDbContextInstaller:IWindsorInstaller
{
????public?const?string?DbConnectionKey = "FakeDbConnection";
????public?const?string?FakeBookLibraryDbContextKey = "FakeBookLibraryDbContext";
????public?void?Install(IWindsorContainer container, IConfigurationStore store)
????{
?
????????container.Register(
????????????Component.For<DbConnection>().UsingFactoryMethod(DbConnectionFactory.CreateTransient)
????????????????.Named(DbConnectionKey)
????????????????.LifestylePerWebRequest()
????????????????);
?
????????container.Register(Component.For<BookLibraryDbContext>()
????????????.DependsOn(Dependency.OnComponent(typeof(DbConnection), DbConnectionKey))
????????????.Named(FakeBookLibraryDbContextKey)
????????????.LifestylePerWebRequest()
????????????.IsDefault());
????}
}

二、為測試編寫場景

為了復(fù)用測試數(shù)據(jù),我們需要編寫場景(Scenario),下面的文件組織結(jié)構(gòu)描述了這一意圖:

以用戶注冊為例,設(shè)計RegisterUserScenario:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public?class?RegisterUserScenario : ScenarioBase
{
????public?UserModel GivingModel { get; set; }
?
????public?Guid Id { get; private?set; }
?
????public?RegisterUserScenario(IWindsorContainer container):base(container)
????{
????????GivingModel = new?UserModel()
????????{
????????????Name = "Lilei",
????????????Password = "Password1",
????????????Email = "lilei@google.com",
????????};
????}
?
????public?override?void?Execute()
????{
????????var?userService = Container.Resolve<IUserService>();
????????Id = userService.Register(GivingModel);
????}
}

場景總是提供了正確的數(shù)據(jù),執(zhí)行這樣的場景總是能夠得到正確的結(jié)果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Fact]
public?void?When_RegisterUserWithValidData_Should_CreateUser()
{
????//Arrange
????var?scenario=new?RegisterUserScenario(Container);
?
????//Act
????scenario.Execute();
?
????//Assert
????var?user = UserService.GetUser(scenario.Id);
?
????user.Name.Should().Be(scenario.GivingModel.Name);
????user.Email.Should().Be(scenario.GivingModel.Email);
}

測試的方法名很重要,我們在讀完這個方法名之后就知道該測試是在干嘛。

為了得到失敗的結(jié)果,我們需要重寫Scenario中的數(shù)據(jù),比如下面的測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Fact]
public?void?When_RegisterUserWithEmptyName_Should_ThrowException()
{
????//Arrange
????var?scenario=new?RegisterUserScenario(Container)
????{
????????GivingModel = new?UserModel()
????????{
????????????Name = string.Empty,
????????????Email = "lilei@google.com",
????????????Password = "Password1"
????????}
????};
?
????//Act
????scenario.Invoking(s => s.Execute()).ShouldThrow<Exception>("invalid username");
}

三、基于之前的場景編寫新的場景,從而達(dá)到復(fù)用數(shù)據(jù)的目的

例如我們需要編寫“用戶登錄”的測試,首先需要編寫LoginScenario

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public?class?LoginScenario:ScenarioBase
{
????public?string?Email { get; set; }
????public?string?Password { get; set; }
?
????public?bool?Login { get; private?set; }
????public?Guid Id { get; private?set; }
????public?LoginScenario(IWindsorContainer container) : base(container)
????{
????????var?registerScenario=new?RegisterUserScenario(container);
????????registerScenario.Execute();
?
????????Id = registerScenario.Id;
????????Email = registerScenario.GivingModel.Email;
????????Password = registerScenario.GivingModel.Password;
?
????}
?
????public?override?void?Execute()
????{
????????var?userService = Container.Resolve<IUserService>();
?
????????Login=userService.Login(Email, Password);
????}
}

在這個場景的構(gòu)造函數(shù)中我們又執(zhí)行了RegisterScenario,從而達(dá)到重復(fù)利用數(shù)據(jù)的目的。

為“用戶登錄”編寫測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public?class?UserLoginTests:TestBase
{
???[Fact]
???public??void?When_LoginWithInexistentEmail_Should_ThrowException()
???{
????????//Arrange
????????var?loginScenario=new?LoginScenario(Container)
????????{
????????????Email = "other@google.com",
????????};
?
????????//Act
???????loginScenario.Invoking(s => s.Execute()).ShouldThrow<ApplicationServiceException>("no such user");
?
???}
?
????[Fact]
???public?void?When_LoginWithWrongPassword_Should_ReturnFalse()
???{
????????//Arrange
????????var?loginScenario=new?LoginScenario(Container)
????????{
????????????Password = "wrongPassword"
????????};
?
????????//Act
????????loginScenario.Execute();
?
????????//Assert
???????loginScenario.Login.Should().BeFalse();
???}
?
????[Fact]
????public?void?When_LoginWithCorrectPassword_Should_ReturnTrue()
????{
????????//Arrange
????????var?loginScenario = new?LoginScenario(Container);
????????
?
????????//Act
????????loginScenario.Execute();
?
????????//Assert
????????loginScenario.Login.Should().BeTrue();
????}
?
}

?

我們總是需要為新的業(yè)務(wù)邏輯編寫新的場景,而新的場景總是基于之前編寫好的場景,整個系統(tǒng)的任何功能都可以用真實(shí)的測試代碼來覆蓋。

由于我們在測試基類中為每個測試都開啟了單獨(dú)的scope,每一個測試結(jié)束都會dispose數(shù)據(jù)庫。所以每一個測試無論運(yùn)行多少遍都是相同的效果。缺點(diǎn)是這些測試不能并行運(yùn)行,XUnit默認(rèn)以不同的測試類為單位并行運(yùn)行,我們通過在測試類上添加相同的[Collection("IntegrationTests")]標(biāo)簽,從而禁用XUnit的并行運(yùn)行能力。

采用該方案覆蓋完畢單元測試的系統(tǒng),開發(fā)者每次提交代碼并保證所有單元測是都是“passed”,開發(fā)者每一次代碼提交都會信心滿滿。

高質(zhì)量的單元測試不但能夠確保系統(tǒng)的平穩(wěn)運(yùn)行,更是一種有效的文檔,當(dāng)你讀完每一個場景的測試用例,你基本就能夠?qū)υ摌I(yè)務(wù)非常熟悉了。

接近真實(shí)的單元測試還可以省去你Debug的時間,只要你編寫的測試通過,基本就可以確保后臺代碼的可靠性。另外你可以在任何時候從這些測試代碼中Debug進(jìn)去,相比從前端界面Debug代碼能夠節(jié)省不少時間,一勞永逸。


領(lǐng)域驅(qū)動設(shè)計之單元測試最佳實(shí)踐(二)的評論 (共 條)

分享到微博請遵守國家法律
林甸县| 修武县| 沈阳市| 鸡西市| 乐至县| 怀柔区| 五寨县| 江城| 上虞市| 马关县| 保康县| 社会| 中山市| 新河县| 祥云县| 乐安县| 合川市| 安乡县| 平凉市| 台江县| 天气| 无棣县| 宁都县| 晋江市| 天等县| 龙山县| 阳高县| 闸北区| 平昌县| 大洼县| 福清市| 巨鹿县| 齐河县| 阳高县| 资阳市| 巴彦淖尔市| 彭山县| 会东县| 凤山市| 封开县| 鱼台县|