Sinon: v7.2.0 릴리슀 ν›„ 예기치 μ•Šκ²Œ μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈ

에 λ§Œλ“  2019λ…„ 07μ›” 23일  Β·  11μ½”λ©˜νŠΈ  Β·  좜처: sinonjs/sinon

버그 μ„€λͺ…
Sinon을 μ΅œμ‹  v7 릴리슀둜 μ—…κ·Έλ ˆμ΄λ“œν•˜λ©΄ 예기치 μ•Šμ€ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€. λ‚΄κ°€ 확인할 수 μžˆμ—ˆλ˜ μœ μΌν•œ κ΄€λ ¨ λ³€κ²½ 사항은 PR : # 1955- deep-equal.js λ₯Ό samsam.deepEqual λŒ€μ²΄ν–ˆμŠ΅λ‹ˆλ‹€.
ν…ŒμŠ€νŠΈλŠ” 7.2.0κΉŒμ§€μ˜ λͺ¨λ“  λ²„μ „μ—μ„œ ν†΅κ³Όν•˜κ³ , 7.1.1 (ν•œ 릴리슀 λ’€λ‘œ)둜 λ˜λŒλ €λ„ λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•Šκ³  λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όλ©λ‹ˆλ‹€.

κ΄€μ°° 된 였λ₯˜ :

AssertionError: expected addTracks to have been called with arguments [{ language: "en", trackContentType: "CEA608" }]
[[functionStub] { language: "en", trackContentType: "CEA608" }] [{ language: "en", trackContentType: "CEA608" }]

ν…ŒμŠ€νŠΈ 생성 였λ₯˜ :

    it('should call getTextTracksManager, Track, and setCaptionLang', () => {
      sinonSandbox.stub(chromecastReceiver, 'setCaptionLang');
      chromecastReceiver.player.getTextTracksManager = sinonSandbox.stub()
        .returns({ addTracks: sinonSandbox.spy() });
      sinonSandbox.stub(cast.framework.messages, 'Track').returns(() => ({}));

      chromecastReceiver.loadCaptions('off');

      expect(chromecastReceiver.player.getTextTracksManager).to.have.callCount(1);
      expect(cast.framework.messages.Track).to.have.been
        .calledWith(3, cast.framework.messages.TrackType.TEXT);
      expect(chromecastReceiver.textTracksManager.addTracks).to.have.been.calledWith([{
        trackContentType: cast.framework.messages.CaptionMimeType.CEA608,
        language: 'en',
      }]);
      expect(chromecastReceiver.setCaptionLang).to.have.been.calledWith('off');
    });

μž¬ν˜„ν•˜λ €λ©΄
λ™μž‘μ„ μž¬ν˜„ν•˜λŠ” 단계 :

  1. 7.1.1 μ΄μƒμ˜ Sinon 버전 μ‚¬μš©
  2. μƒ˜ν”Œ ν…ŒμŠ€νŠΈ μ‹€ν–‰ (μœ„)
  3. 였λ₯˜ μ°Έμ‘°

μ˜ˆμƒλ˜λŠ” 행동
ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όν•΄μ•Όν•©λ‹ˆλ‹€.

슀크린 μƒ·
ν•΄λ‹Ήλ˜λŠ” 경우 문제λ₯Ό μ„€λͺ…ν•˜λŠ” 데 λ„μ›€μ΄λ˜λŠ” 슀크린 샷을 μΆ”κ°€ν•©λ‹ˆλ‹€.

μ»¨ν…μŠ€νŠΈ (λ‹€μŒ 정보λ₯Ό μž‘μ„±ν•˜μ‹­μ‹œμ˜€) :

  • 라이브러리 버전 : 7.2.0+
  • ν™˜κ²½ : macOS 10.14.5

이에 λŒ€ν•œ μžμ„Έν•œ 정보λ₯Ό 제곡 ν•  수 μžˆλŠ”μ§€ μ•Œλ €μ£Όμ‹­μ‹œμ˜€.

Bug Regression pinned

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

μ•ˆλ…•ν•˜μ„Έμš” @ fatso83 ,

μ‹œκ°„μ„λ‚΄μ–΄ 쑰사해 μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€. λ‚˜λŠ” 당신이 μš”μ²­ ν•œλŒ€λ‘œ 버그λ₯Ό λ³΄μ—¬μ£ΌλŠ” λŸ°ν‚· 데λͺ¨λ₯Ό λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ require('sinon') 문을 주석 / 주석 ν•΄μ œν•˜κ³  μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜μ—¬ 버그λ₯Ό ν™•μΈν•˜μ‹­μ‹œμ˜€.

https://runkit.com/danielkg/sinon-issue-reproducible-bug-template

μ–΄λ–€ 이유둜 λ“  runkit이 μž‘λ™ν•˜μ§€ μ•ŠλŠ” 경우λ₯Ό λŒ€λΉ„ν•˜μ—¬ 여기에 μ½”λ“œλ₯Ό κ²Œμ‹œν•©λ‹ˆλ‹€.

// Employs 'mini-mocha' to emulate running in the Mocha test runner (mochajs.org)
require("@fatso83/mini-mocha").install();
// const sinon = require('[email protected]'); // WORKS!
const sinon = require('[email protected]'); // FAILS!
// const sinon = require('sinon');       // FAILS!
const { assert } = require('@sinonjs/referee');

const SAMPLE_USER = {
    id: 1,
    name: 'Kid',
    age: 22,
    weight: 83.5,
    notes: [{ txt: 'abc' }, { txt: 'def' }, { txt: 'ghi' }],
};

class MyUser {
    constructor(id, name, age, weight, notes = []) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.notes = notes;
    }
}

const createFakeUser = overrides => ({
    id: '007',
    name: 'Bond',
    ...overrides,
});

class Talkie {
    talk(user, topic) {
        console.log('talked to', user.name, 'about', topic);
    }
}

const action = (userId, talkie) => {
    const { name, age, weight, notes } = SAMPLE_USER;
    const user = new MyUser(userId, name, age, weight, notes);

    talkie.talk(user, 'apples');

    return user;
}


describe('stubbing', () => {
    const talkie = new Talkie();

    afterEach(() => {
        sinon.restore();
    });

    beforeEach(() => {
        sinon.spy(talkie, 'talk');
    });

    it('is the same user', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        assert.equals(JSON.stringify(userA), JSON.stringify(userB));
    });

    it('should set the return value', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        sinon.assert.calledWith(talkie.talk, userB, sinon.match.string);
    });
});

μ‹œκ°„μ΄μžˆμ„ λ•Œ λ‹€μ‹œ ν•œ λ²ˆλ³΄μ„Έμš”. κ°μ‚¬ν•©λ‹ˆλ‹€.

κ°μ‚¬ν•©λ‹ˆλ‹€.
λ‹€λ‹ˆμ—˜

λͺ¨λ“  11 λŒ“κΈ€

일반적으둜 ν›Œλ₯­ν•œ 버그 λ³΄κ³ μ„œμ— κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€. ν•˜μ§€λ§Œ ν•œ 가지 큰 결함이 μžˆμŠ΅λ‹ˆλ‹€. κ²€μ¦μ΄λ‚˜ 버그 좔적을 μœ„ν•΄ κ·€ν•˜μ˜ 예제λ₯Ό μ‹€ν–‰ν•  수 μ—†μŠ΅λ‹ˆλ‹€! 자체적으둜 μ‹€ν–‰ν•  수 μ—†μœΌλ©° μž¬ν˜„ 방법이 μ—†μœΌλ©΄μ΄λ₯Ό κ³ μΉ˜κΈ°κ°€ μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

μ΅œμ†Œν•œμ˜ λ²ˆμ‹ 후보λ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆκΉŒ? var myObject = { aMethod: function(){}, aProp: 42 }; // etc 와 같은 λ‹¨μˆœν™”μ²˜λŸΌ 전체 원본 예제λ₯Ό 볡제 ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.

이것이 κ³ μž₯λ‚¬λ‹€λŠ” 것을 μ•Œμ•˜λ‹€λ©΄ λ¬Όλ‘  메이저 버전을 μ˜¬λ Έμ„ κ²ƒμ΄λ―€λ‘œ νšŒκ·€ ν…ŒμŠ€νŠΈκ°€ 쒋을 κ²ƒμž…λ‹ˆλ‹€!

μΆ”μ‹  : sinon-chai 도 κ΄€λ ¨λœ 것 κ°™μŠ΅λ‹ˆλ‹€. 그것은 λ‚˜μ—΄ ν•  κ°€μΉ˜κ°€ μžˆμŠ΅λ‹ˆλ‹€.

@ fatso83 볡제λ₯Ό μœ„ν•΄ λΆ„λ¦¬λ˜κ³  μ‹€ν–‰ κ°€λŠ₯ν•œ 예제λ₯Ό μ–»λŠ” 방법을 λ³΄κ² μŠ΅λ‹ˆλ‹€.

μ—…λ°μ΄νŠΈκ°€ μžˆμŠ΅λ‹ˆκΉŒ?

이 λ¬Έμ œλŠ” 졜근 ν™œλ™μ΄ μ—†μ—ˆκΈ° λ•Œλ¬Έμ— μžλ™μœΌλ‘œ 였래된 κ²ƒμœΌλ‘œ ν‘œμ‹œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. 더 이상 ν™œλ™μ΄ λ°œμƒν•˜μ§€ μ•ŠμœΌλ©΄ νμ‡„λ©λ‹ˆλ‹€. κ·€ν•˜μ˜ 기여에 κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€.

@mantoni ν”„λ‘œμ νŠΈμ—μ„œ 이런 일이 λ°œμƒν–ˆλ‹€κ³ λ³΄κ³ ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆκΉŒ?

μ—…λ°μ΄νŠΈκ°€ μ—†μ–΄μ„œ μ£„μ†‘ν•©λ‹ˆλ‹€. 더 이상 λ¬Έμ œκ°€ 포함 된 ν”„λ‘œμ νŠΈμ™€ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ μ‚¬λžŒμ΄ 같은 문제λ₯Ό κ²½ν—˜ν•  수 μžˆλ‹€λ©΄ μž¬ν˜„μ˜ 예λ₯Ό μ œκ³΅ν•˜λ©΄ 쒋을 κ²ƒμž…λ‹ˆλ‹€.

@ fatso83 같은 λ¬Έμ œκ°€ λ°œμƒν•œ 기얡이 μ—†μŠ΅λ‹ˆλ‹€. λͺ‡ 가지 ν”„λ‘œμ νŠΈμ—μ„œ λ¬Έμ œμ—†μ΄ μ΅œμ‹  Sinon으둜 μ—…κ·Έλ ˆμ΄λ“œν–ˆμŠ΅λ‹ˆλ‹€.

μž¬ν˜„ ν•  수 μ—†μŒμœΌλ‘œ λ‹«κΈ°

이 문제λ₯Ό μ£½μŒμ—μ„œ λ˜μ‚΄λ € μ„œ μ£„μ†‘ν•˜μ§€λ§Œ 같은 λ¬Έμ œμ— λ΄‰μ°©ν–ˆμŠ΅λ‹ˆλ‹€.

_ μ•„λž˜ μ½”λ“œλŠ” μ„€λͺ…을 μœ„ν•΄ λ‹¨μˆœν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€ ._

λ‚΄ μ‘μš© ν”„λ‘œκ·Έλž¨μ—λŠ”μ΄ μˆ˜μ—…μ΄ μžˆμŠ΅λ‹ˆλ‹€.

class MyUser {
  // ... some user properties  
}

λ‚˜μ€‘μ— 이와 같은 Koa 컨트둀러 κΈ°λŠ₯μ—μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€.

const mqClient = require('./mqClient');

// GET /userDetails/<id> endpoint
async function getUserDetailsController(req, res) {
  const user = new MyUser();

  // populate user with properties etc.
  user.id = '1';
  user.name = 'Hans Gruber';
  user.tasks = [{ id: '42', title: 'execute Nakatomi tower heist' }];

  // ... get more details ...

  // Notify other users about this user
  mqClient.notifyOthers(user);

  res.json({ ok: true, user });
}

그리고 λ‚΄ getUserDetailsController.test.js λ‹€μŒκ³Ό κ°™μ΄ν•©λ‹ˆλ‹€.

const mqClient = require('./mqClient');

const createExpectedUser = overrides => {
  id: '1',
  name: 'Hans Gruber',
  tasks: [{ id: '42', title: 'execute Nakatomi tower heist' }],
  ...overrides
};

it('does not fail', async () => {
  sinon.spy(mqClient, 'notifyOthers');

  const expectedUser = createExpectedUser();

  await presetRequest // helper object to run the controller code
    .get('/userDetails/1')
    .expect(200);

  // This works with sinon 7.1.1.
  // This fails with sinon 7.2.0 and onwards.
  sinon.assert.calledWithMatch(mqClient.notifyOthers, expectedUser);
});

λ‚˜λŠ” 이것을 sinon 7.1.1 ( samsam 의 "2.1.3"을 μ‚¬μš©ν•¨)으둜 ν…ŒμŠ€νŠΈν–ˆκ³ , μž‘λ™ν•©λ‹ˆλ‹€.
버전 7.2.0이 μ‹€νŒ¨ν•©λ‹ˆλ‹€ ( samsam 3.3.3 μ‚¬μš©).
μ΅œμ‹  버전 9.2.3도 μ‹€νŒ¨ν•©λ‹ˆλ‹€ ( samsam 5.3.0 μ‚¬μš©).

μ €λŠ” Node 14.5.3에 μžˆμ§€λ§Œ 이것은 Node 12.20.0μ—μ„œλ„ λ°œμƒν•©λ‹ˆλ‹€.

컨트둀러의 user 와 expectedUser λ₯Ό 톡해 ν…ŒμŠ€νŠΈμ—μ„œ JSON.stringify μˆ˜λ™μœΌλ‘œ λΉ„κ΅ν–ˆλŠ”λ° 100 % λ™μΌν•©λ‹ˆλ‹€. ꡬ문 상 μœ μΌν•œ 차이점은 μ»¨νŠΈλ‘€λŸ¬λŠ” 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” 반면 ν…ŒμŠ€νŠΈλŠ” 개체 λ¦¬ν„°λŸ΄μ„ μ‚¬μš©ν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ™œ 이런 일이 λ°œμƒν•˜λŠ”μ§€ 쒋은 μ‚¬λžŒμ΄ μžˆμŠ΅λ‹ˆκΉŒ? μ•„λ‹ˆλ©΄μ΄ 문제λ₯Ό ν•΄κ²°ν•˜λŠ” 방법에 λŒ€ν•œ 쒋은 아이디어가 μžˆμŠ΅λ‹ˆκΉŒ?

κ°μ‚¬ν•©λ‹ˆλ‹€.
λ‹€λ‹ˆμ—˜

이λ₯Ό 확인할 μˆ˜μžˆλŠ” μž¬ν˜„ κ°€λŠ₯ν•œ μ‚¬λ‘€κ°€μ—†λŠ” ν•œ,이 문제λ₯Ό μ‘°μ‚¬ν•˜λŠ” 데 무료 μ‹œκ°„μ„ λ³΄λ‚΄λŠ” 것을 μ •λ‹Ήν™” ν•  수 μ—†μŠ΅λ‹ˆλ‹€. λ‘λ ΅μŠ΅λ‹ˆλ‹€. λ‚˜λŠ” 찾을 것이없고 λ‚΄κ°€ μ˜³λ‹€λŠ” 것을 증λͺ…ν•  것도 없을 κ²ƒμž…λ‹ˆλ‹€.

κ·Έλž˜λ„ 버그λ₯Ό κ³ μΉ˜λŠ” 것을 μ’‹μ•„ν•˜λ―€λ‘œ _ λ‚΄κ°€ μ‹€ν–‰ν•  μˆ˜μžˆλŠ” _ 무언가λ₯Ό λ§Œλ“œλŠ” 데 μ‹œκ°„μ„ ν• μ•  ν•  수 μžˆλ‹€λ©΄ κ·Έλ ‡κ²Œν•˜μ„Έμš”! RunKit은이λ₯Όμœ„ν•œ ν›Œλ₯­ν•œ μ„œλΉ„μŠ€μž…λ‹ˆλ‹€ : https://runkit.com/fatso83/sinon-issue-reproducible-bug-template

μ•ˆλ…•ν•˜μ„Έμš” @ fatso83 ,

μ‹œκ°„μ„λ‚΄μ–΄ 쑰사해 μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€. λ‚˜λŠ” 당신이 μš”μ²­ ν•œλŒ€λ‘œ 버그λ₯Ό λ³΄μ—¬μ£ΌλŠ” λŸ°ν‚· 데λͺ¨λ₯Ό λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ require('sinon') 문을 주석 / 주석 ν•΄μ œν•˜κ³  μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜μ—¬ 버그λ₯Ό ν™•μΈν•˜μ‹­μ‹œμ˜€.

https://runkit.com/danielkg/sinon-issue-reproducible-bug-template

μ–΄λ–€ 이유둜 λ“  runkit이 μž‘λ™ν•˜μ§€ μ•ŠλŠ” 경우λ₯Ό λŒ€λΉ„ν•˜μ—¬ 여기에 μ½”λ“œλ₯Ό κ²Œμ‹œν•©λ‹ˆλ‹€.

// Employs 'mini-mocha' to emulate running in the Mocha test runner (mochajs.org)
require("@fatso83/mini-mocha").install();
// const sinon = require('[email protected]'); // WORKS!
const sinon = require('[email protected]'); // FAILS!
// const sinon = require('sinon');       // FAILS!
const { assert } = require('@sinonjs/referee');

const SAMPLE_USER = {
    id: 1,
    name: 'Kid',
    age: 22,
    weight: 83.5,
    notes: [{ txt: 'abc' }, { txt: 'def' }, { txt: 'ghi' }],
};

class MyUser {
    constructor(id, name, age, weight, notes = []) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.notes = notes;
    }
}

const createFakeUser = overrides => ({
    id: '007',
    name: 'Bond',
    ...overrides,
});

class Talkie {
    talk(user, topic) {
        console.log('talked to', user.name, 'about', topic);
    }
}

const action = (userId, talkie) => {
    const { name, age, weight, notes } = SAMPLE_USER;
    const user = new MyUser(userId, name, age, weight, notes);

    talkie.talk(user, 'apples');

    return user;
}


describe('stubbing', () => {
    const talkie = new Talkie();

    afterEach(() => {
        sinon.restore();
    });

    beforeEach(() => {
        sinon.spy(talkie, 'talk');
    });

    it('is the same user', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        assert.equals(JSON.stringify(userA), JSON.stringify(userB));
    });

    it('should set the return value', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        sinon.assert.calledWith(talkie.talk, userB, sinon.match.string);
    });
});

μ‹œκ°„μ΄μžˆμ„ λ•Œ λ‹€μ‹œ ν•œ λ²ˆλ³΄μ„Έμš”. κ°μ‚¬ν•©λ‹ˆλ‹€.

κ°μ‚¬ν•©λ‹ˆλ‹€.
λ‹€λ‹ˆμ—˜

이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰