如何避免巧合测试通过

时间:2019-12-05 00:56:17

标签: angular jasmine tdd bdd

我有以下测试案例:

it('is to show welcome message', () => {
    spyOnProperty(authServiceSpy, 'token').and.returnValue(environment.testAuthenticationToken);

    let teacher: Teacher = authServiceSpy.token.teacher;
    let welcome: HTMLElement = ne.querySelector('#welcome-msg');
    expect(welcome).toBeTruthy();
    expect(welcome.innerHTML).toEqual(`Welcome ${teacher.firstName}`);
});

修改为environment.testAuthenticationToken

testAuthenticationToken: {
    "type": "teacher",
    "emailVerified": true,
    "teacher": {
        "_id": "0000000000000000000000000",
        "title": "Teacher-X",
        "firstName": "X",
        "lastName": "X",
        "locale": "en-US",
        ...
        "emailVerified": true,
        ...
    }
}

这是相应的模板html

<div id="welcome-msg">Welcome X</div>

如果我使用的是TDD,因此从测试开始,然后进行一个最小的实现,使测试通过,那么在这种情况下,我会在HTML X中用硬编码老师的姓名< / em>。测试通过了,尽管在一般情况下实现被破坏了。如果我停止了这一天的工作,第二天又来运行测试,我可能会认为,由于测试通过,所以代码中可能没有问题。

这是预期结果,重构是下一步可能检测到的结果,还是使用随机预期来规避此结果?还是我走错路了?

谢谢。

2 个答案:

答案 0 :(得分:1)

  

如果我在这一天停止工作,第二天又来运行测试,我可能会认为,由于测试通过,所以代码中可能没有问题。

对此特定问题的一个常见答案是更改您的工作例程,以便一天回家时总会出现测试失败的情况。第二天早上,失败的测试充当书签,使您回到工作的环境。

当然,您通常不会在这种状态下发布代码。

当肯特·贝克(Kent Beck)在他的书中描述TDD时,该过程包括他希望编写的测试清单。当他想到新的想法时,他可能会花一点时间写下来,然后继续进行当前的工作。他后来发现的有趣的测试将被剔除。

当列表中的所有项目都被划掉并且您想不出要添加的任何新项目时,操作就会完成。

如果您的测试目的是 也是文档,那么应该直接阅读已实现的测试列表以发现差距。凯夫琳·亨尼(Kevlin Henney)在MRU清单/ leap年计算/堆栈方面的演讲表明,当问题很小时,会是什么样子。

当然,您也可以通过... 测试代码来发现缺陷。或者,是X以外的其他人第一次使用该代码时就报告错误(假设故障导致了failure

答案 1 :(得分:0)

这个问题与TDD方法有关,而不是它在有角前端中的应用

进行测试时,“随机”是禁止的词。除非您没有明确地故意制造混乱,否则所有内容都应该是可重现的:mutation testing的工作是这样,它有助于捕获一些无用的测试或某些情况,在这种情况下不会从测试中检测到错误的实现

但是,我坚持不懈地提出这个例子

describe("authServer.login()" , () => {
    it("should return true if the user exists and password valid" , () => {

        let result = uut.login("VALIDUSER" , "VALIDPASSWORD")

        expect(result).to.be.true
    })
})

我可以简单地通过

function login (username, password) {
     return true
}

现在,如何迫使我更改该实现以使我的功能有价值?

1)不要从幸福的道路开始

通常,当我们编写新代码时,我们从快乐的路径开始,然后添加所有需要的异常(不打算作为软件异常:-D)处理。这根本没有错,实际上是一种非常敏捷的方法,因为它使您可以编写一些工作代码,而不必费神思索所有事情。但是,如果我们有能力并且很幸运,有时将我们的代码拆分为许多小组件并预先设计其行为并不难。在登录情况下,很容易从

开始
describe("authServer.login()" , () => {
    it("should return false if the user doesnt exists" , () => {

        let result = uut.login("NOTFOUNDUSER" , "VALIDPASSWORD")

        expect(result).to.be.false
    })
})

function login (username, password) {
     return false
}

但是,由于您对自己的前进道路不满意,因此必须进行更改

2)从代码中“期望更多”

这是一个令人争论的论点,鲍勃叔叔在其“清洁代码”中写道:在测试中包含多个断言是一种好习惯吗?我没有比参加这场辩论的人更好,但是我可以通知,定义了功能的每组数据都可以划分为“等价类”,因为开发人员编写的功能不是内射的,因此添加了两个断言来自同一个等价类的问题就像拥有一个等价类,但有更多的约束条件

describe("sum()" , () => {
    it("should sum correctly two addends , () => {

        expect(sum(3,7)).to.eq(10)
        expect(sum(1,2)).to.eq(3)
    })
})

现在您不能再对数字进行硬核了。如果确实困扰您,那么您就可以摆脱两者之一了

3)重构不仅仅是编写更好的代码

但也可以使其在不改变黑匣子行为的情况下正常工作。当您的婴儿步骤通过测试后,您必须返回代码进行审查,这是tdd周期!在这种情况下,您会注意到生产代码是一个骗局,您将对其进行处理。如果您没有时间,因为您的队友在逼您去吃午饭,在推动之前故意弄乱了测试

describe("authServer.login()" , () => {
    it("should return true if the user exists and password valid" , () => {

        let result = uut.login("VALIDUSER" , "VALIDPASSWORD")

        expect(result).to.be.true
        expect(true).to.be.false //<----- Have a Nice meal
    })
})

除此之外,请记住,在TDD中测试是活的东西,它们随着代码的变化而变化,您可以利用它