目前,我正在为服务创建一些Cucumber功能。让我们说我有一个用于配置特斯拉汽车的服务:
public class TeslaCar {
Engine engine;
Color color;
// other things here...
}
,其中
public class Engine {
boolean isAllWheelDrive;
// for 60, 70, 90D, P100D
KilowattEnum kWh;
// other things here...
}
简单的创建调用很容易实现,我只需使用Given
与Add
结合配置资源,然后调用 create 。
因此对于CreateEngine
,我会这样做:
Feature: CreateEngine
Scenario Outline: Create Engine
Given an engine was initialized
And engine has <all_wheel>
And kwh is <kwh>
When engine is created
Then engine creation succeeds
Examples:
| kwh | all_wheel |
| 60 | false |
| 60 | true |
| 70 | false |
现在我需要为DriveCar
编写集成测试。我想用Given
确保有一辆我可以驾驶的新车。我想创造一辆新车,因为我不知道过去的汽车状态是什么。如果电池电量为10%,那么它将不是一个好的测试。然后,由于集成测试取决于我的特斯拉类型,我想在我的功能文件中配置它。
所以它看起来像:
Feature: Drive Car
Scenario: Test drive Tesla
Given a car is initialized
And engine kwh is P90D
And engine is all wheel drive
And engine is created
# Need to call CreateEngine with above line, but is this clunky?
And car is created
# Need to call CreateCar above because Engine is a parameter to CreateCar
When car is driven
Then max speed is 120mph
这一开始似乎很合理,但是有更好的方法吗?如果需要为DriveCar
配置更多的东西,那么它会很快变得笨拙。
我想到的一件事是使用@tags
。这让我说,“每当我使用@60kwh
&#39;时,我需要一台60kWh的引擎。但这并没有很好的延伸。
不要配置汽车,而只使用默认汽车。
答案 0 :(得分:3)
我会像发动机一样压缩向汽车添加组件的步骤。使用DataTable
添加引擎的所有属性。从这个新步骤定义中,只需要DataTable
作为参数,您可以调用现有代码进行引擎设置。这样,每次添加新属性时都不需要添加新的步骤定义。只需附加到DataTable的末尾。
如果你有一个新的汽车组件,你需要在你的功能文件中加上3行加上一个步骤定义。例如,我在该功能中添加了一个变速箱。
如果在特征文件DataTable中,您将表头命名为与实例变量相同,那么如果您放置List<Engine> engine
之类的参数而不是DataTable
,Cucumber会自动将值放入类中。
我看到的问题是,如果您有一个具有多个设置的属性。例如,引擎模式可以是City,Cruise和Sport。也许你可以使用逗号分隔的字符串然后拆分它。
Feature: Drive Car
Scenario: Test drive Tesla
Given A car with following components
And Add engine with specifications
| kwh | allwheel |
| P90D | true |
And Add gearbox with specifications
| noofgears | auto |
| 4 | true |
And assemble car
When car is driven
Then max speed is 120mph
如果您想要更多乐趣,请考虑将其作为场景大纲并在一个功能中测试多个组合。但是接下来你会有一个很大的示例表行数据。我实际上会使用该路由,因为我会从一个场景中获得更多,它会将数据从步骤推送到示例表中。
功能文件中的步骤似乎总结了代码,而不是像步骤这样的行为 - 引擎已初始化。但是,如果每个人都理解它并为您的目的服务,那么为什么要改变它。
答案 1 :(得分:3)
Cucumber专为验收测试而设计,测试可以运行整个系统(因此它们也是集成测试),因为用户可以并且可以被利益相关者和开发人员理解。
您的CreateEngine
方案不适用于整个系统,但对于只对开发人员有意义的软件组件,所以我不是用Cucumber测试它,而是使用单元测试框架
你的&#34;驾驶汽车&#34;另一方面,情景是一种适当的验收测试。不过,它有很多软件细节。我写的是这样的:
Feature: Drive Car
Scenario: Test drive Tesla
Given there is a car with an all-wheel-drive engine and P90D engine kwh
When the car is driven
Then the max speed is 120 mph
(我不确定&#34; P90D引擎kwh&#34;语法正确,所以请在必要时更正。)
重点:
我不确定您是否需要比我展示的更灵活的汽车定义步骤。我通常发现在大多数情况下只需要几个简单的步骤。尽量保持简单,只有在需要时才能构建更复杂的步骤。
此外,抵制为所有数据组合编写Cucumber场景(或场景轮廓)的冲动。验收测试很慢并且需要维护,因此您希望尽可能少地接受测试,同时仍然向利益相关者公开所有重要的要求。当您开始编写组合Cucumber场景时,请考虑是否可以将一个Cucumber场景作为示例编写,并在单元测试中测试所有组合。
答案 2 :(得分:2)
非常好的问题,Dave Schweisguth也有一个很好的答案,我也会补充一下。
如果您的汽车结构具有所有配置选项,并且您希望编写集成测试来处理不同类型的汽车,则可以通过命名汽车来消除在功能中指定每项维护的详细信息的需要。例如,我可能有:
然后你会写下这样的功能:
Given I have a rally car
...
Given I have a taxi
等。等
现在重要的是,在你们这个时候,你们不会诉诸于回到细节,例如
Given I have a taxi
Then I should have 2 wheel drive
And I should have 4 gears
是糟糕的,因为你混合了两个抽象层次,详细的抽象层次和更高层次的抽象层次。相反,你应该为出租车编写出租车场景
e.g。
Given I have a taxi
When my passengers puke over my interior
Then it should be easy to clean
这意味着您的名字必须对您的利益相关者很重要。
您从这种方法中获得的一件事是在更改规格时降低变更成本。例如,如果我们确定出租车应该是4wd,我们不必改变每个场景,我们只需改变步骤def`鉴于我有一辆出租车'。
我将按以下步骤实施步骤
Given 'I have a taxi' do
create_taxi
end
module TaxiStepHelper
def create_taxi
create_car(
engine:
drive:
...
)
end
我认为这种方法是“使用更高级别的抽象”。但Matt Wynne提出了一个很好的方式来描述它“推动下来”。我们正在做的是推动汽车配置如何从场景到步骤定义助手的配置。