前后加编码_如何不加思考地编码?

前后加编码

介绍 (Introduction)

While observing different teams and individual developers failing to establish a test-driven development process, I follow a TDD recipe that works fine for me since a couple of years. In this article, I outline possible reasons why TDD doesn't work (when it doesn't) and suggest a step-by-step algorithm that has led me to using TDD as a natural software development approach.

在观察不同的团队和个体开发人员未能建立测试驱动的开发流程的同时,我遵循了TDD的方法,这种方法对我来说已经好几年了。 在本文中,我概述了TDD不起作用(可能不起作用)的可能原因,并提出了逐步的算法,促使我将TDD用作自然的软件开发方法。

如何阅读? (How to Read?)

Left-to-right, top-to-bottom. To read this article with less effort, Section Test-driven implementation of a feature can be skipped. It develops a detailed process of handling a change request or a late improvement in a TDD-style. This might be useful for some readers, but skipping it doesn’t destroy the consistency of the remaining content. From my standpoint at least.

从左到右,从上到下。 为了更轻松地阅读本文,可以跳过部分测试驱动的功能实现 。 它开发了处理变更请求或TDD风格的后期改进的详细过程。 这对某些读者可能有用,但是跳过它不会破坏其余内容的一致性。 至少从我的角度来看。

什么是“吞噬者”? (What Is the Effort Devourer?)

As I heard once from an Austrian SCRUM guru, Andreas Wintersteiger, "all useful productive code you write in a week, you can write on Friday afternoon". Maybe he even didn't say "productive", I don't remember.

正如我曾经听过奥地利SCRUM专家Andreas Wintersteiger所说,“您一周内编写的所有有用的生产代码,都可以在周五下午编写”。 我可能不记得他甚至没有说“富有成效”。

What is difficult to argue is that, if you keep only the useful code, you will find that typing it down didn't cost much in comparison with thinking about what this code should do (that is the behaviour), how do you implement it (e.g., how do you compose the LINQ expressions), and why doesn't it work as you expect it to (debugging). It can't be a two-digit factor between the typing effort and the rest.

很难争论的是,如果只保留有用的代码,就会发现键入下来与思考该代码应该做什么(即行为),如何实现它相比并不会花费太多。 (例如,如何组成LINQ表达式),以及为什么它不按预期运行(调试)。 在打字工作量和其余工作之间,这不能是两位数的因素。

It isn't so much that we think more than we type. The point is that we think on very different things simultaneously: the code behaviour, the implementation details, side effects... That's why it takes longer. At that, the output isn't that good: we will very likely miss something. In this way, we produce more bugs, we have more to refactor, and this again brings us back to the same circle, with less time to deadline left and thus less time to stop and to improve the process.

我们思考的不只是打字。 关键是我们要同时考虑非常不同的事情:代码行为,实现细节,副作用……这就是为什么需要更长的时间。 那样的话,输出效果不是很好:我们很可能会错过一些东西。 这样,我们产生了更多的错误,我们有更多的重构空间,这又使我们回到了同一个圈子,剩余的截止时间减少了,因此停止和改进流程的时间也减少了。

So, if we start coding without a detailed design, our thinking is inefficient because of too frequent context switches. On the other hand, we won't put every public method into a sequence diagram, will we?

因此,如果我们在没有详细设计的情况下开始编码,则由于上下文切换太频繁,我们的思维效率很低。 另一方面,我们不会将每个公共方法都放在序列图中,是吗?

What makes the thing even worse is that, as the behaviour complexity grows, our thinking tends to be chaotic. We hectically jump between different parts of the productive code and different behaviour cases, every time losing our efficiency and producing new potential design and code issues. It looks like it has a cumulative effect. It has.

更糟糕的是,随着行为复杂性的提高,我们的思维趋于混乱。 我们每次都会在生产性代码的不同部分和不同的行为情况之间跳转,每次都会失去效率,并产生新的潜在设计和代码问题。 看起来它具有累积效果。 它有。

窄焦点策略 (Narrow-Focus Strategy)

But how can we control our thoughts? How can I forbid myself to think about "how?" when I'm thinking about "what?"

但是我们如何控制思想呢? 我如何禁止自己思考“如何”? 当我在思考“什么?”时

Well, this is a skill, it can be and ought to be trained.

好吧,这是一种技能,可以而且应该得到训练。

But there is a recipe, too. I can put the "how?" things into another room, even at another floor. I can separate the thinking about my code behaviour and the implementation work so far from each other in time and in space, that it won't be even possible to mix them up.

但是也有一个食谱。 我可以放“如何?” 东西进入另一个房间,甚至在另一个楼层。 我可以将关于我的代码行为和实现工作的想法在时间和空间上彼此分开,以至于不可能将它们混合在一起。

Here is how it works for me.

这是对我有效的方式。

Read the user story and describe the unit and integration tests in the form of test method names (yes, just as they say in the books), like:

阅读用户案例并以测试方法名称的形式描述单元测试和集成测试(是的,就像书中所说的一样),例如:

public void Ctor_Initializes_EmployeeName_WithPassedParameter() 
{ 
   Assert.Inconclusive();
} 

Write down all the test cases you can figure out at the user story basis. Just keep writing them down, one day, two days, even more. Stick with it. Write no test code, even less productive code, so long as there is at least one uncovered behaviour case you can think of within the story scope.

写下您可以根据用户案例确定的所有测试用例。 只要继续写下来,一天,两天甚至更多。 坚持下去。 只要您可以在故事范围内想到至少一个未发现的行为案例,就无需编写测试代码,甚至编写效率更低的代码。

Yes, you will be staying at multiple daily SCRUM meetings in a row and saying "Yesterday I wrote test definitions. Will proceed with it today. Have no impediments." Have fun, you're welcome.

是的,您将连续参加多个SCRUM每日会议,并说:“昨天我编写了测试定义。今天将继续进行。没有任何障碍。” 玩得开心,不客气。

Yes, it needs some confidence, indeed.

是的,确实需要一定的信心。

What is your gain, apart from the respect of your teammates?

除了尊重队友之外,您还能获得什么?

The gain is the effectiveness of your thinking on the code behaviour. You cannot disperse your mind over different things, because you simply aren't working on them. You stay concentrated (narrow-focused) on the behaviour only, thus giving yourself the best chance not to forget anything, which is so difficult to implement at a later point, when you think (and report!) you are almost done.

收获是您对代码行为的思考的有效性。 您无法将注意力分散在不同的事物上,因为您根本不在工作。 您只专注于行为(狭窄地专注于行为),从而为自己提供了不遗忘任何事情的最佳机会,这在以后(当您认为(并报告)!) 差不多完成时很难实现。

设计阶段:专注于测试骨架 (The Design Phase: Focus on the Test Skeleton)

Well, you can pack it nicely. I mean for the daily stand-ups.

好吧,您可以很好地打包它。 我的意思是每天站起来。

This is your design phase.

这是您的设计阶段

On the one hand, this is your design. On the other, you are writing placeholders for the future automated tests. You will then be forced either to implement and to green all of them, or to remove some, thus explicitly cancelling the related behaviour cases. So, at the end of thorough analysis and design, you will have a complete test suite that automatically... Well, there is no need to discuss, how good it is to have a complete automated test suite.

一方面,这是您的设计。 另一方面,您正在为未来的自动化测试编写占位符。 然后,您将被迫实施所有内容并使其全部变绿,或者删除其中的一些内容,从而明确取消相关的行为案例。 因此,在进行完彻底的分析和设计之后,您将拥有一个完整的自动测试套件。。。没有必要讨论拥有一个完整的自动化测试套件的好处。

The only thing you cannot verify objectively, is the very completeness of your test suite. Your future happiness of having this story done without issues and known bugs, is resting on a shaky foundation of how accurate the behaviour description is.

您唯一无法客观验证的是测试套件的完整性。 在没有任何问题和已知错误的情况下完成这个故事,您未来的幸福取决于行为描述的准确性。

The good news is that you won't need so much concentration after that. After having given your best in the test definition/design phase, you can code almost without thinking. Less thinking means less chaos.

好消息是您之后不需要那么集中。 在测试定义/设计阶段尽力而为后,您几乎可以无需考虑就可以编写代码。 少思考意味着少混乱。

Note that the design in its usual form, e.g., with UML, let's call it old-fashion design, doesn't make the same. Instead of a test skeleton, a future test suit, which completely defines your next steps, the old-fashion design yields some UML sheets that you hopefully won't put in the code documentation, as your code will probably significantly differ from what you've scribbled in Visio. The old-fashion design isn't that agile. (Yesss, I knew I could put it somewhere!)

请注意,以通常的形式进行的设计(例如,使用UML进行设计),我们称其为老式设计,但效果却不尽相同。 老式的设计代替了测试框架,而是一个完全定义您下一步的测试套件,老式的设计产生了一些UML表,您希望它们不会放入代码文档中,因为您的代码可能与您的代码有很大不同。 ve在Visio中涂鸦。 老式的设计并不那么敏捷 。 (是的,我知道我可以把它放到某个地方!)

样本用户案例 (Sample User Story)

Let's take a look at how it works in a real user story.

让我们看一下它在真实用户故事中的工作方式。

Consider you have a team of employees that you use to send to business trips for installing local networks, maintenance of security hardware, wine tasting, saving the world, whatever. Within this user story, they travel with passenger cars.

考虑一下您有一个员工团队,您可以使用它们来进行商务旅行,以安装本地网络,维护安全硬件,品尝葡萄酒,拯救世界等等。 在此用户故事中,他们乘坐乘用车旅行。

For the purposes of correct costs accounting and remuneration, the user as a head of department would like to have a function in the accounting software, where they can specify the vehicles the team drove with, associate the team members with the related vehicles and specify their roles, i.e., driver or passenger.

为了正确进行成本核算和报酬,作为部门主管的用户希望在会计软件中具有功能,他们可以在其中指定团队驾驶的车辆,将团队成员与相关车辆相关联,并指定他们的车辆。角色,即驾驶员或乘客。

There are a couple of reasons why it is important for the user to avoid possible errors, like having the same driver associated with multiple vehicles, more drivers than vehicles, same passengers in different vehicles and alike. Indeed, it would be great to know, who of the team members will pay the speed tickets.

对于用户而言,避免可能的错误很重要,原因有两个,例如使同一个驾驶员与多个车辆关联,驾驶员多于车辆,相同乘客在不同车辆中等等。 确实,很高兴知道,谁将支付票务。

The application's graphical layout consists of a two-pane control, where the left-hand part is a so-called hamburger menu that switches the content of the right-hand pane. The user story specifies that there should be an own button added to the hamburger menu for switching to the vehicle/team management function.

该应用程序的图形布局由两窗格控件组成,其中左侧部分是所谓的汉堡菜单,用于切换右侧窗格的内容。 用户故事指定应在汉堡菜单上添加一个自己的按钮,以切换到车辆/团队管理功能。

The PO didn't specify more details to this story, because they like making unadvertised changes to the productive code and the database schema, so they haven't got so much time for writing detailed acceptance criteria. This is the reality you work in. The user story is now yours.

PO并没有为这个故事指定更多细节,因为他们喜欢对生产代码和数据库模式进行不做广告的更改,因此他们没有太多时间来编写详细的接受标准。 这就是您工作的现实。用户故事现在是您的事了。

“用户希望具有功能...” ("The User Would Like to Have a Function...")

The user story specifications on one hand, and the existing framework of the app on the other, imply that the new function's view model should be added to the main view model's list of subordinate view models. This automatically leads to appearance of a new menu option in the left-hand pane. So, the first test is as follows:

一方面,用户故事规范,另一方面,该应用程序的现有框架,意味着应该将新功能的视图模型添加到主视图模型的从属视图模型列表中。 这会自动在左侧窗格中显示一个新的菜单选项。 因此,第一个测试如下:

[TestClass]
public class MainViewModelsTests
{
    [TestMethod]
    public void Ctor_Adds_ManageVehiclesViewModel_To_SubPages()
    {
        Assert.Inconclusive();//don't forget to implement me
    }
    /* 
    some other tests from previous user stories
    */
}

If the new view model is in the list, and its view is specified as a related data template in the main window’s XAML, the (tested) functionality of our application’s frameworks assures that the user has access to the new functionality. XAML content isn’t something that we unit-test, though.

如果新视图模型在列表中,并且其视图在主窗口的XAML中指定为相关数据模板,则我们应用程序框架的(经过测试)功能可确保用户可以访问新功能。 但是,XAML内容不是我们进行单元测试的内容。

It looks like we have forgotten that we would need the list of the team members to assign to the vehicles in the new view model. Yes, we have. Let's go ahead.

似乎我们已经忘记了,我们将需要团队成员列表来分配给新视图模型中的车辆。 是的我们有。 让我们继续。

“ ...他们可以指定车队驾驶的车辆...” ("...Where They Can Specify the Vehicles the Team Drove with...")

So, ManageVehiclesViewModel initially (at least in this use case) has an empty list of vehicles, offers a possibility to add and remove vehicles, lets the rest of the world know that it happens(*), and has a validation possibility which has an impact on save. Ah, there is a save command too!

因此, ManageVehiclesViewModel最初(至少在此用例中)具有空的车辆列表,提供了添加和移除车辆的可能性,让世界其他地方知道它发生了(*),并具有包含影响保存。 啊,也有一个保存命令!

(*) The related property can by of type IEnumerable<Vehicle>. If its field behind is an ObservableCollection or a BindingList, WPF will check it. Not sure about Xamarin.Forms. If the field behind is a List or an array, it should change the reference and raise the property change event, otherwise the binding won't work (only property change event isn't sufficient). The latter option seems to be the most universal, i.e., it will certainly work for both WPF and Xamarin.Forms. For the sake of brevity, we will use BindingList.

(*)相关属性的类型可以为IEnumerable<Vehicle >。 如果其后面的字段是ObservableCollectionBindingList ,则WPF将对其进行检查。 不确定Xamarin.Forms。 如果后面的字段是List或数组,则应更改引用并引发属性更改事件,否则绑定将不起作用(仅属性更改事件是不够的)。 后一个选项似乎是最通用的,即,它对于WPF和Xamarin.Forms肯定都适用。 为了简洁起见,我们将使用BindingList

So, the user should be able to add or remove vehicles. For this, we need a command and an observable collection in ManageVehiclesViewModel, the command should add a vehicle when it makes sense and be disabled when it does not. The added vehicle view model should have a remove-me command, and there should be a way to communicate this desire to ManageVehiclesViewModel (I always use a command-event pair in such cases in favour of isolated unit tests.) We add a dozen tests just for “...where they can specify the vehicles the team drove with...”. It seems we have enough work to do without blaming the PO for an under-defined user story:

因此,用户应该能够添加或删除载具。 为此,我们需要一个命令和ManageVehiclesViewModel一个可观察的集合,该命令应在ManageVehiclesViewModel时添加一个载ManageVehiclesViewModel ,而在ManageVehiclesViewModel时应被禁用。 添加的车辆视图模型应该具有remove-me命令,并且应该有一种方法将此需求传达给ManageVehiclesViewModel (在这种情况下,我总是使用命令事件对,以支持独立的单元测试。)我们添加了十几个测试仅用于“ ...他们可以指定团队驾驶的车辆...”。 似乎我们有足够的工作要做,而不会将PO归咎于定义不明确的用户故事:

public void Ctor_Initializes_Vehicles_With_EmptyBindingList()...

public void Ctor_Initializes_AddVehicleCommand_With_CanExecute_True()...

public void AddVehicleCommand_Adds_VehicleViewModel_ToVehicles()...

public void On_VehicleViewModel_RemoveMeEvent_RemovesSender_FromVehicles()...

Rather soon, we will see that we are missing the teammates collection. For instance, when we will realize that we cannot add an unlimited number of vehicles, anyway not more than not yet assigned teammates.

很快,我们将看到我们缺少队友收藏。 例如,当我们意识到无法添加无限数量的车辆时,无论如何,最多只能分配尚未分配的队友。

What?! Yes, another collection, that of the unassigned teammates which is initialized in constructor from the passed list of teammates, changes when you add some of them to a vehicle as a driver or as a passenger or remove an entire vehicle with some passengers, and this, in its turn, changes the can-execute state of the add-vehicle command, and that of the save command too, and on changing the can-execute state, the command raises the can-execute-changed event...

什么?! 是的,另一个集合是未分配的队友的集合,该集合是在传递的队友列表中的构造函数中初始化的,当您将其中一些作为驾驶员或乘客添加到车辆中或将整辆车辆连同一些乘客一起移出时,集合会发生变化依次更改add-vehicle命令和can命令的can-execute状态,并在更改can-execute状态时,该命令引发can-execute-changed事件...

It sounds simple: just read the user story and write down in the form of empty tests everything that comes to mind. It isn’t a big deal if you must rework it then, since there is no implementation effort behind them yet.

听起来很简单:只需阅读用户案例,然后以空测试的形式写下想到的所有内容。 如果您随后必须对其进行重新设计,这没什么大不了的,因为它们背后还没有实现工作。

There will be tests and tests, new behaviour cases, new tests for them, where your find further new behaviour cases and so one and it seems to have no end...

会有测试和测试,新的行为案例,针对它们的新测试,您可以在其中找到更多新的行为案例,等等,而且似乎没有止境...

Image 1

Well, in most cases it does have the end. It’s great fun to reach it, because it happens suddenly. Suddenly, you figure out that you have nothing to add, simply nothing, while all the tests you've written so far are green. Then you are done with that story.

好吧,在大多数情况下,它确实有尽头。 实现它非常有趣,因为它突然发生。 突然,您发现您没有什么可添加的,仅仅是什么也没有,而到目前为止您编写的所有测试都是绿色的。 然后您就完成了这个故事。

If it doesn't, it has nothing to do with TDD. Your analysis of the behaviour details - this is what you were doing all the time - has led you to a conclusion that the user story has no consistent solution, at least not in your understanding. It is good to know about it so early, before having written a line of productive code. It's time to have a brainstorming and to talk with the PO.

如果不是,则与TDD无关。 您对行为细节的分析(这就是您一直在做的事情)使您得出结论,即用户故事没有一致的解决方案,至少在您的理解范围内。 最好在编写一行生产性代码之前就这么早地了解它。 现在是时候进行头脑风暴和与采购代表进行交谈了。

Our user story does have a consistent solution. It is implemented in project Sample2.

我们的用户故事确实有一个一致的解决方案。 它在项目Sample2实现。

It turns out, however, that consequent adding/removing passengers or drivers leads to changing their order in the initial list.

然而,事实证明,随后增加/减少乘客或驾驶员导致改变他们在初始列表中的顺序。

Image 2
Figure 1. Initial view of two vehicles before assigning any team-mate as a driver or passenger
图1.在指派任何队友作为驾驶员或乘客之前,两辆车的初始视图

Clicking “Georgy Zhukov” in the collection of the first vehicle’s available drivers assigns him as a driver. The same is for “Dwight Eisenhower” if we select him as the second vehicle's driver. In both cases, these team members are removed from “Available Passengers” and “Available Drivers” of both vehicles.

在第一辆车的可用驾驶员集合中单击“ Georgy Zhukov ”,将他分配为驾驶员。 如果我们选择他作为第二辆车的驾驶员,那么“ 德怀特·艾森豪威尔 ”也是如此。 在这两种情况下,这些团队成员都从这两种车辆的“ 可用乘客 ”和“ 可用驾驶员 ”中删除。

Image 3
Figure 2. View after specifying the drivers. The assigned drivers are not available any more to be assigned as passengers or drivers of both vehicles.
图2.指定驱动程序后的视图。 分配的驾驶员不再可作为两个车辆的乘客或驾驶员分配。

If we click the driver button with an assigned driver, the latter is de-assigned and returns to “Available Passengers” and “Available Drivers” of both vehicles. However, the order of the unassigned team members is now different:

如果单击带有已分配驾驶员的驾驶员按钮,则会取消分配后者,并返回到两辆车的“ 可用乘客 ”和“ 可用驾驶员 ”。 但是,未分配的团队成员的顺序现在不同:

Image 4
Figure 3. View after removing the drivers. The former drivers are appended to the collections of the available drivers and passengers.
图3.删除驱动程序后的视图。 以前的驾驶员会附加到可用驾驶员和乘客的集合中。

The functionality can be used as specified in the user story, but the PO doesn’t find it nice and it’s difficult to argue. Indeed, we are supposed to bring the vehicle-driver-passenger association to its initial state, and we do so, but the user expects to see the entire view in its initial state.

可以按照用户案例中指定的方式使用该功能,但是PO并不觉得很好,并且很难争论。 确实,我们应该将车辆-驾驶员-乘客协会带入其初始状态,并且这样做,但是用户希望以其初始状态看到整个视图。

Let’s look at test-driven implementation of this improvement in detail.

让我们详细看一下测试驱动的改进实施。

测试驱动的后期功能实施 (Test-Driven Implementation of a Late Feature)

First, we describe a couple of tests for this.

首先,我们为此进行一些测试。

Ah, no! First, we decide where to place these tests.

啊,不! 首先,我们决定将这些测试放在何处。

If you examine project Sample2, you will see that we have tested that:

如果检查项目Sample2 ,您将看到我们已经测试过:

  1. the collections of unassigned team members in all vehicle view models share the same reference, and

    所有车辆视图模型中未分配的团队成员的集合共享相同的参考,并且
  2. Available Passengers” and “Available Drivers” are automatically synchronized with the collection of unassigned team members.

    可用乘客 ”和“ 可用驾驶员 ”会自动与未分配团队成员的集合同步。

The first position could have seemed excessive at the beginning. Should we really test such things? Well, in the original customer project, where this story occurred, we didn’t, what made it necessary to test synchronization between the vehicles. For this article, I implemented it from scratch and differently, so I could spare nearly a dozen of integration-level tests without even adding more unit-level tests.

一开始,第一个位置可能看起来过高。 我们真的应该测试这些东西吗? 好吧,在最初的客户项目中,发生了这个故事,但我们没有,因此有必要测试车辆之间的同步性。 对于本文,我从头开始并以不同的方式实施,因此我可以省去将近十二个集成级别的测试,而无需添加更多的单元级别的测试。

With this observation, it seems to suffice if we test the new feature within one vehicle view model, too. Let’s define such tests:

有了这个观察结果,如果我们也可以在一个车辆视图模型中测试新功能就足够了。 让我们定义这样的测试:

[TestFixture]
public class VehicleVieweModelTests
{
    [Test] public void 
           Setting_Removing_Driver_Preserves_OriginalOrder_OfUnassignedEmployees()...
    [Test] public void 
           Adding_Removing_Passengers_Preserves_OriginalOrder_OfUnassignedEmployees()...
}

As I’m not a LINQ guru, I have no idea how I would implement it, and I prefer not to think about it at this point. It fits well to the scheme.

由于我不是LINQ专家,所以我不知道如何实现它,并且我现在不愿意考虑它。 它非常适合该计划。

The two tests are not quite similar:

这两个测试不太相似:

[Test]
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
public void Setting_Removing_Driver_Preserves_OriginalOrder_OfUnassignedEmployees(int expected)
{
    // arrange 
    var target = new VehicleViewModel(this.unassingedEmployees, this.unassingedEmployees.ToList());
    var labRat = this.unassingedEmployees[expected];

    // act
    target.AvailableDrivers.Single(el => el.Person == labRat).SelectCommand.Execute(null);
    target.Driver.SelectCommand.Execute(null);

    // assert
    var actual = this.unassingedEmployees.IndexOf(labRat);
    Assert.AreEqual(expected, actual);
}

In this test, we see that the collection of employees is passed to the constructor of VehicleViewModel twice, but as two different instances. You find the related discussion below in this section. The test verifies exactly what we have observed and depicted in the screenshots above. But something tells us that the things will be the same if we try it with the passenger. Maybe even more complicated, as we can add and remove multiple passengers in an arbitrary order.

在此测试中,我们看到雇员集合两次作为两个不同的实例传递给VehicleViewModel的构造函数。 您可以在本节下面找到相关的讨论。 该测试准确地验证了我们在上面的屏幕截图中观察和描述的内容。 但是有件事告诉我们,如果我们与乘客一起尝试的话,情况将是相同的。 甚至可能更加复杂,因为我们可以按任意顺序添加和删除多名乘客。

[Test]
[TestCase(new[] { 0 }, new[] { 0 })]
[TestCase(new[] { 1 }, new[] { 1 })]
[TestCase(new[] { 2 }, new[] { 2 })]
/*lots of test cases ...*/
[TestCase(new[] { 0, 1, 2 }, new[] { 1, 0, 2 })]
[TestCase(new[] { 1, 0, 2 }, new[] { 1, 0, 2 })]
[TestCase(new[] { 2, 0, 1 }, new[] { 1, 0, 2 })]
[TestCase(new[] { 2, 1, 0 }, new[] { 1, 0, 2 })]
[TestCase(new[] { 2, 1, 0 }, new[] { 2, 0, 1 })]
public void Adding_Removing_Passengers_Preserves_OriginalOrder_OfUnassignedEmployees
       (int[] toAdd, int[] toRemove)
{
    // arrange 
    var target = new VehicleViewModel(this.unassingedEmployees, this.unassingedEmployees.ToList());
    var labRats = this.unassingedEmployees.ToArray();
    foreach (var i in toAdd)
    {
        target.AvailablePassengers.Single(el => el.Person == labRats[i])
                                  .SelectCommand.Execute(null);
    }

    // act
    foreach (var i in toRemove)
    {
        target.Passengers.Single(el => el.Person == labRats[i])
                         .SelectCommand.Execute(null);
    }

    // assert
    foreach (var expected in toAdd)
    {
        var actual = this.unassingedEmployees.IndexOf(labRats[expected]);
        Assert.AreEqual(expected, actual);
    }
}

Yet, it didn’t (and still doesn’t) seem evident to me that the original order can be restored correctly after all available teammates are selected to passengers and then unselected in a different order. Instead of gazing down at the productive code and trying to figure out how it would work in more complicated cases, or messing with mathematical induction or something, I just add the test cases and consider it is good enough if they pass while the algorithm isn’t quite clear to me.

然而,在我看来(现在仍然不是),在将所有可用的队友都选给乘客然后以不同顺序取消选择后,可以正确恢复原始顺序。 与其凝视有效的代码并试图弄清它在更复杂的情况下如何工作,或者弄乱数学归纳之类,我只是添加了测试用例,并认为如果在算法未通过的情况下通过它们就足够了。我不太清楚。

There are lots of such situations, for instance in numerical methods, where understanding every algorithm detail in connection to any thinkable application case is simply not affordable.

有很多这样的情况,例如在数值方法中,要了解与任何可想到的应用案例有关的每个算法细节都是负担不起的。

For making it sure that this implementation has an expected effect with regard to “Available Passengers” and “Available Drivers”, recall that we have already tested that these collections are synchronized with this.unassingedEmployees.

为了确保此实现对“ 可用乘客 ”和“ 可用驾驶员 ”具有预期效果,请回想一下,我们已经测试了这些集合是否与this.unassingedEmployees同步。

There remains a nuance that no test covers yet, namely that the ManageVehiclesViewModel creates a new vehicle view model with two different collections, namely this.unassignedEmployees and this.originalEmployees.

仍然存在尚未进行测试的细微差别,即ManageVehiclesViewModel创建具有两个不同集合(即this.unassignedEmployeesthis.originalEmployees的新的车辆视图模型。

var newVehicleVm = new VehicleViewModel(this.unassignedEmployees, this.originalEmployees);

The vehicle view models share the former collection’s reference, so its content changes in time. Can we really use it to keep the order template?

车辆视图模型共享先前集合的参考,因此其内容会随时间变化。 我们真的可以使用它保留订单模板吗?

It is quite annoying to test such a small thing, especially when we cannot figure out right away, how we can do it in an elegant manner. It would however be even more annoying, if it wouldn’t work because of a stupid copy-paste error.

测试这么小的事情是很烦人的,尤其是当我们无法立即弄清楚如何以一种优雅的方式进行测试时。 但是,如果由于一个愚蠢的复制粘贴错误而无法正常工作,那将更加烦人。

I did my best trying to keep it as simple as possible:

我尽力使它尽可能简单:

[Test]
[TestCase(0, 1)]
[TestCase(1, 2)]
public void 
Adding_Removing_Passengers_ForTwoVehicles_Preserves_OriginalOrder_OfUnassignedEmployees
(int toAddRemove1, int toAddRemove2)
{
    // arrange 
    var target = new ManageVehiclesViewModel(this.employees, this.containerMock.Object);
    target.AddVehicleCommand.Execute(null);
    var vehicle1 = target.Vehicles.Last();
    vehicle1.AvailablePassengers.Single(el => el.Person == 
             this.employees[toAddRemove1]).SelectCommand.Execute(null);

    target.AddVehicleCommand.Execute(null);
    var vehicle2 = target.Vehicles.Last();
    vehicle2.AvailablePassengers.Single(el => el.Person == 
             this.employees[toAddRemove2]).SelectCommand.Execute(null);

    // act
    vehicle1.Passengers.Single(el => el.Person == 
             this.employees[toAddRemove1]).SelectCommand.Execute(null);
    vehicle2.Passengers.Single(el => el.Person == 
             this.employees[toAddRemove2]).SelectCommand.Execute(null);

    // assert
    CollectionAssert.AreEqual(this.employees, target.UnassignedEmployees.ToList());
}

This is an integration test. So as to reduce its overlapping with the unit tests in VehicleViewModelTests, I retained here only those tests cases that fail if we pass this.unassignedEmployees as the second parameter like below:

这是一个集成测试。 为了减少与VehicleViewModelTests的单元测试的VehicleViewModelTests ,我仅在此处保留那些如果我们通过this.unassignedEmployees作为第二个参数的情况下失败的this.unassignedEmployees ,如下所示:

var newVehicleVm = new VehicleViewModel(this.unassignedEmployees, this.unassignedEmployees);

It didn’t take much to think on its implementation, as it resembles the analogous unit tests in VehicleViewModelTests. Nevertheless, what is its value? I mean, besides covering this ridiculous copy-paste opportunity. Well, it verifies that passing two different collections is necessary indeed, so we haven't added any technical debt here and don’t need to think about eventual simplification. At some point, I had a doubt.

它的实现无需花太多时间,因为它类似于VehicleViewModelTests的类似单元测试。 但是,它的价值是什么? 我的意思是,除了涵盖这个荒谬的复制粘贴机会之外。 好吧,它验证了确实需要通过两个不同的集合,因此我们在这里没有添加任何技术债务,也不需要考虑最终的简化。 在某个时候,我有一个疑问。

When messing around with the above integration test, I found another behaviour case, namely that of deleting an entire vehicle with passengers or drivers, where correct order recovery is to be verified too. So, further integration tests are added. This time, these are undoubtedly integration cases:

在进行上述集成测试时,我发现了另一个行为案例,即与乘客或驾驶员一起删除整辆车的情况,在此情况下也要验证正确的订单恢复。 因此,添加了进一步的集成测试。 这次,这些无疑是集成案例:

public void On_Removing_VehicleViewModel_Adds_VehiclesAssingedPassengers_
ToUnassgignedEmployees_InOriginalOrder()...

public void On_Removing_VehicleViewModel_Adds_VehiclesAssingedDriver_
ToUnassgignedEmployees_InOriginalOrder()...

These new cases would require reuse of the insertion-to-original-order algorithm, which was initially implemented as a method of VehicleViewModel. Now we move it to an own utility class OriginalOrderTemplate and we should test it, shouldn’t we? And what is then with the already written tests in VehicleViewModelTests? Will they be duplicated?

这些新情况将需要重用原始顺序插入算法,该算法最初是作为VehicleViewModel一种方法VehicleViewModel 。 现在我们将其移动到自己的实用程序类OriginalOrderTemplate ,我们应该对其进行测试,不是吗? VehicleViewModelTests已经编写的测试VehicleViewModelTests如何呢? 他们会被复制吗?

No, not really. The initially written tests verify only the cases that can occur in this user story. But the new class is a utility. So, its usage should either be limited to the cases of our user story, which would require defining and testing its reaction on the cases beyond the required scope, like throwing argument exceptions. Or we extend its scope and implement some limit case tests outside the user story scope. In this specific situation, I found it more pragmatic to add limit test cases and thus a) add more value to this utility, b) make it less fragile, and c) avoid changes to the productive code logic, which I would have tested too. There is something to test in OriginalOrderTemplateTests, anyway.

不,不是。 最初编写的测试仅验证该用户案例中可能发生的情况。 但是新类是一个实用程序。 因此,它的用法应仅限于我们用户案例的情况,这将需要定义和测试其对超出要求范围的案例的React,例如引发参数异常。 或者,我们扩展其范围,并在用户故事范围之外实施一些极限情况测试。 在这种特定情况下,我发现添加极限测试用例更为实际,因此a)为该实用程序增加了更多价值,b)使其更不脆弱,并且c)避免对生产性代码逻辑进行更改,而我也应该对此进行测试。 。 无论如何,在OriginalOrderTemplateTests有一些要测试的东西。

The above-mentioned tests and the related productive code changes are found in project Sample3.

在项目Sample3可以找到上述测试和相关的生产代码更改。

It is important to indicate that there is no need to verify this improvement in an automated UI test. Indeed, we have not only tested the synchronization of “Available Passengers” and “Available Drivers”, but also that they are of an observable type (IBindingList in our case). So, the only thing that still could go wrong is the related XAML binding expression, which we as developers do not cover with automated tests. If QA would like to, they can, but they certainly don’t need to do this additionally for this specific improvement.

重要的是要指出,无需在自动UI测试中验证此改进。 实际上,我们不仅测试了“ Available Passengers ”和“ Available Drivers ”的同步性,而且还测试了它们是否是可观察的类型(在本例中为IBindingList )。 因此,唯一仍然可能出错的是相关的XAML绑定表达式,我们(作为开发人员)并不涉及自动测试。 如果质量检查人员愿意,他们可以,但是他们当然不需要为此进行额外的改进。

As you can see, late definition of behaviour cases and late test implementation involves that chaotic jumping between behaviour analysis, tests definitions and implementations details that I have talked about at the beginning, the old, good and expensive thinking-n-coding.

正如您所看到的,行为案例的较晚定义和测试的较晚实现涉及行为分析,测试定义和实现细节之间的混乱跳跃,而这些细节在我一开始就已经谈到过,既旧又好又昂贵。

TDD算法 (TDD Algorithm)

In the rule, you realize some new behaviour cases when you are already working on the implementation details of the productive code.

在规则中,当您已经在研究生产代码的实现细节时,就会意识到一些新的行为情况。

The algorithm is simple: whatever you are doing, stop it if you find a new behaviour case, and write an empty test for it. It secures you from forgetting that new case (you will forget it if you find a second one or a third). Besides, if you have any inconsistency in your design or in the user story definition, you have a chance to figure it out and take measures as early as possible, thus reducing the risk of excessive costs. In any way, you stay in the test definition phase so long as you can add or change anything in the test skeleton.

该算法很简单:无论您做什么,在发现新的行为案例时都将其停止,并为其编写空测试。 它可以使您避免忘记该新案件(如果找到第二个或第三个案件,则将其忘记)。 此外,如果您在设计或用户故事定义中存在任何不一致之处,则有机会找出并尽快采取措施,从而降低了成本过高的风险。 无论如何,只要您可以在测试框架中添加或更改任何内容,就可以停留在测试定义阶段。

It might help if you define the priorities of your work as follows:

如果按以下方式定义工作的优先级可能会有所帮助:

  1. Define the behaviour cases in form of empty (inconclusive) unit/integration tests. If there is nothing to add here, review it with the PO or the teammates and by the necessity reiterate this point. If nothing is added, go to the next priority level and...

    以空的(不确定的)单元/集成测试形式定义行为案例。 如果没有要在此处添加的内容,请与PO或队友进行审查,并有必要重申这一点。 如果未添加任何内容,请转到下一个优先级并...
  2. ...Implement the tests and the boiler plate code in the productive part to get the tests compiled. At any occurrence of a new behaviour case, return to 1. If all tests are compilable, proceed with....

    ...在生产部分中执行测试和样板代码以编译测试。 在出现新的行为情况时,请返回1。如果所有测试都可编译,请继续...。
  3. ...Implementing the productive part to green all the tests. At any opportunity, return to 2 or 1.

    ...实施生产部分以使所有测试变绿。 如有可能,请返回2或1。
  4. If you have reached this point, you are done.

    如果您已经达到了这一点,那么您就完成了。

At least you can feel so, even if you have forgotten the XAML part. It systematically happens to me to forget about the view. Anyway, you will laugh on it with your teammates on the stand-up and then will be refining and tuning the UI part with an easy heart, because the thing already works.

即使您忘记了XAML部分,至少也可以感受到。 我系统地忘记了这个观点。 无论如何,您会在站起来的时候与队友一起大笑,然后轻松地完善和调整UI部件,因为事情已经发生了。

Phases 2 and 3 can be merged. It depends upon your personal preferences and how confident or unconfident you are about implementation of the productive part in this specific user story. I usually merge.

阶段2和3可以合并。 这取决于您的个人喜好以及您对该特定用户故事中的生产性部分的实现有多自信或不自信。 我通常合并。

What is the meaning of this work from the productive code perspective?

从生产代码的角度来看,这项工作的意义是什么?

  1. Analyse the requirements, define class structure, define the behaviour and interaction of the new classes "in prose".

    分析需求,定义班级结构,定义“散文”中新班级的行为和交互。
  2. Define the behaviour and interaction of the new classes in terms of their publicly exposed programming interfaces.

    根据公开的编程接口定义新类的行为和交互。
  3. Implement the new classes.

    实施新类。

What concerns the class interaction, it is pretty much like CRC design, except that you define it in within the integration tests. Besides, you define the class behaviour much more detailed as when you do it in a usual CRC or UML design.

与类交互有关的内容与CRC设计非常相似,只是您在集成测试中定义了它。 此外,与在常规CRC或UML设计中进行操作时相比,您可以更详细地定义类行为。

Anyway, at every step, you are doing the productive work. Having a complete test suit at the end is a bonus.

无论如何,您在每一步都在进行富有成效的工作。 结束时拥有完整的测试服是一种奖励。

借用编程的乐趣 (The Fun of Borrowing Programming)

The software developer work is creative. That’s why we like it. What if the suggested recipe removes the thinking work from where it is the most creative, from the productive code implementation?

软件开发人员的工作富有创造力。 这就是为什么我们喜欢它。 如果建议的配方将有思想的工作从生产性代码实现中移除了最具创意的工作该怎么办?

Well, partly it does.

好吧,部分可以。

There is however a phenomenon that spoils a pleasure of a super-creative work, namely a bitter experience of never-ending stories. The chart below displays functionality F versus costs C in a project or user story with elevated technical debt (poor design and test coverage are parts thereof):

但是,有一种现象破坏了超创意作品的乐趣,即对永无止境的故事的痛苦体验。 下图显示了在技术债务增加的情况下,项目或用户故事中功能F与成本C的关系(不良的设计和测试范围是其中的一部分):

Image 5
Figure 4. Costs versus functionality when running a project with high technical debt.
图4.运行技术债台高筑的项目时成本与功能的关系。

The fun of a super enthusiastic and creative feature-driven work at the beginning turns into frustration at the end. At this point, you just wouldn’t like to think about what would happen if a change request comes. You can experience it once, twice, a couple of times more...

一开始,由超级热情和创造性的功能驱动的工作带来的乐趣最终变成了沮丧。 在这一点上,您只是不想考虑如果有变更请求会发生什么。 您可以体验一次,两次甚至更多次...

The next chart shows another case, namely how the costs-versus-functionality curve looks like in a TDD-style project. You get done suddenly. And certainly.

下图显示了另一种情况,即在TDD风格的项目中成本与功能的关系曲线。 你突然完成了。 当然可以。

Image 6
Figure 5. Costs versus functionality when low technical debt.
图5.技术债务低时的成本与功能。

Here, you might feel uncomfortable at the beginning, as you’re working hard but produce no functional increments. Then, as you start greening the tests, you might be thinking “no, it cannot be that simple!”. Si, it can! It feels like you are working almost without thinking. This is because your thinking is more efficient. You are concentrated on the implementation details only, that’s why it doesn’t take so much effort.

在这里,一开始您可能会感到不舒服,因为您正在努力工作,但不会产生功能上的增加。 然后,当您开始绿化测试时,您可能会想到“不,它不可能那么简单!”。 ,可以! 感觉就像您几乎没有思考就在工作。 这是因为您的思考效率更高。 您只关注实现细节,这就是为什么它不需要那么多精力。

These diagrams are valid for projects with higher and lower technical debt in general. Test-driven development helps you to reduce the part of technical debt that is linked to poor design and tests coverage.

这些图通常适用于技术债务较高和较低的项目。 测试驱动的开发可以帮助您减少与不良设计和测试范围相关的技术债务。

It is like in many other life situations: your either invest up-front and then enjoy or you have fun from the beginning, but not for long:

就像在许多其他生活场景中一样:您要么先投资然后享受,要么从一开始就玩得开心,但时间不长:

Image 7
Figure 6. Where fun starts and ends in low- and high-technical-debt projects.
图6.在高负债和低负债项目中,乐趣的起点和终点。

TDD为什么(何时)不起作用? (Why (When) Doesn’t TDD Work?)

信心不足 (Lack of Confidence)

How can you write empty unit tests (for days!), if you are not sure that you can implement the productive part?

如果不确定是否可以实现生产性部分,如何编写空的单元测试(几天!)?

You cannot. If you are not sure about the productive part, this is the case for a prototype.

你不能。 如果您不确定生产部分,那么原型就是这种情况。

原型制作 (Prototyping)

As books say, a prototype is something that you throw away after. The further you go with your prototyping, the more difficult is to stop it and start a clean productive solution. The further you continue, the lower are the technical risks, but the higher are the risks to continue the production with a poorly designed prototype code.

就像书中所说的那样,原型就是您要抛弃的东西。 进行原型开发的距离越远,停止它并启动清洁高效的解决方案就越困难。 继续进行得越远,技术风险就越低,但是使用设计不良的原型代码继续生产的风险就越高。

Prototype code isn't something you’re used to cover with unit tests, simply because you may need to refactor it too often and too deeply, so that refactoring the related unit tests may turn out to be too expensive.

原型代码不是单元测试所需要的,仅仅是因为您可能需要过于频繁和深入地进行重构,以至于重构相关的单元测试可能会变得过于昂贵。

过度自信 (Excessive Confidence)

You think you realize the software component's behaviour and the interaction between the productive classes good enough to skip this borrowing work. I often have such temptation. It might lead me to having a leaky tests coverage. What consoles me is a hope that the not-covered classes would never be changed in the future nor impacted by any changes in other parts of my software component.

您认为您已经意识到软件组件的行为以及生产类之间的交互足够好,可以跳过此借用工作。 我经常有这种诱惑。 这可能会导致我无法进行测试。 令我感到欣慰的是,希望未覆盖的类将来永远不会更改,也不会受到软件组件其他部分的任何更改的影响。

重构过多 (Too Much Refactoring)

Even if at the beginning you were sure enough about the productive part, you encounter a necessity of significant refactoring at a later point, where you already have tons of unit tests which should be refactored too. So, while without the unit tests the refactoring costs would be quite moderate, with them it turns out to be very expensive.

即使一开始就对生产部分有足够的把握,但在以后的某个时刻,您仍然需要进行大量的重构,因为那里已经有大量的单元测试,也应该重构。 因此,尽管没有单元测试,但重构成本将相当适中,而事实证明它们的成本非常高昂。

It is the matter of design to organize the things in your productive code and the tests so, that such deep refactoring is sufficiently unlikely. Is there any recipe for a good design? Yes, there is. Read about design anti-patters and avoid them. Even if you, for whatever the reason, don't like using design patterns, simply avoiding anti-patterns will make you code good enough.

在生产代码和测试中组织事物是设计的问题,这样的深度重构就不太可能了。 有什么好的设计秘方吗? 就在这里。 阅读有关设计反模式的知识,并避免使用它们。 即使出于某种原因,即使您不喜欢使用设计模式,只要避免使用反模式都会使您的代码足够好。

In a real project, where this sample user story occurred, vehicles were initially represented by strings (car plate numbers). It’s the same what happens if you pass the event data as is, without encapsulating it into an EventArgs-descendant. I haven’t found any specific anti-pattern for this, so I have christened it as under-typing. When it was time to add car mileage, we already had a lot of unit tests, where we've got to change the test data types.

在一个实际的项目中,发生了此示例用户故事,车辆最初以字符串(车牌号)表示。 如果您按原样传递事件数据,而不将其封装到EventArgs -descendant中,则发生的情况相同。 我尚未为此找到任何特定的反模式,因此我将其称为“打字错误” 。 当需要增加汽车行驶里程时,我们已经进行了许多单元测试,我们必须更改测试数据类型。

太费劲了 (It Is Too Strenuous)

We had once a great experience of defining all thinkable tests cases exactly as I put it here. But we did it in the form of SCRUM Planning II, that is seating altogether, the entire team or almost, and writing that stuff on a board. We did only one user story in this way and all of us highly estimated it at the consequent retrospective meeting. But we have never done it again.

曾经有一次完全按照我在这里定义的定义所有可想到的测试用例的丰富经验。 但是我们以SCRUM Planning II的形式做到了这一点,那就是整个团队,或者几乎整个团队都坐着,然后把这些东西写在板上。 我们仅以这种方式制作了一个用户故事,我们所有人都在随后的回顾会议上对其进行了高度评价。 但是我们再也没有做过。

In my actual understanding, TDD should be cosy. I would even say, this is the objective.

以我的实际理解,TDD应该舒适。 我什至会说,这是目标。

太费力了 (It Takes Too to Much Effort)

That means, you feel it brings less than it costs. This is the killer of any undertaking.

这意味着,您认为它带来的收益少于成本。 这是任何事业的杀手。

Yet, we should never forget about the technical debt effect. It is always delayed and always inevitable. Figures 5 and 7 display how it works. When the costs explode, the learned lesson will probably convince you to start documenting your code, refactor, increase the test coverage, etc. But you cannot recover the costs of what has already happened. The regret to not having done it earlier will remain.

但是,我们永远不应忘记技术债务的影响。 它总是被延迟并且总是不可避免的。 图5和7显示了它的工作方式。 当成本激增时,所学到的教训可能会说服您开始编写代码,进行重构,增加测试范围等。但是您无法收回已经发生的成本。 遗憾的是没有早做。

Nevertheless, it would be nice to know...

尽管如此,很高兴知道...

如何降低单元测试成本? (How Can I Reduce the Unit-Testing Costs?)

Every automated test has a value and its cost. The objective is to always have the former possibly high and the latter possibly low. In case where we cannot estimate the value of the cost of each individual test in advance, a couple of rules can be used to make the value expectation higher and the cost expectation lower.

每个自动化测试都有其价值和成本。 目的是始终使前者可能较高而后者可能较低。 如果我们无法预先估计每个测试的成本值,可以使用一些规则来使期望值更高而成本期望更低。

  1. Up-front-written tests are statistically more valuable and less expensive than tests written for the existing code.

    与为现有代码编写的测试相比,预先编写的测试在统计上更有价值且更便宜。
  2. The tests written at a lower integration level are usually less expensive and more valuable than tests of the same behaviour cases at a higher integration level.

    与较低集成级别的相同行为案例的测试相比,较低集成级别的测试通常更便宜,更有价值。

As Martin Fowler depicted in his "Test Pyramid" article, the lower is the test in the pyramid, the cheaper it is:

正如马丁·福勒(Martin Fowler)在他的“ 测试金字塔 ”文章中所描述的那样, 金字塔中的测试越低,价格越便宜:

Image 8
Figure 7. The Martin Fowler's test pyramid.
图7.马丁·福勒的测试金字塔。

It is in general true for pure unit tests and for integration tests, unless unit-testable isolation of productive classes requires too much effort. We have integration tests in the sample user story in this article. Basically, if I have a choice, I test any behaviour as low in the test pyramid as possible.

通常,对于纯单元测试和集成测试都是如此,除非对生产类进行单元可测试的隔离需要太多的工作。 在本文的示例用户故事中,我们进行了集成测试。 基本上,如果我有选择的话,我会在测试金字塔中测试尽可能低的行为。

If you tested the thing at a lower level, avoid retesting it elsewhere. In other words, if I have already tested some behaviour in lower-level (lower-integration-degree) tests, I rely entirely on that in the higher-level tests.

如果您在较低级别上进行测试,请避免在其他地方重新测试。 换句话说,如果我已经在较低级别(较低集成度)的测试中测试了某些行为,那么我将完全依靠较高级别测试中的行为。

And do not copy-paste test-data-creating code. Use auxiliary methods.

并且不要复制粘贴测试数据创建代码。 使用辅助方法。

And…

和…

There are many recommendations and unit-testing best-practices. Let’s have them outside this article’s scope.

有许多建议和单元测试最佳实践。 让它们超出本文的讨论范围。

TDD何时特别有用? (When TDD is Especially Useful?)

In any case where you don't have any idea about the software component's behaviour cases and detailed design. Ok, ok... You simply don't feel excessive confidence, alright? In other words, if you know that it isn't rocket science, but you don't know what to start with, it wouldn't be wrong if you start with empty test cases, top-bottom, exactly as in the example above.

在任何情况下,您都不会对软件组件的行为情况和详细设计有任何了解。 好吧,好吧...您根本不会感到过度自信,好吗? 换句话说,如果您知道这不是火箭科学,但您不知道该从什么开始,那么从上至下的空测试用例开始就完全没有问题,就像上面的示例一样。

If you are tired and have concentration problems, it could help if you focus on simple and small yet useful things, like empty test cases.

如果您累了并且有专心的问题,那么如果您专注于简单而又小而有用的事情(例如空的测试用例),则可能会有所帮助。

从什么开始? (What to Start With?)

If your team is new to TDD (otherwise, they would tell you what to start with), you should first agree with your teammates on doing a user story or two in the TDD-style. If your team practices pet projects, this could be a good place to try something without an obligation to succeed right away.

如果您的团队是TDD的新手(否则,他们会告诉您如何开始),那么您应该首先与您的队友达成一致,以TDD风格编写一两个用户故事。 如果您的团队练习宠物项目,那么这可能是一个尝试某件事的好地方,而无需立即取得成功。

If your project guidelines imply high test coverage, it's more reasonable to write the tests first. It is simply less time consuming because of the reasons I tried to explain in this article. Besides, with tests-first, you will test the behaviour only, your tests will be shorter (not so much to refactor in case of), and your productive code will forcefully be test-cooperative.

如果您的项目指南暗示测试覆盖率很高,那么首先编写测试是更合理的。 由于我试图在本文中解释的原因,它仅减少了时间。 此外,首先使用测试,您将只测试行为,测试将更短(如果需要的话,重构得不那么多),并且您的生产代码将强制进行测试合作。

Whenever you feel it's too difficult, just recall how it was when you were learning to ride a bike.

每当您觉得太困难时,只要回想一下学习骑自行车时的情况即可。

更多提示 (Some More Tips)

The more people have reviewed your empty tests, the less will be your remaining effort.

审查您的空测试的人越多,您剩余的精力就越少。

Pair programming is much easier in the test definition phase than in the implementation, because the only thing you need to agree on is the behaviour. Besides, pair programming in the test definition phase is especially valuable.

在测试定义阶段,结对编程要比在实现中容易得多,因为您唯一需要同意的就是行为。 此外,在测试定义阶段的配对编程特别有价值。

If you even don't know, what empty tests to start with, as it happened to me in this user story, add functionality regions to individual test files, like "Constructor", "Adding/removing vehicles", "Validation and saving", etc. Remember that thinking takes time, typing does not.

如果您甚至不知道,从什么空的测试开始(就像在本用户故事中发生的那样),请向各个测试文件中添加功能区域,例如“构造函数”,“添加/删除车辆”,“验证和保存”等等。请记住,思考需要时间,而打字则不需要。

Consider adding Assert.Inconclusive() to the empty of copy-pasted tests. Keeping the empty tests in mind so as not to leave passing test placeholders is tiresome. Typing is... you already now. Why Assert.Inconclusive() and not throwing a not-implemented exception? Sounds logical, but then you probably won't be able to check it in and share the implementation work.

考虑将Assert.Inconclusive()添加到没有复制粘贴的测试中。 牢记空的测试,以免留下通过测试的占位符很麻烦。 打字是...您已经在。 为什么要使用Assert.Inconclusive()而不抛出未实现的异常? 听起来很合逻辑,但是您可能将无法检入它并共享实现工作。

源代码 (The Source Code)

The source code is a VS2019 solution. It contains three productive executable projects, namely Sample1, Sample2 and Sample3. The former is a boiler plate WPF project, the second one implements the sample user story as it is defined, the latter adds an improvement discussed in Test-driven implementation of a feature.

源代码是VS2019解决方案。 它包含三个高效的可执行项目,即Sample1 , Sample2Sample 3 。 前者是样板WPF项目,第二个实现了定义的示例用户案例,后者则增加了功能的测试驱动实现中讨论的改进。

The productive projects have their test counterparts, namely Sample1.Tests, Sample2.Tests, and Sample3.Tests.

生产性项目有自己的测试同行,即Sample1.TestsSample 2 .Tests ,Sample 3 .Tests

The former contains only empty tests that define the behaviour cases I thought about before I've set up with the implementation.

前者仅包含空测试,这些空测试定义了我在实施之前设置的行为案例。

The second test project contains the test suit of Sample2.

第二个测试项目包含Sample2的测试服。

The test suits of the both sample projects are different. Some tests of the first project are removed in the second. This is normal. Indeed, in the suit of empty tests, it deals about the behaviour definition. Later, it can turn out that some behaviour cases are not important, should be different, or even cancelled. Sometimes you cannot know it in advance.

两个示例项目的测试服都不同。 第一个项目的某些测试在第二个项目中被删除。 这是正常的。 确实,在进行空测试时,它涉及行为定义。 后来,事实证明某些行为案例并不重要,应该有所不同,甚至取消。 有时您无法事先知道。

The significantly increased number of tests, as displayed in Test Explore, namely 83 in Sample2.Tests versus 43 in Sample1.Tests is because I have changed from MSUnit to NUnit and implemented some tests with multiple data-driven tests cases. NUnit accounts data test cases as individual tests. Sample3.Tests displays 133 tests, although we have added only 6 test methods.

正如“测试探索”中显示的那样,测试数量的显着增加,即Sample 2 83 2 .TestsSample 1 43 1 .Tests由于我从MSUnit更改为NUnit并使用多个数据驱动的测试案例实施了某些测试。 NUnit数据测试用例视为单独的测试。 Sample 3 .Tests显示133个测试,尽管我们仅添加了6种测试方法。

There is also a utility project with an auxiliary stuff and unit tests thereof.

还有一个带有辅助材料及其单元测试的公用工程。

翻译自: https://www.codeproject.com/Articles/5257738/How-to-Code-Without-Thinking

前后加编码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值