软件测试测试用例编写
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.
测试驱动开发(TDD)有时被称为“首先编写测试”。 TDD的口头禅规定,在编写用于执行该代码的自动测试之前,请勿编写代码。 首先编写代码被认为是次优的。
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 ‘application building’ stage, we have an ‘application testing’ stage, we have an ‘application deployment’ stage and so on.
当然,首先编写代码是我们遵循所谓的瀑布模型开发软件的方式。 在该模型中,我们将软件开发活动划分为多个阶段。 例如,我们有一个“需求收集”阶段,有一个“应用程序构建”阶段,有一个“应用程序测试”阶段,有一个“应用程序部署”阶段,依此类推。
But how is that different from the agile methodology? Don’t we have the exact same stages in agile?
但是,这与敏捷方法学有何不同? 我们在敏捷中没有完全相同的阶段吗?
敏捷方法论与瀑布方法论 (Agile Methodology vs Waterfall Methodology)
Of course we do. The crucial difference is that in agile, those stages are not gated.
当然可以 关键的区别在于,在敏捷开发中,这些阶段不会受到限制。
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.
在瀑布中,我们对阶段进行门控并严格执行。 这意味着直到需求被收集,完成,签署并冻结后,我们才会开始构建运输应用程序。 一旦需求被冻结(并由我们的变更管理策略控制),我们便进入下一阶段(或阶段)–应用程序构建。
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.
同样,在整个应用程序都已构建并且达到代码完整的里程碑之前,我们不会进入测试阶段,此时代码更改已被冻结。
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.
一旦代码被冻结(然后冻结由我们的变更管理策略控制),我们便将其交给测试人员。 测试阶段开始,只有完成所有测试(并且前提是未检测到重大缺陷)后,我们才进入部署阶段。
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.
在敏捷中,我们同时进行上述所有活动。 与此同时。 我们会继续处理用户案例(规格),同时构建运输应用程序。 在构建应用程序时,我们也在对其进行测试。 在构建和测试应用程序时,我们也在部署它。
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.
我们从部署到生产中的运输应用程序中学习,并将经过验证的学习用作将为新用户故事提供信息的反馈。 这样,循环就闭合了,我们进行迭代,逐步提高了价值。
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.
实现这种迭代式价值流交付的唯一方法是依靠自动化测试。 正如我们已经描述的那样,这些测试是在游戏的早期阶段编写的。 实际上,在编写运输代码之前必须先编写测试。
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:
那么,为什么标题为“不要先编写所有测试,只编写一个”? 听起来有点混乱。 让我们解开此标题的含义。 但是首先,这是我们将使用的技术的概述:
本练习使用的技术栈 (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
.
为了使练习简单易行,我选择了.NET Core平台以及xUnit.net测试平台。 要遵循编码示例,请安装.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
:
为了能够运行示例代码,请打开./tests/tests.csproj
文件并将此行添加到ItemGroup
:
<ProjectReference Include="../app/app.csproj" />
You’re now all set for following the coding exercises.
现在您已经准备好进行编码练习。
一个简单的例子 (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.
要了解先编写所有测试与先编写一个测试之间的区别,最好显示而不是仅仅讲。
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.
因此,让我们尝试建立一个简单的示例-在本练习中,我选择了一个在餐厅计算小费的简单案例。 通常,我们发现自己想要为餐厅提供服务小费,但是很难计算出我们所占的百分比。 因此,一个漂亮的小Tip Calculator
可能会派上用场。
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
作为顾客我想计算总账单(总计加上小费),因为我想对餐厅的服务表示赞赏
方案1:赞助人计算可怕服务的总数 (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
假设餐厅的总价为$ 100.00并且服务很糟糕,当小费计算器计算总费用时,小费计算器将显示$ 100.00的总费用
方案2:顾客计算服务质量差的总数 (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
假设餐厅的总价为$ 100.00并且服务很差,当小费计算器计算总费用时,小费计算器将显示$ 105.00的总费用
方案3:顾客计算出良好服务的总金额 (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
假设餐厅的总价为$ 100.00并且服务很好,则当小费计算器计算总费用时,小费计算器将显示$ 110.00的总费用
方案4:顾客计算出优质服务的总额 (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
假设餐厅的总价为$ 100.00,服务很棒,那么当小费计算器计算总费用时,小费计算器就会显示$ 115.00的总费用
方案5:顾客计算出优质服务的总额 (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
假设餐厅的总价为$ 100.00,服务非常好,当小费计算器计算总费用时,小费计算器显示总费用为$ 120.00
Let’s now implement the above user story.
现在,让我们实现上面的用户故事。
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:
我们看到这个故事有5个接受标准(也称为方案)。 现在,我们进入分析阶段-考虑一下Tip Calculator
应用程序应该实现的第一个功能。 但是首先,让我们打开命令行终端并创建新目录:
md TipCalculator
cd TipCalculator
and create app
and tests
directories inside the TipCalculator
directory.
并在TipCalculator
目录中创建app
和tests
目录。
Now cd tests
and run:
现在进行cd tests
并运行:
dotnet new xunit
Then cd ..
and cd app
, then run:
然后cd ..
和cd app
,然后运行:
dotnet new classlib
We’re now ready to boogie!
我们现在可以开始聊天了!
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
?
打开您最喜欢的文本编辑器(我的是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:
为了缩小我们的期望范围,通常有助于采用一个接受标准(即一种情况)并首先关注它。 让我们来看场景1:
方案1:赞助人计算可怕服务的总数 (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
假设餐厅的总价为$ 100.00并且服务很糟糕,当小费计算器计算总费用时,小费计算器显示总费用为$ 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?
万一服务糟糕,我们不会添加任何小费, Tip Calculator
会计算$ 0.00小费。 那么,如何使这种情况自动化?
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.
我的第一个期望是,我们需要以某种方式告知Tip Calculator
该服务很糟糕。 我们可以在输入字段中输入单词“ Terrible”,或者从可用服务等级列表中选择“ 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.
因此,这里要做的第一件事是阐明有关Tip Calculator
能够获得通知该服务很糟糕的能力的一些期望。
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.
我总是希望用户输入的内容有效。 因此,我首先要编写一个测试,检查“ Tip Calculator
是否将“可怕”等级识别为有效等级。
Go to the tests
directory, rename the autogenerated UnitTest1.cs
file to TipCalculatorTests.cs
and add the following test:
转到tests
目录,将自动生成的UnitTest1.cs
文件重命名为TipCalculatorTests.cs
并添加以下测试:
[Fact]
public void CheckIfRatingTerribleIsValid(){
var expectedResponseForValidRating = true;
var actualResponseForValidRating = false;
Assert.Equal(expectedResponseForValidRating,
actualResponseForValidRating);
}
Now go to the command line, cd tests
, and run:
现在转到命令行cd tests
并运行:
dotnet test
Of 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.
当然,上述微不足道的测试将失败,因为我们已经对值进行了硬编码。 但是,在继续进行之前,确保我们看到测试失败是一种很好的做法。 如果没有测试失败,则不遵守测试失败可能会在以后给我们带来错误的安全感,而我们最终认为一切都会按预期进行。
A few more observations about the above test:
关于上述测试的更多观察结果:
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.如果测试名称具有描述性,它将很有帮助。 我选择
CheckIfRatingTerribleIsValid
来传达这样一个事实,即我们必须确保我们的应用程序能够识别我们的命令。It also helps if the expected and actual variable names are descriptive. I chose
expectedResponseForValidRating
andactualResponseForValidRating
as fairly indicative of what our expectation in this test is, and also what actual value will theTip Calculator
produce.如果期望的变量名和实际的变量名是描述性的,这也有帮助。 我选择
expectedResponseForValidRating
和actualResponseForValidRating
得到公平意味着就是我们在本次测试的期望是,也什么实际价值,并将在Tip Calculator
产品。- Test is a first-class source code and must be approached with equal care lavished upon the production code. 测试是一流的源代码,必须以与生产代码相当的谨慎对待。
最初的设计决定 (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?
在这一点上,我们不得不做出决定-我们新生的Tip Calculator
如何知道用户提供的服务等级是否有效?
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.
想到的设计决定是Tip Calculator
必须能够存储和检索一些数据。 在这种情况下,我们感兴趣的数据就是服务等级。
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:
如果我们回到用户故事并回顾五个接受标准,我们将看到, Tip Calculator
必须能够识别五个不同的服务等级:
- Terrible 可怕
- Poor 较差的
- Good 好
- Great 大
- Excellent 优秀的
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
存储该信息的最简单方法是为其赋予数组或列表。
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.
但是,我们不要急于执行该列表,而应该再次检查期望,以查看是否还有其他我们可能错过的事情。 并且–不仅Tip Calculator
必须能够识别有效的服务等级,还必须能够将每个等级与百分比值相关联。
Our analysis shows the following associations:
我们的分析显示以下关联:
- Terrible => 0% 很差=> 0%
- Poor => 5% 差=> 5%
- Good => 10% 好=> 10%
- Great => 15% 很好=> 15%
- Excellent => 20% 优秀=> 20%
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.
在这种情况下,简单的数组或简单的列表不足以容纳上述关联。 允许我们实现这些关联的下一个最简单的数据结构是什么? 经过一些研究,我们发现Hashtable
可能是最合适的数据结构,可以用最少的仪式满足我们的需求。
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:
现在,我们导航到app
目录,并将自动生成的Class1.cs
文件重命名为TipCalculator.cs
。 现在,我们要添加一个Hashtable
,该Hashtable
表将保存服务评级和相关的百分比值:
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.
现在是回想一下TDD专注于将期望与应用程序的行为而非应用程序的结构耦合的好时机。 知道这一点,我们需要修改测试以使“ Tip Calculator
表现出某些行为。 该测试将有关应用程序必须如何行为的一些期望汇总起来,正在运行的应用程序提供了预期行为的证据。
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.
但是,该应用程序行为的证据是什么? 除了检查正在运行的应用程序产生的值之外,我们没有其他方法可以评估和评估应用程序的行为。
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.
在这种情况下,我们希望正在运行的应用程序在询问某个值(例如,服务等级)是否有效之后会产生值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:
要教应用程序如何以预期的方式工作,我们需要为它提供一个API。 在这种情况下,我们将API设计如下:
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:
在我们的测试中,我们将修改实际期望值以执行正在运行的应用程序并收集输出值:
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.
从上面的屏幕快照中可以看到,我们已经实例化了TipCalculator
但是当尝试要求实例检查所提供的等级(“可怕”)是否有效时,编辑器抱怨说找不到该方法。
Well of course, the method hasn’t been implemented yet. Now’s the time to go ahead and do it:
当然,该方法尚未实现。 现在是时候继续做下去了:
public bool CheckIfRatingIsValid(string rating) {
return false;
}
Now that the method is implemented, the test works; here is the complete listing:
现在已经实现了该方法,测试就可以进行; 这是完整的清单:
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.
从上面的示例中我们看到我们再次作弊(在我们新创建的方法中,我们已经硬编码return false;)。 在灌木丛中跳动,仅创建骨架和脚手架,而不是卷起袖子并进行实际编码,有什么意义? 让我们讨论这个重要的话题。
讨论我们的第一个设计决策 (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.
我们在这里说明如何逐步进行TDD。 有趣的部分是,此分步说明实际上是我们进行TDD的确切方法:分步。 除了逐步进行之外,没有其他方法可以进行TDD。 一步一步来。
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:
与进行软件开发的任何其他方式有何不同? 即使不遵循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对我来说就像骑着骏马。 我们正在Swift向目标迈进,但我们经常碰到地面(奔腾的马现在不时地撞到地面,以便反弹并快速奔跑)。
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).
相比之下,当我在没有TDD的情况下进行软件开发时,对我来说就像在放风筝。 我正在用风筝快速移动,但我从未触地,甚至没有触地。 到我放风筝时,降落的地方可能不是我想要放风筝的地方(如果风很大,很难控制风筝的方向)。
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时,只要我们更改代码(测试代码和运输应用程序代码),就可以运行测试,因此就可以使用。 我们在疾驰,但同时我们需要这种频繁的接地。 我们需要查看我们是否朝着正确的方向前进,以及在疾驰过程中是否损坏了任何东西。 我们的测试是Oracle,它会不断告诉我们一切是否按预期进行,或者某些事情开始出现异常。
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.
更改代码是一项冒险的业务。 TDD提供了一个很好的工具,既可以指导我们的设计决策,又可以确保我们不会弄乱我们已经确认能达到预期效果的东西。
用实际的处理逻辑替换硬编码的值 (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:
现在让我们用实际的运行代码替换硬编码的值。 我们首先告诉我们的Tip Calculator
,有一个服务评级为“可怕”,并且与该评级相关的小费百分比为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:
现在,我们的Tip Calculator
可以了解以下事实:服务等级标记为“糟糕”,并且与糟糕服务相关的小费百分比为0%。 很好,但是我们仍然返回硬编码值false
。 是时候用实际计算替换它了:
public bool CheckIfRatingIsValid(string rating) {
ratingPercentages.Add("Terrible", 0);
return ratingPercentages.ContainsKey(rating);
}
Run the test again:
再次运行测试:
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.
很好,但是代码看起来还是人为的。 我们正在将“可怕”值加载到Hashtable ratingPercentages
实例中,然后立即检查该值是否存在于Hashtable
。 现在我们已经从失败测试(红色)移至通过测试(绿色),是时候执行TDD循环中的第三步–重构。
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.
重构基本上是在不影响代码行为的情况下修改代码结构的活动。 我们的任务很简单:将负责填充Hashtable ratingPercentages
的代码提取到单独的代码块中。
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:
进行加载的最自然的地方是进行Tip Calculator
初始化的代码块- constructor
方法。 重构后,我们的运输应用程序源代码如下所示:
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.
再次运行测试,测试通过(绿色)。 我们已经修改了代码的结构,而没有修改其行为! 做得好。
掷硬币 (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.
每当我们满足积极的期望时,明智的做法就是转过头去描述消极的期望。
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
.
在这一点上,由于我们已经满意在Tip Calculator
找到了合法的服务评级值,因此我们要确保在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):
非合法价值是什么意思? “差”,“差”,“好”,“好”和“优秀”以外的任何值。 是时候写出新的期望了(即测试):
[Fact]
public void CheckIfRatingWhateverIsValid() {
var expectedResponseForValidRating = true;
var actualResponseForValidRating =
tipCalculator.CheckIfRatingIsValid("Whatever");
Assert.Equal(expectedResponseForValidRating,
actualResponseForValidRating);
}
Run the tests:
运行测试:
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”.)
失败 如预期的那样。 (我们指定在提供“任何”服务等级时的期望应该是true
。实际上,这是false
,因为我们的Tip Calculator
不包含值“任何”。)
Fix the test (change the expectedResponseForValidRating
from true
to false
) and run it again:
修复测试(将expectedResponseForValidRating
从true
更改为false
)并再次运行它:
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.
片刻的反思:为什么我们要伪造第一次测试并使其失败? 因为我们一直想确保我们观察到新测试失败。 这样,我们就会知道,将来任何成功通过测试的都不仅仅是假阳性。
赞美稳态 (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?
软件工程是稳定状态和不稳定状态周期之间的平衡行为。 稳定状态是什么意思?
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.
如果我们有一个系统(正在运行的应用程序)以我们期望的方式运行(即产生我们指定为期望值的值),则我们声明该系统处于稳定状态。 它正在运行,并提供了一些价值。
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.
价值传递仍然是部分的。 在我们的案例中,此Tip Calculator
对用户而言是唯一的价值 交付是其将服务等级“糟糕”识别为合法等级的能力。 此外,它还可以告知我们服务等级“无论如何”都不是合法的等级。
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.
数量不多,但总比没有好。 好消息–我们正在运行的应用程序目前处于稳定状态。 现在,我们要研究如何向“ Tip Calculator
添加更多有价值的行为。 而增加价值的唯一方法是进行一些更改。
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.
每当我们对应用程序进行更改时,我们都会破坏其稳态。 这种干扰是危险的。 这可能意味着我们的更改可能会破坏已经起作用的某些功能。 因此,我们努力使这种不稳定状态的持续时间尽可能短。
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.
还记得我们如何将TDD与骑马驰horse相提并论吗? 当马在飞行中(即不接触地面)时,它正在向我们的目标前进,但它不是处于稳定状态。 只有当马接触地面时,其状态才会稳定。
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鼓励进行微小的更改(在飞行中),并通过验证系统是否处于稳定状态来立即将其接地。 尽管我们热切地接受变化,但我们仍重视稳定状态。 没有变化,我们将无法交付价值,但是我们必须以一种非常谨慎,谨慎的方式来实现它。
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.
在进行TDD时,我们像在蛋壳上行走一样对待稳态的变化。 无论我们有多确定要知道什么以及如何进行软件工程,还是要谨慎地让失败的测试来指导我们的决策。
检查是否正确的小费百分比与服务等级相关联 (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:
现在,让我们在应用程序中进行另一个更改–测试以验证正确的笔尖百分比是否与“可怕”服务等级相关联。 请记住,我们使用以下值填充了Hashtable ratingPercentages
实例:
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.
我们编写了一个测试,以验证我们的Hashtable ratingPercentages
是否包含合法的服务等级“ Terrible”。 现在,我们需要进行一项测试,以验证服务等级“糟糕”是否意味着该等级的小费百分比为0。
[Fact]
public void CheckIfRatingTerribleHasZeroPercentTip() {
var expectedZeroPercentForTerribleRating = 0;
var actualZeroPercentForTerribleRating = 10;
Assert.Equal(expectedZeroPercentForTerribleRating,
actualZeroPercentForTerribleRating);
}
The new test CheckIfRatingTerribleHasZeroPercentTip
should fail:
新测试CheckIfRatingTerribleHasZeroPercentTip
应该失败:
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:
同样,我们有目的地硬编码错误的实际值,以便可以观察到全新的测试失败。 现在,我们必须用对Tip Calculator
方法的实际调用替换硬编码的值,该方法返回服务等级的提示百分比:
[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.
与前面的情况一样,我们为Tip Calculator
发明了一个新的API。 我们将此新功能GetPercentageTipForRating("Terrible")
。 它采用服务等级的值,并返回该等级的小费百分比。
Flip over to the app/TipCalculator.cs
and add the hard coded skeleton of the new method:
翻转到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:
由于我们已经对返回值进行了硬编码,因此运行测试再次失败。 让我们用实际处理代替它:
public int GetPercentageTipForRating(string rating) {
int tipPercentage = Int32.Parse(ratingPercentages[rating].ToString());
return tipPercentage;
}
Run the test again:
再次运行测试:
All three tests pass – we’re in green, we’re back to steady state!
所有三个测试均通过–我们处于绿色状态,我们回到了稳定状态!
对于非合法的服务评级,我们期望什么小费百分比? (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”).
在该领域的多年经验教会我有些悲观。 现在我们已经使应用程序恢复到稳定状态并交付了价值(回答有关合法服务等级的问题,并为我们提供了“可怕”等级的正确提示百分比),我们需要通过提供应用程序来查看运行应用程序时会发生什么不合法的服务等级值(例如,将其服务等级定为“ Whatever”)。
Time for leaving the steady state yet again. We will write another test:
是时候再次离开稳定状态了。 我们将编写另一个测试:
[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.
当要求Tip Calculator
返回服务等级为“任何”的小费百分比时,我们正在描述我们的期望。 由于服务等级“无论如何”都是不合法的等级,因此我们希望Tip Calculator
返回值-1的小费百分比。
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.
现在,此测试可对我们的运输代码进行改进。 我们需要添加一些逻辑以首先检查所提供的服务等级是否合法。 仅在合法的情况下,我们才要求Hashtable ratingPercentages
告诉我们小费百分比的关联值是多少。 如果提供的服务等级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:
运行测试,所有4个测试均通过:
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.
我们回到了稳定状态。 再次短暂进入不稳定地区,又一次Swift获胜,安全返回稳定,稳定的状态。
填充其他服务等级提示百分比 (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:
现在是按照已经建立的模式喘口气并进行较小风险更改的好时机。 通过添加新的测试来验证服务等级“差”是否是有效的合法等级,从而保持稳定状态的安全,并短暂进入不稳定区域:
[Fact]
public void CheckIfRatingPoorIsValid() {
var expectedResponseForValidRating = true;
var actualResponseForValidRating =
tipCalculator.CheckIfRatingIsValid("Poor");
Assert.Equal(expectedResponseForValidRating,
actualResponseForValidRating);
}
Running this test will fail:
运行此测试将失败:
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:
服务等级“差”尚未实施。 要使测试通过,请将此行添加到TipCalculator
构造函数中,以实现服务等级“差”:
ratingPercentages.Add("Poor", 5);
Run the tests, and we’re back to safety:
运行测试,我们回到安全状态:
We’re enjoying steady state with 6 tests successfully passing.
我们正在通过6个测试成功通过的状态保持稳定。
Now that we have added service rating “Poor” associated with the 5% tip, let’s write a test that will describe that expectation:
现在,我们添加了与5%的小费相关的服务评级“差”,让我们编写一个测试来描述这种期望:
[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:
测试成功运行,我们已回到稳定的安全状态。我将它留给读者进行更改,以推动实现“好”,“好”和“优秀”服务等级。 在练习结束时,您应该使系统恢复稳定并成功通过12个测试:
给定总数和服务等级,计算总数 (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.
现在我们准备好进行最后一步了-给定总账单和服务等级,我们希望Tip Calculator
能够计算小费百分比并将其加到总计中,从而产生要支付给餐厅的总计。
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:
像往常一样,我们首先硬编码一些我们知道会失败的期望。 这样我们可以观察到我们的新测试失败了:
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:
是时候实施处理逻辑,用小费计算正确的总数。 鉴于总金额为118.0美元,服务评级为“很好”(小费15%),我们预计总金额为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:
我们设计了一个新的API Tip Calculator
-一种称为CalculateTotalWithTip(total, rating)
。 它获取总价值和服务等级,并带小费返回总和。 该方法的实现如下所示:
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:
运行测试,我们回到稳定状态:
我们在这里完成吗? (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.
还没有。 即使所有测试都呈绿色,并且我们又回到了稳定状态,仍然需要做几件事。
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:
首先,我们需要根据服务等级为Tip Calculator
的总费用添加悲观期望:
[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:
运行测试会产生一个失败的测试:
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!
我们对非合法服务等级(“ Meh”)的预期不正确。 实际总数为-1,因此我们需要通过将-135.7替换为-1来调整期望值。 再次运行测试,我们回到了稳定状态!
We now have 14 tests, they all successfully pass, and our Tip Calculator works according to our expectations and satisfies the acceptance criteria.
现在,我们有14个测试,它们都成功通过了,并且我们的小费计算器可以按照我们的期望工作并满足验收标准。
We’re almost done. One more sanity check before we can confidently ship our shiny new Tip Calculator
– we must run mutation testing.
我们快完成了。 在我们可以放心地发送闪亮的新Tip Calculator
之前,还需要进行一次完整性检查-我们必须运行突变测试 。
Our mutation testing framework will mutate the shipping code, one line at a time, and will run all tests for each individual mutation.
我们的突变测试框架将一次更改一行代码的运输代码,并将针对每个单独的突变运行所有测试。
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.
如果测试抱怨突变的代码,那一切都很好,我们已经杀死了该突变体。 如果测试没有问题,我们就麻烦了。 我们的代码库中有一个尚存的变体,这意味着我们的存储库中有一些代码行正在做一些我们没有想到的事情。
Let’s run mutation testing to see how solid our solution is. Good news – our solution has killed 100% of mutants!
让我们运行突变测试,看看我们的解决方案有多牢固。 好消息–我们的解决方案杀死了100%的突变体!
Mutation testing has given our shipping application a clean bill of health. Our Tip Calculator
seems to be in good shape.
突变测试为我们的运输应用程序提供了清晰的健康清单。 我们的Tip Calculator
似乎状态良好。
红绿重构反射 (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
构建练习。 我们通过使用经典用户故事格式描述我们的期望来开始该过程。 用户故事(顾名思义)专注于描述实现最终用户目标的方案。
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.
在这种情况下,简单的目标是从提供的服务等级和餐厅账单总额中计算小费金额。 然后将计算出的小费金额自动添加到总数中。
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.
从那里开始,我们遵循TDD方法来构建运输应用程序。 如我们所展示的,该方法包括编写一个失败的测试,观察它是否失败(TDD的红色阶段),然后立即进行代码更改以确保测试通过(TDD的绿色阶段)。 测试通过后,我们进入重构阶段(我们在不影响其行为的情况下重构了代码)。 这样,我们确保更改代码的代价并不昂贵。
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.
适当的TDD做法也要求经常进行回顾-我们称其为反思。 我们停下来想一想到目前为止我们已经完成的事情,看看我们是否可以从最近的经验中学到东西。 这种反映加强了该过程,因为它依赖于失败的测试和随后的测试所提供的频繁而严格的反馈。
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.
我已经将“测试驱动开发”与“奔马”的体验进行了比较。 骑马时,我们在空中飞行(即,马从地面跳下时达到的速度)和操纵马之间进行切换。 当我们离开地面,悬而未决时,不可能操纵马匹。 在这一点上,我们可以提高速度,但是不能改变方向。 只有当马触地时,我们才能改变方向。
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中,我们努力尽可能多地接触地面。 我们在不触地面的情况下进行的跳跃越长,纠正路线的机会就越少。
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原理的软件开发实践与放风筝的体验进行了比较。 放风筝时,我们永远不会触地。 让风把风筝拾起并在空中弹起,这是一种令人振奋的感觉。 这样我们可以达到可观的速度。 但是我们在这样的情况下努力维持预期的路线。 在我们最终放下风筝后,它通常不会降落在我们最初希望放下的地方。
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.
为什么本文强调“不要先编写测试”? 许多不熟悉TDD中实现的敏捷实践的软件工程师通常要么声称不需要编写自动测试,要么声称应该在代码完成后编写自动测试。
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.
一旦他们开始学习敏捷和TDD,他们可能会重新考虑他们的实践,并决定在编写实现代码之前编写测试可能更有意义。 但是,由于根深蒂固的瀑布思维,其中一些工程师犯了先编写所有测试,然后才编写代码的错误。
That approach is completely wrong. It is equivalent to the traditional waterfall approach where we go through the development process by respecting gated phases.
这种方法是完全错误的。 它等效于传统的瀑布方法,在这种方法中,我们会遵循选通阶段来经历开发过程。
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.
首先,我们编写需求(在这种情况下,需求就是以自动化测试的形式编写的期望)。 只有在所有要求(即自动测试)均已编写,签字并冻结后,我们才能进入下一个封闭阶段–编写运输应用程序的代码。
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与“先写测试”方法完全相反。 在TDD中,我们总是只编写一个测试。 该测试描述了期望的行为。 所需的行为尚不存在(这就是所需的原因),并且测试失败。
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).
然后,我们立即着手对代码进行更改,以尝试创建所需的行为。 一旦创建了期望的行为,它就会通过测试进行验证,如果满足了测试的期望,我们将着手重构代码(以满足非功能性需求,例如变更成本)。
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.
我们实行严格的纪律,以免屈从于一次编写多个测试的诱惑。 这样,我们可以确保我们尽可能频繁地接触地面。
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.
我们希望在最短的时间内保持“飞行”状态。 当测试中描述的所需行为尚未实现时,我们正在“飞行中”。 预期和期望的行为越小,我们的“飞行中”轨迹就越短。 这样,我们就经常与地面接触,这给了我们调整转向的机会。
结论 (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.
建立一个简单的Tip Calculator
是一个玩具大小的问题,使用该练习来说明TDD方法论并不一定提供支持TDD的令人信服的论点。 尽管如此,在技术文章的限制范围内,继续进行此动手练习可能会为采用TDD的好处提供有价值的见解。
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.
我们仍然认为,只有在处理更大,更复杂的软件工程工作时,TDD的真正好处才会显现出来。 在对大型复杂系统进行潜在风险更改时保持接地的能力通常可以挽救生命。
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方法构建软件可以减少返工。 TDD驱动了高度的模块化,这导致了模块的高内聚性和模块之间的低耦合。
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’.
所有这些特征产生了一个运输应用程序,其代码库易于更改且成本低廉。 事实证明,降低变更成本是拥抱变更并摒弃“范围蔓延”概念的最佳途径。
Bottom line, TDD enables software engineering teams to deliver high degree of flexibility to the business.
总而言之,TDD使软件工程团队能够为业务提供高度的灵活性。
翻译自: https://www.freecodecamp.org/news/dont-write-all-your-software-tests-first-just-write-one/
软件测试测试用例编写