软件测试测试用例编写_不要先编写所有软件测试-只需编写一个
軟件測(cè)試測(cè)試用例編寫(xiě)
Test Driven Development (TDD) is sometimes described as “writing tests first”. The TDD mantra states that we should not write code before we have written automated tests that exercise that code. Writing code first is considered suboptimal.
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)有時(shí)被稱為“首先編寫(xiě)測(cè)試”。 TDD的口頭禪規(guī)定,在編寫(xiě)用于執(zhí)行該代碼的自動(dòng)測(cè)試之前,請(qǐng)勿編寫(xiě)代碼。 首先編寫(xiě)代碼被認(rèn)為是次優(yōu)的。
And of course, writing code first is how we develop software following the so-called waterfall model. In that model, we divide software development activities into stages. For example, we have a ‘requirements gathering’ stage, we have an ‘a(chǎn)pplication building’ stage, we have an ‘a(chǎn)pplication testing’ stage, we have an ‘a(chǎn)pplication deployment’ stage and so on.
當(dāng)然,首先編寫(xiě)代碼是我們遵循所謂的瀑布模型開(kāi)發(fā)軟件的方式。 在該模型中,我們將軟件開(kāi)發(fā)活動(dòng)劃分為多個(gè)階段。 例如,我們有一個(gè)“需求收集”階段,有一個(gè)“應(yīng)用程序構(gòu)建”階段,有一個(gè)“應(yīng)用程序測(cè)試”階段,有一個(gè)“應(yīng)用程序部署”階段,依此類(lèi)推。
But how is that different from the agile methodology? Don’t we have the exact same stages in agile?
但是,這與敏捷方法學(xué)有何不同? 我們?cè)诿艚葜袥](méi)有完全相同的階段嗎?
敏捷方法論與瀑布方法論 (Agile Methodology vs Waterfall Methodology)
Of course we do. The crucial difference is that in agile, those stages are not gated.
當(dāng)然可以 關(guān)鍵的區(qū)別在于,在敏捷開(kāi)發(fā)中,這些階段不會(huì)受到限制。
In waterfall, we gate the stages and execute them in strict sequence. This means we won’t begin building the shipping application until such time as the requirements have been gathered, completed, signed off on and frozen. Once requirements are frozen (and controlled by our change management policies), we move into the next stage (or phase) – application building.
在瀑布中,我們對(duì)階段進(jìn)行門(mén)控并嚴(yán)格執(zhí)行。 這意味著直到需求被收集,完成,簽署并凍結(jié)后,我們才會(huì)開(kāi)始構(gòu)建運(yùn)輸應(yīng)用程序。 一旦需求被凍結(jié)(并由我們的變更管理策略控制),我們便進(jìn)入下一階段(或階段)–應(yīng)用程序構(gòu)建。
And similarly, we won’t move into the testing stage until the entire application has been built and we have reached the code complete milestone, at which point code changes have been frozen.
同樣,在整個(gè)應(yīng)用程序都已構(gòu)建并且達(dá)到代碼完整的里程碑之前,我們不會(huì)進(jìn)入測(cè)試階段,此時(shí)代碼更改已被凍結(jié)。
Once code gets frozen (and code freeze is then controlled by our change management policies), we hand it off to the testers. The testing phase begins, and only once all testing has completed (and provided that no significant defects have been detected), do we move into the deployment phase.
一旦代碼被凍結(jié)(然后凍結(jié)由我們的變更管理策略控制),我們便將其交給測(cè)試人員。 測(cè)試階段開(kāi)始,只有完成所有測(cè)試(并且前提是未檢測(cè)到重大缺陷)后,我們才進(jìn)入部署階段。
In agile, we do all the above activities in parallel. At the same time. We keep working on user stories (specs) while simultaneously building the shipping application. As we’re building the application we are also testing it. And as we are building and testing the application, we are also deploying it.
在敏捷中,我們同時(shí)進(jìn)行上述所有活動(dòng)。 與此同時(shí)。 我們會(huì)繼續(xù)處理用戶案例(規(guī)格),同時(shí)構(gòu)建運(yùn)輸應(yīng)用程序。 在構(gòu)建應(yīng)用程序時(shí),我們也在對(duì)其進(jìn)行測(cè)試。 在構(gòu)建和測(cè)試應(yīng)用程序時(shí),我們也在部署它。
We learn from the shipping application deployed to production and use that validated learning as the feedback that will inform new user stories. That way, the loop gets closed, and we’re iterating, improving the value incrementally.
我們從部署到生產(chǎn)中的運(yùn)輸應(yīng)用程序中學(xué)習(xí),并將經(jīng)過(guò)驗(yàn)證的學(xué)習(xí)用作將為新用戶故事提供信息的反饋。 這樣,循環(huán)就閉合了,我們進(jìn)行迭代,逐步提高了價(jià)值。
The only way to enable such iterative value stream delivery is by relying on automated tests. And as we’ve described, those tests are being written very early in the game. Actually, tests must be written before we write shipping code.
實(shí)現(xiàn)這種迭代式價(jià)值流交付的唯一方法是依靠自動(dòng)化測(cè)試。 正如我們已經(jīng)描述的那樣,這些測(cè)試是在游戲的早期階段編寫(xiě)的。 實(shí)際上,在編寫(xiě)運(yùn)輸代碼之前必須先編寫(xiě)測(cè)試。
Why then is the title of this article “Don’t Write All Your Tests First, Just Write One”? It sounds a bit confusing. Let’s unpack the meaning of this title. But first, here's an overview of what tech we'll be using:
那么,為什么標(biāo)題為“不要先編寫(xiě)所有測(cè)試,只編寫(xiě)一個(gè)”? 聽(tīng)起來(lái)有點(diǎn)混亂。 讓我們解開(kāi)此標(biāo)題的含義。 但是首先,這是我們將使用的技術(shù)的概述:
本練習(xí)使用的技術(shù)棧 (The technology stack used for this exercise)
In the attempt to keep the exercise simple and easy to follow, I have chosen .NET Core platform, together with xUnit.net testing platform. To follow the coding examples, please install .NET Core and xUnit.net.
為了使練習(xí)簡(jiǎn)單易行,我選擇了.NET Core平臺(tái)以及xUnit.net測(cè)試平臺(tái)。 要遵循編碼示例,請(qǐng)安裝.NET Core和xUnit.net 。
In order to be able to run the sample code, please open ./tests/tests.csproj file and add this line to the ItemGroup:
為了能夠運(yùn)行示例代碼,請(qǐng)打開(kāi)./tests/tests.csproj文件并將此行添加到ItemGroup :
<ProjectReference Include="../app/app.csproj" />You’re now all set for following the coding exercises.
現(xiàn)在您已經(jīng)準(zhǔn)備好進(jìn)行編碼練習(xí)。
一個(gè)簡(jiǎn)單的例子 (A simple example)
To understand the difference between writing all tests first and writing one test first, it may be better to show rather than just tell.
要了解先編寫(xiě)所有測(cè)試與先編寫(xiě)一個(gè)測(cè)試之間的區(qū)別,最好顯示而不是僅僅講。
So let’s try to build a simple example – for this exercise I’ve chosen a trivial case of calculating a tip at a restaurant. Often times we find ourselves in a position where we want to tip the restaurant for the service, but it’s tough to calculate percentages in our head. So a nifty little Tip Calculator could come in handy.
因此,讓我們嘗試建立一個(gè)簡(jiǎn)單的示例-在本練習(xí)中,我選擇了一個(gè)在餐廳計(jì)算小費(fèi)的簡(jiǎn)單案例。 通常,我們發(fā)現(xiàn)自己想要為餐廳提供服務(wù)小費(fèi),但是很難計(jì)算出我們所占的百分比。 因此,一個(gè)漂亮的小Tip Calculator可能會(huì)派上用場(chǎng)。
Here are the expectations:
這是期望值:
As a patronI want to calculate the total bill (total plus the tip)Because I want to compliment the restaurant for the service
作為顧客我想計(jì)算總賬單(總計(jì)加上小費(fèi)),因?yàn)槲蚁雽?duì)餐廳的服務(wù)表示贊賞
方案1:贊助人計(jì)算可怕服務(wù)的總數(shù) (Scenario 1: Patron calculates the total for terrible service)
Given that the restaurant total is $100.00And the service was terribleWhen the tip calculator calculates the total chargeThen tip calculator shows $100.00 total charge
假設(shè)餐廳的總價(jià)為$ 100.00并且服務(wù)很糟糕,當(dāng)小費(fèi)計(jì)算器計(jì)算總費(fèi)用時(shí),小費(fèi)計(jì)算器將顯示$ 100.00的總費(fèi)用
方案2:顧客計(jì)算服務(wù)質(zhì)量差的總數(shù) (Scenario 2: Patron calculates the total for poor service)
Given that the restaurant total is $100.00And the service was poorWhen the tip calculator calculates the total chargeThen tip calculator shows $105.00 total charge
假設(shè)餐廳的總價(jià)為$ 100.00并且服務(wù)很差,當(dāng)小費(fèi)計(jì)算器計(jì)算總費(fèi)用時(shí),小費(fèi)計(jì)算器將顯示$ 105.00的總費(fèi)用
方案3:顧客計(jì)算出良好服務(wù)的總金額 (Scenario 3: Patron calculates the total for good service)
Given that the restaurant total is $100.00And the service was goodWhen the tip calculator calculates the total chargeThen tip calculator shows $110.00 total charge
假設(shè)餐廳的總價(jià)為$ 100.00并且服務(wù)很好,則當(dāng)小費(fèi)計(jì)算器計(jì)算總費(fèi)用時(shí),小費(fèi)計(jì)算器將顯示$ 110.00的總費(fèi)用
方案4:顧客計(jì)算出優(yōu)質(zhì)服務(wù)的總額 (Scenario 4: Patron calculates the total for great service)
Given that the restaurant total is $100.00And the service was greatWhen the tip calculator calculates the total chargeThen tip calculator shows $115.00 total charge
假設(shè)餐廳的總價(jià)為$ 100.00,服務(wù)很棒,那么當(dāng)小費(fèi)計(jì)算器計(jì)算總費(fèi)用時(shí),小費(fèi)計(jì)算器就會(huì)顯示$ 115.00的總費(fèi)用
方案5:顧客計(jì)算出優(yōu)質(zhì)服務(wù)的總額 (Scenario 5: Patron calculates the total for excellent service)
Given that the restaurant total is $100.00And the service was excellentWhen the tip calculator calculates the total chargeThen tip calculator shows $120.00 total charge
假設(shè)餐廳的總價(jià)為$ 100.00,服務(wù)非常好,當(dāng)小費(fèi)計(jì)算器計(jì)算總費(fèi)用時(shí),小費(fèi)計(jì)算器顯示總費(fèi)用為$ 120.00
Let’s now implement the above user story.
現(xiàn)在,讓我們實(shí)現(xiàn)上面的用戶故事。
We see that the story has 5 acceptance criteria (a.k.a. scenarios). Now we move into the analysis phase – think about what should be the first functionality that our Tip Calculator application should implement. But first, let’s open the command line terminal and create the new directory:
我們看到這個(gè)故事有5個(gè)接受標(biāo)準(zhǔn)(也稱為方案)。 現(xiàn)在,我們進(jìn)入分析階段-考慮一下Tip Calculator應(yīng)用程序應(yīng)該實(shí)現(xiàn)的第一個(gè)功能。 但是首先,讓我們打開(kāi)命令行終端并創(chuàng)建新目錄:
md TipCalculator cd TipCalculatorand create app and tests directories inside the TipCalculator directory.
并在TipCalculator目錄中創(chuàng)建app和tests目錄。
Now cd tests and run:
現(xiàn)在進(jìn)行cd tests并運(yùn)行:
dotnet new xunitThen cd .. and cd app, then run:
然后cd ..和cd app ,然后運(yùn)行:
dotnet new classlibWe’re now ready to boogie!
我們現(xiàn)在可以開(kāi)始聊天了!
Open your favourite text editor (mine is Visual Studio Code) and set your mind on the expectations. What behaviour are we expecting from the Tip Calculator?
打開(kāi)您最喜歡的文本編輯器(我的是Visual Studio Code ),然后放下期望。 我們期望Tip Calculator有什么行為?
To narrow the scope of our expectations, it usually helps to take one acceptance criteria (i.e. one scenario) and focus on it first. Let’s take scenario #1:
為了縮小我們的期望范圍,通常有助于采用一個(gè)接受標(biāo)準(zhǔn)(即一種情況)并首先關(guān)注它。 讓我們來(lái)看場(chǎng)景1:
方案1:贊助人計(jì)算可怕服務(wù)的總數(shù) (Scenario 1: Patron calculates the total for terrible service)
Given that the restaurant total is $100.00And the service was terribleWhen the tip calculator calculates the total chargeThen tip calculator shows $100.00 total charge
假設(shè)餐廳的總價(jià)為$ 100.00并且服務(wù)很糟糕,當(dāng)小費(fèi)計(jì)算器計(jì)算總費(fèi)用時(shí),小費(fèi)計(jì)算器顯示總費(fèi)用為$ 100.00
In case the service was terrible, we are not adding any tips, and Tip Calculator is calculating a $0.00 tip. So how do we automate that scenario?
萬(wàn)一服務(wù)糟糕,我們不會(huì)添加任何小費(fèi), Tip Calculator會(huì)計(jì)算$ 0.00小費(fèi)。 那么,如何使這種情況自動(dòng)化?
My first expectation would be that we need to somehow inform the Tip Calculator that the service was terrible. We either type the word ‘Terrible’ into the input field, or we select ‘Terrible’ from the list of available service ratings.
我的第一個(gè)期望是,我們需要以某種方式告知Tip Calculator該服務(wù)很糟糕。 我們可以在輸入字段中輸入單詞“ Terrible”,或者從可用服務(wù)等級(jí)列表中選擇“ Terrible”。
So the first thing to do here is to articulate some expectations regarding Tip Calculator’s ability to get notified that the service was terrible.
因此,這里要做的第一件事是闡明有關(guān)Tip Calculator能夠獲得通知該服務(wù)很糟糕的能力的一些期望。
I like to always start with the expectation that what the user inputs is valid. So I’d first write a test that checks if the rating ‘Terrible’ is recognized by the Tip Calculator as a valid rating.
我總是希望用戶輸入的內(nèi)容有效。 因此,我首先要編寫(xiě)一個(gè)測(cè)試,檢查“ Tip Calculator是否將“可怕”等級(jí)識(shí)別為有效等級(jí)。
Go to the tests directory, rename the autogenerated UnitTest1.cs file to TipCalculatorTests.cs and add the following test:
轉(zhuǎn)到tests目錄,將自動(dòng)生成的UnitTest1.cs文件重命名為T(mén)ipCalculatorTests.cs并添加以下測(cè)試:
[Fact] public void CheckIfRatingTerribleIsValid(){ var expectedResponseForValidRating = true; var actualResponseForValidRating = false; Assert.Equal(expectedResponseForValidRating, actualResponseForValidRating); }Now go to the command line, cd tests, and run:
現(xiàn)在轉(zhuǎn)到命令行cd tests并運(yùn)行:
dotnet testOf course, the above trivial test will fail, because we have hardcoded the values. But it’s always a good practice to make sure we see our tests fail before we proceed. Not observing a test fail may give us false sense of safety later on, if no tests fail and we end up thinking that everything works as expected.
當(dāng)然,上述微不足道的測(cè)試將失敗,因?yàn)槲覀円呀?jīng)對(duì)值進(jìn)行了硬編碼。 但是,在繼續(xù)進(jìn)行之前,確保我們看到測(cè)試失敗是一種很好的做法。 如果沒(méi)有測(cè)試失敗,則不遵守測(cè)試失敗可能會(huì)在以后給我們帶來(lái)錯(cuò)誤的安全感,而我們最終認(rèn)為一切都會(huì)按預(yù)期進(jìn)行。
A few more observations about the above test:
關(guān)于上述測(cè)試的更多觀察結(jié)果:
It helps if the test name is descriptive. I chose CheckIfRatingTerribleIsValid to communicate the fact that we must make sure our application is capable of recognizing our commands.
如果測(cè)試名稱具有描述性,它將很有幫助。 我選擇CheckIfRatingTerribleIsValid來(lái)傳達(dá)這樣一個(gè)事實(shí),即我們必須確保我們的應(yīng)用程序能夠識(shí)別我們的命令。
It also helps if the expected and actual variable names are descriptive. I chose expectedResponseForValidRating and actualResponseForValidRating as fairly indicative of what our expectation in this test is, and also what actual value will the Tip Calculator produce.
如果期望的變量名和實(shí)際的變量名是描述性的,這也有幫助。 我選擇expectedResponseForValidRating和actualResponseForValidRating得到公平意味著就是我們?cè)诒敬螠y(cè)試的期望是,也什么實(shí)際價(jià)值,并將在Tip Calculator產(chǎn)品。
- Test is a first-class source code and must be approached with equal care lavished upon the production code. 測(cè)試是一流的源代碼,必須以與生產(chǎn)代碼相當(dāng)?shù)闹?jǐn)慎對(duì)待。
最初的設(shè)計(jì)決定 (First design decision)
At this point, we are forced to make a decision – how will our nascent Tip Calculator know if the service rating provided by the user is valid or not?
在這一點(diǎn)上,我們不得不做出決定-我們新生的Tip Calculator如何知道用戶提供的服務(wù)等級(jí)是否有效?
The design decision that comes to mind is that Tip Calculator must be able to store and retrieve some data. In this case, the data we’re interested in is the service rating.
想到的設(shè)計(jì)決定是Tip Calculator必須能夠存儲(chǔ)和檢索一些數(shù)據(jù)。 在這種情況下,我們感興趣的數(shù)據(jù)就是服務(wù)等級(jí)。
If we go back to the user story and review the five acceptance criteria, we will see that the expectations are that Tip Calculator must be able to recognize five different service ratings:
如果我們回到用戶故事并回顧五個(gè)接受標(biāo)準(zhǔn),我們將看到, Tip Calculator必須能夠識(shí)別五個(gè)不同的服務(wù)等級(jí):
So the simplest way to get Tip Calculator to store that information would be to endow it with an array, or a list.
因此,使“ Tip Calculator存儲(chǔ)該信息的最簡(jiǎn)單方法是為其賦予數(shù)組或列表。
But rather than rushing in to implement that list, we should examine the expectations again, to see if there’s anything else we may have missed. And there is – not only must Tip Calculator be able to recognize valid service ratings, it also must be able to associate each rating with a percentage value.
但是,我們不要急于執(zhí)行該列表,而應(yīng)該再次檢查期望,以查看是否還有其他我們可能錯(cuò)過(guò)的事情。 并且–不僅Tip Calculator必須能夠識(shí)別有效的服務(wù)等級(jí),還必須能夠?qū)⒚總€(gè)等級(jí)與百分比值相關(guān)聯(lián)。
Our analysis shows the following associations:
我們的分析顯示以下關(guān)聯(lián):
In this case, a simple array or a simple list won’t be sufficient for holding the above associations. What’s the next simplest data structure that will allow us to implement these associations? After doing a little bit of research, we figure out that Hashtable is probably the most fitting data structure that can cover our needs with the least amount of ceremony.
在這種情況下,簡(jiǎn)單的數(shù)組或簡(jiǎn)單的列表不足以容納上述關(guān)聯(lián)。 允許我們實(shí)現(xiàn)這些關(guān)聯(lián)的下一個(gè)最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)是什么? 經(jīng)過(guò)一些研究,我們發(fā)現(xiàn)Hashtable可能是最合適的數(shù)據(jù)結(jié)構(gòu),可以用最少的儀式滿足我們的需求。
We now navigate to the app directory and rename autogenerated Class1.cs file to TipCalculator.cs. We now want to add a Hashtable that will hold service ratings and the associated percentage values:
現(xiàn)在,我們導(dǎo)航到app目錄,并將自動(dòng)生成的Class1.cs文件重命名為T(mén)ipCalculator.cs 。 現(xiàn)在,我們要添加一個(gè)Hashtable ,該Hashtable表將保存服務(wù)評(píng)級(jí)和相關(guān)的百分比值:
System.Collections.Hashtable ratingPercentages = new System.Collections.Hashtable();Now is a good time to recall that TDD is focused on coupling the expectations to the application’s behaviour, not to the application’s structure. Knowing that, we need to modify our test to make Tip Calculator exhibit some behaviour. The test codifies some expectations with regards to how the application must behave, and the running application provides the evidence of the expected behaviour.
現(xiàn)在是回想一下TDD專(zhuān)注于將期望與應(yīng)用程序的行為而非應(yīng)用程序的結(jié)構(gòu)耦合的好時(shí)機(jī)。 知道這一點(diǎn),我們需要修改測(cè)試以使“ Tip Calculator表現(xiàn)出某些行為。 該測(cè)試將有關(guān)應(yīng)用程序必須如何行為的一些期望匯總起來(lái),正在運(yùn)行的應(yīng)用程序提供了預(yù)期行為的證據(jù)。
But what is the evidence of the application’s behaviour? There is no other way for us to assess and evaluate application’s behaviour other than through examining the values that the running application produces.
但是,該應(yīng)用程序行為的證據(jù)是什么? 除了檢查正在運(yùn)行的應(yīng)用程序產(chǎn)生的值之外,我們沒(méi)有其他方法可以評(píng)估和評(píng)估應(yīng)用程序的行為。
In this case, we are expecting the running application to produce values true or false (Boolean values) after we ask the application if certain value (i.e. service rating) is valid.
在這種情況下,我們希望正在運(yùn)行的應(yīng)用程序在詢問(wèn)某個(gè)值(例如,服務(wù)等級(jí))是否有效之后會(huì)產(chǎn)生值true或false (布爾值)。
To teach the application how to behave in the expected fashion, we need to endow it with an API. In this case, we design the API as follows:
要教應(yīng)用程序如何以預(yù)期的方式工作,我們需要為它提供一個(gè)API。 在這種情況下,我們將API設(shè)計(jì)如下:
public bool CheckIfRatingIsValid(string rating)In our test, we will modify the actual expected value to exercise the running application and collect the output value:
在我們的測(cè)試中,我們將修改實(shí)際期望值以執(zhí)行正在運(yùn)行的應(yīng)用程序并收集輸出值:
As you can see from the screenshot above, we have instantiated TipCalculator but when attempting to ask the instance to check if the supplied rating (“Terrible”) is valid, the editor is complaining that it cannot find that method.
從上面的屏幕快照中可以看到,我們已經(jīng)實(shí)例化了TipCalculator但是當(dāng)嘗試要求實(shí)例檢查所提供的等級(jí)(“可怕”)是否有效時(shí),編輯器抱怨說(shuō)找不到該方法。
Well of course, the method hasn’t been implemented yet. Now’s the time to go ahead and do it:
當(dāng)然,該方法尚未實(shí)現(xiàn)。 現(xiàn)在是時(shí)候繼續(xù)做下去了:
public bool CheckIfRatingIsValid(string rating) { return false; }Now that the method is implemented, the test works; here is the complete listing:
現(xiàn)在已經(jīng)實(shí)現(xiàn)了該方法,測(cè)試就可以進(jìn)行; 這是完整的清單:
using Xunit; using app;namespace tests { public class TipCalculatorTests { TipCalculator tipCalculator = new TipCalculator(); [Fact] public void CheckIfRatingTerribleIsValid(){ var expectedResponseForValidRating = true; var actualResponseForValidRating = tipCalculator.CheckIfRatingIsValid("Terrible"); Assert.Equal(expectedResponseForValidRating, actualResponseForValidRating); } } }We see from the above example that we’re cheating again (we have hardcoded return false; in our newly minted method). What’s the point of beating around the bush and merely creating skeletons and scaffoldings instead of rolling up our sleeves and doing actual coding? Let’s discuss this important topic.
從上面的示例中我們看到我們?cè)俅巫鞅?在我們新創(chuàng)建的方法中,我們已經(jīng)硬編碼return false;)。 在灌木叢中跳動(dòng),僅創(chuàng)建骨架和腳手架,而不是卷起袖子并進(jìn)行實(shí)際編碼,有什么意義? 讓我們討論這個(gè)重要的話題。
討論我們的第一個(gè)設(shè)計(jì)決策 (Discussion about our first design decision)
We’re illustrating here how to do TDD step-by-step. The funny part is that this step-by-step illustration is actually the exact way how we do TDD: step-by-step. There is no other way to do TDD than by doing it step-by-step. One step at a time.
我們?cè)谶@里說(shuō)明如何逐步進(jìn)行TDD。 有趣的部分是,此分步說(shuō)明實(shí)際上是我們進(jìn)行TDD的確切方法:分步。 除了逐步進(jìn)行之外,沒(méi)有其他方法可以進(jìn)行TDD。 一步一步來(lái)。
How’s that different from any other way of doing software development? Don’t we also do everything step-by-step even when not following TDD methodology? Well, not really. Let me explain:
與進(jìn)行軟件開(kāi)發(fā)的任何其他方式有何不同? 即使不遵循TDD方法,我們也不一步一步地做所有事情嗎? 好吧,不是真的。 讓我解釋:
TDD to me feels like riding a galloping horse. We’re moving swiftly toward our goal, but we’re frequently touching the ground (the galloping horse is every now and then hitting the ground in order to bounce off and run fast).
TDD對(duì)我來(lái)說(shuō)就像騎著駿馬。 我們正在Swift向目標(biāo)邁進(jìn),但我們經(jīng)常碰到地面(奔騰的馬現(xiàn)在不時(shí)地撞到地面,以便反彈并快速奔跑)。
In comparison, when I’m doing software development without TDD, it feels to me like I’m flying a kite. I’m making swift moves with the kite, but I never touch the ground, not even once. By the time I land the kite, the landing place may not be where I intended the kite to go (it’s very hard to control the direction of a kite if it’s flying in a strong wind).
相比之下,當(dāng)我在沒(méi)有TDD的情況下進(jìn)行軟件開(kāi)發(fā)時(shí),對(duì)我來(lái)說(shuō)就像在放風(fēng)箏。 我正在用風(fēng)箏快速移動(dòng),但我從未觸地,甚至沒(méi)有觸地。 到我放風(fēng)箏時(shí),降落的地方可能不是我想要放風(fēng)箏的地方(如果風(fēng)很大,很難控制風(fēng)箏的方向)。
With TDD, any time we make a change to the code (both the test code and the shipping application code), we run the tests and so we touch the ground. We are galloping, but at the same time we need this frequent grounding. We need to see whether we’re going in the right direction and also whether we’ve broken anything during our galloping. Our tests are the Oracle who keeps telling us if everything works as expected or if something started misbehaving.
使用TDD時(shí),只要我們更改代碼(測(cè)試代碼和運(yùn)輸應(yīng)用程序代碼),就可以運(yùn)行測(cè)試,因此就可以使用。 我們?cè)诩柴Y,但同時(shí)我們需要這種頻繁的接地。 我們需要查看我們是否朝著正確的方向前進(jìn),以及在疾馳過(guò)程中是否損壞了任何東西。 我們的測(cè)試是Oracle,它會(huì)不斷告訴我們一切是否按預(yù)期進(jìn)行,或者某些事情開(kāi)始出現(xiàn)異常。
Making changes to the code is a risky business. TDD provides a nice harness that is both guiding our design decisions and ensuring we don’t mess up something that we’ve already confirmed works to our expectations.
更改代碼是一項(xiàng)冒險(xiǎn)的業(yè)務(wù)。 TDD提供了一個(gè)很好的工具,既可以指導(dǎo)我們的設(shè)計(jì)決策,又可以確保我們不會(huì)弄亂我們已經(jīng)確認(rèn)能達(dá)到預(yù)期效果的東西。
用實(shí)際的處理邏輯替換硬編碼的值 (Replace hardcoded value with actual processing logic)
Let’s now replace the hardcoded value with actual running code. We first teach our Tip Calculator that there is a service rating called “Terrible” and that tip percentage associated with this rating is 0:
現(xiàn)在讓我們用實(shí)際的運(yùn)行代碼替換硬編碼的值。 我們首先告訴我們的Tip Calculator ,有一個(gè)服務(wù)評(píng)級(jí)為“可怕”,并且與該評(píng)級(jí)相關(guān)的小費(fèi)百分比為0:
public bool CheckIfRatingIsValid(string rating) { ratingPercentages.Add("Terrible", 0); return false; }Our Tip Calculator is now knowledgeable about the fact that there is a service rating labeled “Terrible” and the tip percentage associated with terrible service is 0%. Great, but we’re still returning hardcoded value false. Time to replace it with actual calculation:
現(xiàn)在,我們的Tip Calculator可以了解以下事實(shí):服務(wù)等級(jí)標(biāo)記為“糟糕”,并且與糟糕服務(wù)相關(guān)的小費(fèi)百分比為0%。 很好,但是我們?nèi)匀环祷赜簿幋a值false 。 是時(shí)候用實(shí)際計(jì)算替換它了:
public bool CheckIfRatingIsValid(string rating) { ratingPercentages.Add("Terrible", 0); return ratingPercentages.ContainsKey(rating); }Run the test again:
再次運(yùn)行測(cè)試:
Great, but the code still looks contrived. We are loading the “Terrible” value into the instance of Hashtable ratingPercentages and then immediately checking to see if that value exists in the Hashtable. Now that we have moved from the failing test (Red) to the passing test (Green), it’s time to perform the third step in the TDD loop – Refactor.
很好,但是代碼看起來(lái)還是人為的。 我們正在將“可怕”值加載到Hashtable ratingPercentages實(shí)例中,然后立即檢查該值是否存在于Hashtable 。 現(xiàn)在我們已經(jīng)從失敗測(cè)試(紅色)移至通過(guò)測(cè)試(綠色),是時(shí)候執(zhí)行TDD循環(huán)中的第三步–重構(gòu)。
Refactoring is basically the activity of modifying the code structure without affecting the code behaviour. Our task here is simple: extract the code responsible for populating of the Hashtable ratingPercentages into a separate block of code.
重構(gòu)基本上是在不影響代碼行為的情況下修改代碼結(jié)構(gòu)的活動(dòng)。 我們的任務(wù)很簡(jiǎn)單:將負(fù)責(zé)填充Hashtable ratingPercentages的代碼提取到單獨(dú)的代碼塊中。
The most natural place for this loading is in the block of code that is doing the initialization of the Tip Calculator – the constructor method. After refactoring, our shipping application source code looks like this:
進(jìn)行加載的最自然的地方是進(jìn)行Tip Calculator初始化的代碼塊- constructor方法。 重構(gòu)后,我們的運(yùn)輸應(yīng)用程序源代碼如下所示:
using System.Collections;namespace app { public class TipCalculator { private Hashtable ratingPercentages = new Hashtable(); public TipCalculator() { ratingPercentages.Add("Terrible", 0); } public bool CheckIfRatingIsValid(string rating) { return ratingPercentages.ContainsKey(rating); } } }Run the test again, and it passes (we’re in green). We have modified the structure of the code without modifying its behaviour! Good job.
再次運(yùn)行測(cè)試,測(cè)試通過(guò)(綠色)。 我們已經(jīng)修改了代碼的結(jié)構(gòu),而沒(méi)有修改其行為! 做得好。
擲硬幣 (Flip the coin)
Any time we satisfy a positive expectation, it is a prudent practice to turn things on their head and describe the negative expectation.
每當(dāng)我們滿足積極的期望時(shí),明智的做法就是轉(zhuǎn)過(guò)頭去描述消極的期望。
At this point, since we’ve satisfied that a legitimate service rating value is found in the Tip Calculator, we want to ensure that non-legitimate values are not found in the Tip Calculator.
在這一點(diǎn)上,由于我們已經(jīng)滿意在Tip Calculator找到了合法的服務(wù)評(píng)級(jí)值,因此我們要確保在Tip Calculator中未找到不合法的值。
What do we mean by non-legitimate values? Any value other than “Terrible”, “Poor”, “Good”, “Great” and “Excellent”. Time to write the new expectation (i.e. test):
非合法價(jià)值是什么意思? “差”,“差”,“好”,“好”和“優(yōu)秀”以外的任何值。 是時(shí)候?qū)懗鲂碌钠谕?即測(cè)試):
[Fact] public void CheckIfRatingWhateverIsValid() { var expectedResponseForValidRating = true; var actualResponseForValidRating = tipCalculator.CheckIfRatingIsValid("Whatever");Assert.Equal(expectedResponseForValidRating, actualResponseForValidRating); }Run the tests:
運(yùn)行測(cè)試:
Fails. As expected. (We specified that our expectation when supplying the service rating as “Whatever” should be true. In reality, it is false, because our Tip Calculator does not contain value “Whatever”.)
失敗 如預(yù)期的那樣。 (我們指定在提供“任何”服務(wù)等級(jí)時(shí)的期望應(yīng)該是true 。實(shí)際上,這是false ,因?yàn)槲覀兊腡ip Calculator不包含值“任何”。)
Fix the test (change the expectedResponseForValidRating from true to false) and run it again:
修復(fù)測(cè)試(將expectedResponseForValidRating從true更改為false )并再次運(yùn)行它:
A moment of reflection: why did we fake the first test run and made it fail? Because we always want to make sure we observe our new test failing. That way, we’ll know that in the future any successful passing of the test is not merely a false positive.
片刻的反思:為什么我們要偽造第一次測(cè)試并使其失敗? 因?yàn)槲覀円恢毕氪_保我們觀察到新測(cè)試失敗。 這樣,我們就會(huì)知道,將來(lái)任何成功通過(guò)測(cè)試的都不僅僅是假陽(yáng)性。
贊美穩(wěn)態(tài) (In praise of steady state)
Software engineering is a balancing act between steady state and periods of unstable state. What do we mean by steady state?
軟件工程是穩(wěn)定狀態(tài)和不穩(wěn)定狀態(tài)周期之間的平衡行為。 穩(wěn)定狀態(tài)是什么意思?
If we have a system (a running application) that behaves the way we expect it to behave (i.e. it produces values we have specified as expected values), we declare that the system is in a steady state. It is running, and delivering some value.
如果我們有一個(gè)系統(tǒng)(正在運(yùn)行的應(yīng)用程序)以我們期望的方式運(yùn)行(即產(chǎn)生我們指定為期望值的值),則我們聲明該系統(tǒng)處于穩(wěn)定狀態(tài)。 它正在運(yùn)行,并提供了一些價(jià)值。
That value delivery is still partial. In our case, the only value to the users this Tip Calculator delivers is its ability to recognize service rating “Terrible” as a legitimate rating. In addition, it is capable of informing us that service rating “Whatever” is not a legitimate rating.
價(jià)值傳遞仍然是部分的。 在我們的案例中,此Tip Calculator對(duì)用戶而言是唯一的價(jià)值 交付是其將服務(wù)等級(jí)“糟糕”識(shí)別為合法等級(jí)的能力。 此外,它還可以告知我們服務(wù)等級(jí)“無(wú)論如何”都不是合法的等級(jí)。
That’s not much, but still is better than nothing. And good news – our running application is currently in a steady state. Now we want to look into how to add more valuable behaviour to our Tip Calculator. And the only way to add more value is by making some changes.
數(shù)量不多,但總比沒(méi)有好。 好消息–我們正在運(yùn)行的應(yīng)用程序目前處于穩(wěn)定狀態(tài)。 現(xiàn)在,我們要研究如何向“ Tip Calculator添加更多有價(jià)值的行為。 而增加價(jià)值的唯一方法是進(jìn)行一些更改。
Any time we make a change to our application, we disturb its steady state. This disturbance is risky. It may mean our changes could break something that is already working. Because of that concern, we strive to make the duration of this unstable state as short as possible.
每當(dāng)我們對(duì)應(yīng)用程序進(jìn)行更改時(shí),我們都會(huì)破壞其穩(wěn)態(tài)。 這種干擾是危險(xiǎn)的。 這可能意味著我們的更改可能會(huì)破壞已經(jīng)起作用的某些功能。 因此,我們努力使這種不穩(wěn)定狀態(tài)的持續(xù)時(shí)間盡可能短。
Remember how we compared TDD to riding a galloping horse? When the horse is in flight (i.e. not touching the ground) it is advancing toward our goal, but it’s not in the steady state. Only when the horse touches the ground does its state stabilize.
還記得我們?nèi)绾螌DD與騎馬馳horse相提并論嗎? 當(dāng)馬在飛行中(即不接觸地面)時(shí),它正在向我們的目標(biāo)前進(jìn),但它不是處于穩(wěn)定狀態(tài)。 只有當(dāng)馬接觸地面時(shí),其狀態(tài)才會(huì)穩(wěn)定。
TDD encourages making small changes (in flight) and immediately grounding the system by verifying that it is back in the steady state. We value steady state despite the fact that we eagerly embrace changes. Without changes, we won’t be able to deliver value, but we must do it in a very deliberate, careful fashion.
TDD鼓勵(lì)進(jìn)行微小的更改(在飛行中),并通過(guò)驗(yàn)證系統(tǒng)是否處于穩(wěn)定狀態(tài)來(lái)立即將其接地。 盡管我們熱切地接受變化,但我們?nèi)灾匾暦€(wěn)定狀態(tài)。 沒(méi)有變化,我們將無(wú)法交付價(jià)值,但是我們必須以一種非常謹(jǐn)慎,謹(jǐn)慎的方式來(lái)實(shí)現(xiàn)它。
When doing TDD, we treat changes to steady state like walking on eggshells. No matter how sure we may be in knowing what and how we’re doing software engineering, it is prudent to still let failing tests guide our decisions.
在進(jìn)行TDD時(shí),我們像在蛋殼上行走一樣對(duì)待穩(wěn)態(tài)的變化。 無(wú)論我們有多確定要知道什么以及如何進(jìn)行軟件工程,還是要謹(jǐn)慎地讓失敗的測(cè)試來(lái)指導(dǎo)我們的決策。
檢查是否正確的小費(fèi)百分比與服務(wù)等級(jí)相關(guān)聯(lián) (Check if correct tip percentage is associated with service rating)
Let’s now introduce another change into our application – a test to verify if correct tip percentage is associated with service rating “Terrible”. Remember that we populated the instance of Hashtable ratingPercentages with the following values:
現(xiàn)在,讓我們?cè)趹?yīng)用程序中進(jìn)行另一個(gè)更改–測(cè)試以驗(yàn)證正確的筆尖百分比是否與“可怕”服務(wù)等級(jí)相關(guān)聯(lián)。 請(qǐng)記住,我們使用以下值填充了Hashtable ratingPercentages實(shí)例:
ratingPercentages.Add("Terrible", 0);We have written a test that verifies that our Hashtable ratingPercentages does contain legitimate service rating “Terrible”. Now we need a test that verifies that service rating “Terrible” means that the tip percentage for that rating is 0.
我們編寫(xiě)了一個(gè)測(cè)試,以驗(yàn)證我們的Hashtable ratingPercentages是否包含合法的服務(wù)等級(jí)“ Terrible”。 現(xiàn)在,我們需要進(jìn)行一項(xiàng)測(cè)試,以驗(yàn)證服務(wù)等級(jí)“糟糕”是否意味著該等級(jí)的小費(fèi)百分比為0。
[Fact] public void CheckIfRatingTerribleHasZeroPercentTip() { var expectedZeroPercentForTerribleRating = 0; var actualZeroPercentForTerribleRating = 10; Assert.Equal(expectedZeroPercentForTerribleRating, actualZeroPercentForTerribleRating); }The new test CheckIfRatingTerribleHasZeroPercentTip should fail:
新測(cè)試CheckIfRatingTerribleHasZeroPercentTip應(yīng)該失敗:
Again, we’re purposefully hard coding wrong actual values just so that we could observe our brand new test fail. Now we must replace hard coded value with the actual call to the Tip Calculator’s method that returns tip percentage for the service rating:
同樣,我們有目的地硬編碼錯(cuò)誤的實(shí)際值,以便可以觀察到全新的測(cè)試失敗。 現(xiàn)在,我們必須用對(duì)Tip Calculator方法的實(shí)際調(diào)用替換硬編碼的值,該方法返回服務(wù)等級(jí)的提示百分比:
[Fact] public void CheckIfRatingTerribleHasZeroPercentTip() { var expectedZeroPercentForTerribleRating = 0; var actualZeroPercentForTerribleRating = tipCalculator.GetPercentageTipForRating("Terrible");Assert.Equal(expectedZeroPercentForTerribleRating, actualZeroPercentForTerribleRating); }As in the previous case, we have invented a new API for Tip Calculator. We call this new capability GetPercentageTipForRating("Terrible"). It takes the value of the service rating and returns the tip percentage for that rating.
與前面的情況一樣,我們?yōu)門(mén)ip Calculator發(fā)明了一個(gè)新的API。 我們將此新功能GetPercentageTipForRating("Terrible") 。 它采用服務(wù)等級(jí)的值,并返回該等級(jí)的小費(fèi)百分比。
Flip over to the app/TipCalculator.cs and add the hard coded skeleton of the new method:
翻轉(zhuǎn)到app/TipCalculator.cs并添加新方法的硬編碼框架:
public int GetPercentageTipForRating(string rating) { return 10; }Running the test fails again, because we have hard coded the return value. Let’s replace it with actual processing:
由于我們已經(jīng)對(duì)返回值進(jìn)行了硬編碼,因此運(yùn)行測(cè)試再次失敗。 讓我們用實(shí)際處理代替它:
public int GetPercentageTipForRating(string rating) { int tipPercentage = Int32.Parse(ratingPercentages[rating].ToString());return tipPercentage; }Run the test again:
再次運(yùn)行測(cè)試:
All three tests pass – we’re in green, we’re back to steady state!
所有三個(gè)測(cè)試均通過(guò)–我們處于綠色狀態(tài),我們回到了穩(wěn)定狀態(tài)!
對(duì)于非合法的服務(wù)評(píng)級(jí),我們期望什么小費(fèi)百分比? (What tip percentage do we expect for non-legitimate service ratings?)
Many years of experience in the field taught me to be a bit pessimistic. Now that we have our application back in the steady state and delivering value (answering questions about legitimate service ratings and also giving us correct tip percentage for the “Terrible” rating), we need to see what happens when we run our application by giving it non-legitimate service rating value (for example, by giving it service rating “Whatever”).
在該領(lǐng)域的多年經(jīng)驗(yàn)教會(huì)我有些悲觀。 現(xiàn)在我們已經(jīng)使應(yīng)用程序恢復(fù)到穩(wěn)定狀態(tài)并交付了價(jià)值(回答有關(guān)合法服務(wù)等級(jí)的問(wèn)題,并為我們提供了“可怕”等級(jí)的正確提示百分比),我們需要通過(guò)提供應(yīng)用程序來(lái)查看運(yùn)行應(yīng)用程序時(shí)會(huì)發(fā)生什么不合法的服務(wù)等級(jí)值(例如,將其服務(wù)等級(jí)定為“ Whatever”)。
Time for leaving the steady state yet again. We will write another test:
是時(shí)候再次離開(kāi)穩(wěn)定狀態(tài)了。 我們將編寫(xiě)另一個(gè)測(cè)試:
[Fact] public void CheckIfRatingWhateverHasNegativeOnePercentTip() { var expectedZeroPercentForWhateverRating = -1; var actualZeroPercentForWhateverRating = tipCalculator.GetPercentageTipForRating("Whatever");Assert.Equal(expectedZeroPercentForWhateverRating, actualZeroPercentForWhateverRating); }We are describing our expectation when Tip Calculator is asked to return tip percentage for service rating “Whatever”. Because service rating “Whatever” is a non-legitimate rating, we are expecting Tip Calculator to return tip percentage of value -1.
當(dāng)要求Tip Calculator返回服務(wù)等級(jí)為“任何??”的小費(fèi)百分比時(shí),我們正在描述我們的期望。 由于服務(wù)等級(jí)“無(wú)論如何”都是不合法的等級(jí),因此我們希望Tip Calculator返回值-1的小費(fèi)百分比。
This test now precipitates one improvement to our shipping code. We need to add some logic to first check whether the supplied service rating is legitimate or not. Only if it is legitimate do we ask Hashtable ratingPercentages to tell us what the associated value of the tip percentage is. If the supplied service rating is non-legitimate (for example, if it is “Whatever”) we bypass talking to Hashtable ratingPercentages and simply return -1.
現(xiàn)在,此測(cè)試可對(duì)我們的運(yùn)輸代碼進(jìn)行改進(jìn)。 我們需要添加一些邏輯以首先檢查所提供的服務(wù)等級(jí)是否合法。 僅在合法的情況下,我們才要求Hashtable ratingPercentages告訴我們小費(fèi)百分比的關(guān)聯(lián)值是多少。 如果提供的服務(wù)等級(jí)Hashtable ratingPercentages (例如,如果是“ Whatever”),我們將忽略與Hashtable ratingPercentages并僅返回-1。
public int GetPercentageTipForRating(string rating) { int tipPercentage = -1; if(CheckIfRatingIsValid(rating)) { tipPercentage = Int32.Parse(ratingPercentages[rating].ToString()); } return tipPercentage; }Run the tests, and all 4 tests pass:
運(yùn)行測(cè)試,所有4個(gè)測(cè)試均通過(guò):
We are back to the steady state. Another short excursion into the volatile area, and another swift victory and a safe return to steady, imperturbable state.
我們回到了穩(wěn)定狀態(tài)。 再次短暫進(jìn)??入不穩(wěn)定地區(qū),又一次Swift獲勝,安全返回穩(wěn)定,穩(wěn)定的狀態(tài)。
填充其他服務(wù)等級(jí)提示百分比 (Populate other service rating tip percentages)
Now is a good time to take a breather and make less risky changes, following the already established pattern. Leave the safety of the steady state and make short trips into the volatile territory by adding a new test to verify if service rating “Poor” is a valid, legitimate rating:
現(xiàn)在是按照已經(jīng)建立的模式喘口氣并進(jìn)行較小風(fēng)險(xiǎn)更改的好時(shí)機(jī)。 通過(guò)添加新的測(cè)試來(lái)驗(yàn)證服務(wù)等級(jí)“差”是否是有效的合法等級(jí),從而保持穩(wěn)定狀態(tài)的安全,并短暫進(jìn)入不穩(wěn)定區(qū)域:
[Fact] public void CheckIfRatingPoorIsValid() { var expectedResponseForValidRating = true; var actualResponseForValidRating = tipCalculator.CheckIfRatingIsValid("Poor");Assert.Equal(expectedResponseForValidRating, actualResponseForValidRating); }Running this test will fail:
運(yùn)行此測(cè)試將失敗:
Service rating “Poor” hasn’t been implemented yet. To make the test pass, implement service rating “Poor” by adding this line to the TipCalculator constructor:
服務(wù)等級(jí)“差”尚未實(shí)施。 要使測(cè)試通過(guò),請(qǐng)將此行添加到TipCalculator構(gòu)造函數(shù)中,以實(shí)現(xiàn)服務(wù)等級(jí)“差”:
ratingPercentages.Add("Poor", 5);Run the tests, and we’re back to safety:
運(yùn)行測(cè)試,我們回到安全狀態(tài):
We’re enjoying steady state with 6 tests successfully passing.
我們正在通過(guò)6個(gè)測(cè)試成功通過(guò)的狀態(tài)保持穩(wěn)定。
Now that we have added service rating “Poor” associated with the 5% tip, let’s write a test that will describe that expectation:
現(xiàn)在,我們添加了與5%的小費(fèi)相關(guān)的服務(wù)評(píng)級(jí)“差”,讓我們編寫(xiě)一個(gè)測(cè)試來(lái)描述這種期望:
[Fact] public void CheckIfRatingPoorHasFivePercentTip() { var expectedZeroPercentForPoorRating = 5; var actualZeroPercentForPoorRating = tipCalculator.GetPercentageTipForRating("Poor");Assert.Equal(expectedZeroPercentForPoorRating, actualZeroPercentForPoorRating); }The tests run successfully, and we’re back to being safe in the steady state.I will leave it to the reader to make the changes that will drive the implementation of the service ratings “Good”, “Great” and “Excellent”. At the end of the exercise you should have your system back in the steady state with 12 tests successfully passing:
測(cè)試成功運(yùn)行,我們已回到穩(wěn)定的安全狀態(tài)。我將它留給讀者進(jìn)行更改,以推動(dòng)實(shí)現(xiàn)“好”,“好”和“優(yōu)秀”服務(wù)等級(jí)。 在練習(xí)結(jié)束時(shí),您應(yīng)該使系統(tǒng)恢復(fù)穩(wěn)定并成功通過(guò)12個(gè)測(cè)試:
給定總數(shù)和服務(wù)等級(jí),計(jì)算總數(shù) (Calculate grand total given the total and the service rating)
We are now ready for the final step – given the total bill and the service rating, we expect Tip Calculator to calculate tip percentage and add it to the total, producing the grand total to be paid to the restaurant.
現(xiàn)在我們準(zhǔn)備好進(jìn)行最后一步了-給定總賬單和服務(wù)等級(jí),我們希望Tip Calculator能夠計(jì)算小費(fèi)百分比并將其加到總計(jì)中,從而產(chǎn)生要支付給餐廳的總計(jì)。
As we always do, first we describe the expectation:
像往常一樣,首先我們描述期望:
[Fact] public void CalculateTotalWithTip() { var expectedTotalWithTip = 135.7; var actualTotalWithTip = 200.0; Assert.Equal(expectedTotalWithTip, actualTotalWithTip); }As usual, we first hard code some expectations that we know are going to fail. This is so that we observe our new test failing:
像往常一樣,我們首先硬編碼一些我們知道會(huì)失敗的期望。 這樣我們可以觀察到我們的新測(cè)試失敗了:
Time to implement processing logic that will calculate correct total with tip. Given the total of $118.0, and the service rating “Great” (15% tip), we’re expecting the total to be $135.7:
是時(shí)候?qū)嵤┨幚磉壿?#xff0c;用小費(fèi)計(jì)算正確的總數(shù)。 鑒于總金額為118.0美元,服務(wù)評(píng)級(jí)為“很好”(小費(fèi)15%),我們預(yù)計(jì)總金額為135.7美元:
[Fact] public void CalculateTotalWithTip() { var rating = "Great"; var total = 118; var expectedTotalWithTip = 135.7; var actualTotalWithTip = tipCalculator.CalculateTotalWithTip(total, rating); Assert.Equal(expectedTotalWithTip, actualTotalWithTip); }We have designed a new API the Tip Calculator – a method called CalculateTotalWithTip(total, rating). It takes the total value and the service rating and returns the total with tip. The implementation of the method looks like this:
我們?cè)O(shè)計(jì)了一個(gè)新的API Tip Calculator -一種稱為CalculateTotalWithTip(total, rating) 。 它獲取總價(jià)值和服務(wù)等級(jí),并帶小費(fèi)返回總和。 該方法的實(shí)現(xiàn)如下所示:
public double CalculateTotalWithTip(double total, string rating) { double totalWithTip = -1; if(CheckIfRatingIsValid(rating)) { int percentage = GetPercentageTipForRating(rating); totalWithTip = total + ((total/100) * percentage); } return totalWithTip; }Run the tests, and we’re back to steady state:
運(yùn)行測(cè)試,我們回到穩(wěn)定狀態(tài):
我們?cè)谶@里完成嗎? (Are we done here?)
No, not yet. Even when all tests are in green and we’re back to the steady state, there are still a couple of things we need to do.
還沒(méi)有。 即使所有測(cè)試都呈綠色,并且我們又回到了穩(wěn)定狀態(tài),仍然需要做幾件事。
To begin with, we need to add a pessimistic expectation for our Tip Calculator calculation of total with the tip based on the service rating:
首先,我們需要根據(jù)服務(wù)等級(jí)為T(mén)ip Calculator的總費(fèi)用添加悲觀期望:
[Fact] public void CalculateTotalWithTipForNonlegitimateRating() { var rating = "Meh"; var total = 118; var expectedTotalWithTip = 135.7; var actualTotalWithTip = tipCalculator.CalculateTotalWithTip(total, rating); Assert.Equal(expectedTotalWithTip, actualTotalWithTip); }Running the tests produces one failing test:
運(yùn)行測(cè)試會(huì)產(chǎn)生一個(gè)失敗的測(cè)試:
Our expectation for non-legitimate service rating (“Meh”) was incorrect. The actual total is -1, so we need to adjust our expectation by replacing 135.7 with -1. Run the tests again, and we’re back to the steady state!
我們對(duì)非合法服務(wù)等級(jí)(“ Meh”)的預(yù)期不正確。 實(shí)際總數(shù)為-1,因此我們需要通過(guò)將-135.7替換為-1來(lái)調(diào)整期望值。 再次運(yùn)行測(cè)試,我們回到了穩(wěn)定狀態(tài)!
We now have 14 tests, they all successfully pass, and our Tip Calculator works according to our expectations and satisfies the acceptance criteria.
現(xiàn)在,我們有14個(gè)測(cè)試,它們都成功通過(guò)了,并且我們的小費(fèi)計(jì)算器可以按照我們的期望工作并滿足驗(yàn)收標(biāo)準(zhǔn)。
We’re almost done. One more sanity check before we can confidently ship our shiny new Tip Calculator – we must run mutation testing.
我們快完成了。 在我們可以放心地發(fā)送閃亮的新Tip Calculator之前,還需要進(jìn)行一次完整性檢查-我們必須運(yùn)行突變測(cè)試 。
Our mutation testing framework will mutate the shipping code, one line at a time, and will run all tests for each individual mutation.
我們的突變測(cè)試框架將一次更改一行代碼的運(yùn)輸代碼,并將針對(duì)每個(gè)單獨(dú)的突變運(yùn)行所有測(cè)試。
If the tests complain about the mutated code, all is good, we have killed the mutant. If the tests don’t complain, we’re in trouble. We have a surviving mutant in our codebase, which means there are lines of code in our repo that are doing something for which we haven’t provided any expectations.
如果測(cè)試抱怨突變的代碼,那一切都很好,我們已經(jīng)殺死了該突變體。 如果測(cè)試沒(méi)有問(wèn)題,我們就麻煩了。 我們的代碼庫(kù)中有一個(gè)尚存的變體,這意味著我們的存儲(chǔ)庫(kù)中有一些代碼行正在做一些我們沒(méi)有想到的事情。
Let’s run mutation testing to see how solid our solution is. Good news – our solution has killed 100% of mutants!
讓我們運(yùn)行突變測(cè)試,看看我們的解決方案有多牢固。 好消息–我們的解決方案殺死了100%的突變體!
Mutation testing has given our shipping application a clean bill of health. Our Tip Calculator seems to be in good shape.
突變測(cè)試為我們的運(yùn)輸應(yīng)用程序提供了清晰的健康清單。 我們的Tip Calculator似乎狀態(tài)良好。
紅綠重構(gòu)反射 (Red-Green-Refactor-Reflect)
Let’s review our Tip Calculator building exercise. We started the process by describing our expectations using the classical user story format. User story (as the name implies) is focused on describing scenarios that fulfill end user’s goals.
讓我們回顧一下Tip Calculator構(gòu)建練習(xí)。 我們通過(guò)使用經(jīng)典用戶故事格式描述我們的期望來(lái)開(kāi)始該過(guò)程。 用戶故事(顧名思義)專(zhuān)注于描述實(shí)現(xiàn)最終用戶目標(biāo)的方案。
In this case, the simple goal is to calculate the tip amount from the supplied service rating and the restaurant bill total. The calculated tip amount is then automatically added to the total.
在這種情況下,簡(jiǎn)單的目標(biāo)是從提供的服務(wù)等級(jí)和餐廳賬單總額中計(jì)算小費(fèi)金額。 然后將計(jì)算出的小費(fèi)金額自動(dòng)添加到總數(shù)中。
From there we proceeded to build our shipping application by following the TDD methodology. As we’ve demonstrated, the methodology consists of writing a failing test, observing it fail (the Red phase of TDD), then immediately making code changes that ensure the test passes (the Green phase of TDD). Once the test passes, we move into the Refactor phase (we restructure the code without affecting its behaviour). That way, we make sure our code is not expensive to change.
從那里開(kāi)始,我們遵循TDD方法來(lái)構(gòu)建運(yùn)輸應(yīng)用程序。 如我們所展示的,該方法包括編寫(xiě)一個(gè)失敗的測(cè)試,觀察它是否失敗(TDD的紅色階段),然后立即進(jìn)行代碼更改以確保測(cè)試通過(guò)(TDD的綠色階段)。 測(cè)試通過(guò)后,我們進(jìn)入重構(gòu)階段(我們?cè)诓挥绊懫湫袨榈那闆r下重構(gòu)了代碼)。 這樣,我們確保更改代碼的代價(jià)并不昂貴。
A proper TDD practice also mandates frequent retrospective – we call it reflection. We stop and think about the things we’ve accomplished thus far, to see if we could learn from our recent experiences. This reflection fortifies the process, as it relies on frequent and tight feedback provided by the failing, then succeeding tests.
適當(dāng)?shù)腡DD做法也要求經(jīng)常進(jìn)行回顧-我們稱其為反思。 我們停下來(lái)想一想到目前為止我們已經(jīng)完成的事情,看看我們是否可以從最近的經(jīng)驗(yàn)中學(xué)到東西。 這種反映加強(qiáng)了該過(guò)程,因?yàn)樗蕾囉谑〉臏y(cè)試和隨后的測(cè)試所提供的頻繁而嚴(yán)格的反饋。
I have already compared Test Driven Development to the experience of riding a galloping horse. While riding a horse, we’re alternating between flying through the air (i.e. speed achieved when the horse leaps from the ground) and steering the horse. It is impossible to steer the horse while we’re off the ground, up in the air. At that point, we gain speed, but we cannot make any changes of the direction. It is only once the horse touches the ground that we can make a change in direction.
我已經(jīng)將“測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”與“奔馬”的體驗(yàn)進(jìn)行了比較。 騎馬時(shí),我們?cè)诳罩酗w行(即,馬從地面跳下時(shí)達(dá)到的速度)和操縱馬之間進(jìn)行切換。 當(dāng)我們離開(kāi)地面,懸而未決時(shí),不可能操縱馬匹。 在這一點(diǎn)上,我們可以提高速度,但是不能改變方向。 只有當(dāng)馬觸地時(shí),我們才能改變方向。
In TDD, we strive to touch the ground as frequently as possible. The longer the leaps we make without touching the ground, the less chance we have for correcting the course.
在TDD中,我們努力盡可能多地接觸地面。 我們?cè)诓挥|地面的情況下進(jìn)行的跳躍越長(zhǎng),糾正路線的機(jī)會(huì)就越少。
I also compared software development practices that don’t follow TDD principles to the experience of flying a kite. When flying a kite, we never touch the ground. It is an exhilarating feeling of letting the wind pick the kite up and bounce it up in the air. We can achieve considerable speed that way. But we struggle in such situations to maintain desired course. And after we eventually land the kite, it usually does not land in the spot we originally wanted it to land.
我還將不遵循TDD原理的軟件開(kāi)發(fā)實(shí)踐與放風(fēng)箏的體驗(yàn)進(jìn)行了比較。 放風(fēng)箏時(shí),我們永遠(yuǎn)不會(huì)觸地。 讓風(fēng)把風(fēng)箏拾起并在空中彈起,這是一種令人振奮的感覺(jué)。 這樣我們可以達(dá)到可觀的速度。 但是我們?cè)谶@樣的情況下努力維持預(yù)期的路線。 在我們最終放下風(fēng)箏后,它通常不會(huì)降落在我們最初希望放下的地方。
Why is the emphasis of this article on “don’t write tests first”? Many software engineers who are not familiar with agile practices as implemented in TDD usually either claim that writing automated tests isn’t necessary, or claim that automated tests should be written after the code is complete.
為什么本文強(qiáng)調(diào)“不要先編寫(xiě)測(cè)試”? 許多不熟悉TDD中實(shí)現(xiàn)的敏捷實(shí)踐的軟件工程師通常要么聲稱不需要編寫(xiě)自動(dòng)測(cè)試,要么聲稱應(yīng)該在代碼完成后編寫(xiě)自動(dòng)測(cè)試。
Once they start learning about agile and TDD, they may reconsider their practices and decide that writing tests before writing implementation code may make more sense. Still, because of the ingrained waterfall mentality, some of those engineers make the mistake of writing all tests first, and only then move into writing the code.
一旦他們開(kāi)始學(xué)習(xí)敏捷和TDD,他們可能會(huì)重新考慮他們的實(shí)踐,并決定在編寫(xiě)實(shí)現(xiàn)代碼之前編寫(xiě)測(cè)試可能更有意義。 但是,由于根深蒂固的瀑布思維,其中一些工程師犯了先編寫(xiě)所有測(cè)試,然后才編寫(xiě)代碼的錯(cuò)誤。
That approach is completely wrong. It is equivalent to the traditional waterfall approach where we go through the development process by respecting gated phases.
這種方法是完全錯(cuò)誤的。 它等效于傳統(tǒng)的瀑布方法,在這種方法中,我們會(huì)遵循選通階段來(lái)經(jīng)歷開(kāi)發(fā)過(guò)程。
First we write the requirements (in this case, requirements would be expectations written in the form of automated tests). Only once all the requirements (i.e. automated tests) have been written, signed off and frozen, do we move into the next gated phase – write the code for the shipping application.
首先,我們編寫(xiě)需求(在這種情況下,需求就是以自動(dòng)化測(cè)試的形式編寫(xiě)的期望)。 只有在所有要求(即自動(dòng)測(cè)試)均已編寫(xiě),簽字并凍結(jié)后,我們才能進(jìn)入下一個(gè)封閉階段–編寫(xiě)運(yùn)輸應(yīng)用程序的代碼。
TDD is the exact opposite of the “write tests first” approach. In TDD, we always write only one test. That test describes a desired behaviour. The desired behaviour does not exist yet (that’s why it is desired), and the test fails.
TDD與“先寫(xiě)測(cè)試”方法完全相反。 在TDD中,我們總是只編寫(xiě)一個(gè)測(cè)試。 該測(cè)試描述了期望的行為。 所需的行為尚不存在(這就是所需的原因),并且測(cè)試失敗。
We then immediately move into making changes to the code in the attempt to create the desired behaviour. Once desired behaviour is created, it gets validated by the test, and if the expectations of the test are satisfied, we move into refactoring the code (to satisfy nonfunctional requirements, such as cost of change).
然后,我們立即著手對(duì)代碼進(jìn)行更改,以嘗試創(chuàng)建所需的行為。 一旦創(chuàng)建了期望的行為,它就會(huì)通過(guò)測(cè)試進(jìn)行驗(yàn)證,如果滿足了測(cè)試的期望,我們將著手重構(gòu)代碼(以滿足非功能性需求,例如變更成本)。
We practice a rigorous discipline to never succumb to the temptation to write more than one test at a time. That way, we ensure that we keep touching the ground as frequently as possible.
我們實(shí)行嚴(yán)格的紀(jì)律,以免屈從于一次編寫(xiě)多個(gè)測(cè)試的誘惑。 這樣,我們可以確保我們盡可能頻繁地接觸地面。
We prefer to remain ‘in flight’ for the shortest possible time. We are ‘in flight’ during that period when the desired behaviour described in the test has not materialized yet. The smaller the expected and desired behaviour is, the shorter will be our ‘in flight’ trajectory. That way, we keep touching the ground often, which gives us a chance to adjust the steering.
我們希望在最短的時(shí)間內(nèi)保持“飛行”狀態(tài)。 當(dāng)測(cè)試中描述的所需行為尚未實(shí)現(xiàn)時(shí),我們正在“飛行中”。 預(yù)期和期望的行為越小,我們的“飛行中”軌跡就越短。 這樣,我們就經(jīng)常與地面接觸,這給了我們調(diào)整轉(zhuǎn)向的機(jī)會(huì)。
結(jié)論 (Conclusion)
Building a simple Tip Calculator is a toy sized problem, and using that exercise to illustrate TDD methodology is not necessarily providing a convincing argument in favour of TDD. Still, within the constraints of a technical article, going over this hands-on exercise may provide valuable insights into the benefits of adopting TDD.
建立一個(gè)簡(jiǎn)單的Tip Calculator是一個(gè)玩具大小的問(wèn)題,使用該練習(xí)來(lái)說(shuō)明TDD方法論并不一定提供支持TDD的令人信服的論點(diǎn)。 盡管如此,在技術(shù)文章的限制范圍內(nèi),繼續(xù)進(jìn)行此動(dòng)手練習(xí)可能會(huì)為采用TDD的好處提供有價(jià)值的見(jiàn)解。
We would still argue that the real benefits of TDD only become apparent when dealing with much larger, more complex software engineering efforts. The ability to remain grounded while making potentially risky changes to a large, complex system is often a life saver.
我們?nèi)匀徽J(rèn)為,只有在處理更大,更復(fù)雜的軟件工程工作時(shí),TDD的真正好處才會(huì)顯現(xiàn)出來(lái)。 在對(duì)大型復(fù)雜系統(tǒng)進(jìn)行潛在風(fēng)險(xiǎn)更改時(shí)保持接地的能力通常可以挽救生命。
In addition to that, building software using TDD methodology results in much less rework. TDD drives high degree of modularization, which results in high cohesiveness of the modules and low coupling between the modules.
除此之外,使用TDD方法構(gòu)建軟件可以減少返工。 TDD驅(qū)動(dòng)了高度的模塊化,這導(dǎo)致了模塊的高內(nèi)聚性和模塊之間的低耦合。
All these characteristics produce a shipping application whose codebase is easy and inexpensive to change. And lowering the cost of change has proven to be the best way on the path to embracing changes and abandoning the concept known as ‘scope creep’.
所有這些特征產(chǎn)生了一個(gè)運(yùn)輸應(yīng)用程序,其代碼庫(kù)易于更改且成本低廉。 事實(shí)證明,降低變更成本是擁抱變更并摒棄“范圍蔓延”概念的最佳途徑。
Bottom line, TDD enables software engineering teams to deliver high degree of flexibility to the business.
總而言之,TDD使軟件工程團(tuán)隊(duì)能夠?yàn)闃I(yè)務(wù)提供高度的靈活性。
翻譯自: https://www.freecodecamp.org/news/dont-write-all-your-software-tests-first-just-write-one/
軟件測(cè)試測(cè)試用例編寫(xiě)
總結(jié)
以上是生活随笔為你收集整理的软件测试测试用例编写_不要先编写所有软件测试-只需编写一个的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 渗透测试初学者_渗透测试许可证:面向初学
- 下一篇: 梦到自己胳膊受伤了怎么回事