如果在业务逻辑更改时失败,则单元测试是否被认为是脆弱的?


27

请参见下面的代码;它会测试以查看具有性别的女性是否有资格接受要约1:

[Fact]
public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale()
{
    var personId = Guid.NewGuid();
    var gender = "F";
    var person = new Person(personId, gender);

    var id = Guid.NewGuid();
    var offer1 = new Offer1(id,"Offer1");
    Assert.False(offer1.IsEligible(person));
}

此单元测试成功。但是,如果将来向女性提供“ Offer1”,它将失败。

可以接受的说法是-如果围绕报价1的业务逻辑发生了变化,那么单元测试就必须发生变化。请注意,在某些情况下(对于某些商品),业务逻辑会在数据库中更改,如下所示:

update Offers set Gender='M' where offer=1;

在某些情况下,在域模型中如下所示:

if (Gender=Gender.Male)
{
  //do something
}

另请注意,在某些情况下,背后的域逻辑会定期更改,而在某些情况下则不会。


2
换个角度思考:您是否要进行在更改被测系统中的逻辑时不会失败的测试?
法比奥

Answers:


77

通常情况下,这并不脆弱。如果单元测试由于实现更改而中断,但不影响测试行为,则认为该单元测试是易碎的。但是,如果业务逻辑本身发生变化,则对该逻辑的测试应该会失败。

就是说,如果业务逻辑确实经常变化,那么将期望硬编码到单元测试中也许是不合适的。相反,您可以测试数据库中的配置是否会按预期影响商品。

测试的名称Returns False When Given A Person With A Gender Of Female未描述业务规则。业务规则将是类似的Offers Applicable to M should not be applied to persons of gender F

因此,您可以编写一个测试来确认是否将要约定义为仅适用于M型人员,那么F型人员将不会被表示为有资格。该测试将确保即使特定商品的配置发生更改,逻辑也能正常工作。


@JaquesB,那么它将不会是单元测试?还是会?我相信如果涉及数据库,那将是一个集成测试。那正确吗?您是说如果业务逻辑发生很大变化就不要使用单元测试?
w0051977

@ w0051977:取决于您如何编写测试。如果测试包括实际更改数据库中的某些更改,那么它将是集成测试。
JacquesB

3
@ w0051977更好的主意-不要让存储库成为负责实现业务规则的组件的依赖项。有一个较高级别的业务流程,该业务流程会调用存储库,然后调用业务规则。现在,您可以单独对业务规则进行单元测试。
Ant P

5
@ w0051977当然是-测试指定行为。如果控制组件行为的规则发生了变化,则测试必须进行更改以反映行为的变化。什么不应该需要改变是指定比正在改变其他行为测试。如果行为是由数据库确定的,则涵盖其他一些代码的测试本质上是不相关的,除非数据库逻辑在测试范围之内,否则无需更改。该范围供您定义,它是单元测试还是集成测试的语义并不重要。
Ant P

3
@ w0051977在某种程度上扩展了这个想法,如果业务逻辑发生变化或错误已修复,并且测试不需要调整,则表明测试未涵盖足够的情况,通常应进行扩展。
摩根(Morgen)

14

在生产数据库(或用于测试的克隆)中定义属性后,这不是单元测试。单元测试检查工作单元,不需要特定的外部状态即可工作。假定Offer1在数据库中将其定义为仅限男性的商品。那是外部状态。因此,这更多是集成测试,特别是系统测试或验收测试。请注意,验收测试通常没有编写脚本(不在测试框架中运行,而是由人类手动执行)。

当在域模型中使用if语句定义属性时,同一测试就是单元测试。而且它可能很脆。但是真正的问题是代码很脆弱。通常,如果可配置而不是硬编码业务行为,则代码将更具弹性。因为急于部署以解决小的编码错误应该很少。但是,更改业务需求而不另行通知只是星期二(每周发生一次)。

您可能正在使用单元测试框架来运行测试。但是单元测试框架不仅限于运行单元测试。他们也可以并且确实运行集成测试。

如果你正在写一个单元测试,你就同时创建person,并offer1从一开始就对数据库状态没有依赖。就像是

[Fact]
public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale()
{
    var personId = Guid.NewGuid();
    var gender = "F";
    var person = new Person(personId, gender);

    var id = Guid.NewGuid();
    var offer1 = new Offer1(id, "ReturnsFalseWhenGivenAPersonWithAGenderOfFemale");
    offer1.markLimitedToGender("M");

    Assert.False(offer1.IsEligible(person));
}

请注意,这不会根据业务逻辑而改变。并不是说offer1拒绝女性。这是offer1拒绝女性的一种提议。

您可以在测试中创建和配置数据库。在C#中,使用NUnit或在Java的JUnit中,您可以通过一种Setup方法来设置数据库。大概您的测试框架具有类似的概念。在这种方法中,您可以使用SQL将记录插入数据库。

如果您很难编写用测试数据库代替生产数据库的代码,这听起来像是应用程序中的测试弱点。对于测试,最好使用诸如依赖注入之类的允许替换的东西。然后,您可以编写独立于当前业务规则的测试。

这样做的附带好处是,企业所有者(不一定是企业所有者,更像是企业层次结构中负责此产品的人员)通常更容易直接配置业务规则。因为如果您拥有这种技术框架,就很容易允许企业所有者使用用户界面(UI)来配置商品。企业所有者将在UI中选择限制,然后它将发出markLimitedToGender("M")呼叫。然后,当要约持久化到数据库时,它将存储此内容。但是您不需要存储要使用的报价。因此,您的测试可以创建和配置数据库中不存在的商品。

在所描述的系统中,企业所有者必须向技术组提出请求,技术组将发出适当的SQL并更新测试。或者技术小组必须编辑您的代码和测试(或先测试,然后再编码)。这似乎是一个相当沉重的方法。你能行的。但是,如果您不必这样做的话,您的软件(而不仅仅是您的测试)将不会那么脆弱。

TL; DR:您可以编写这样的测试,但是最好不要编写软件,这样就不必这样做。


我可以自由地改善您的答案中的一些小细节。请检查我是否正确理解了您的意图。
布朗

原始帖子中没有表明涉及数据库。因此声称它认为Offer1已经在数据库中是奇怪的。
温斯顿·埃韦特

2
@WinstonEwert:有明确的指示,您必须仔细阅读问题。一读时我也没有意识到,但这确实是OP所说的。
布朗

@ mdfst13,很抱歉我错过了。但是,OP所说的是条件有时在数据库中,有时在域模型中。对于第一种情况,您的回答是完美的,而对于第二种情况,您的回答是毫不留情的。如果您要修改答案以澄清这一点,我将删除仓促的投票。
温斯顿·埃韦特18'Dec
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.