-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RankChartのutils.js
内の処理のテストを追加
#868
Conversation
c71dc41
to
db3af87
Compare
お、vitestを導入するかどうかって話が出てたのか まあ、Astroが推奨してるしいいのではという感想ですが、いかがでしょう。 |
beforeEach(() => { | ||
/** テストの結果が変わらないように、最初の50記事のみを取得 */ | ||
const PUBLISHED_FIRST_50_ARTICLES = getArticles({ | ||
isPublished: true, | ||
}).slice(0, 50); | ||
RANKING = utils.getRanking(PUBLISHED_FIRST_50_ARTICLES); | ||
TOP_10_RANKING = utils.getTopNRanking(RANKING, 10); | ||
Object.freeze(TOP_10_RANKING); // Ensure that the array is not mutated | ||
}); | ||
|
||
it("getRanking", () => { | ||
expect(RANKING).toMatchSnapshot(); // Snapshotが大きいので別途ファイルに保存 | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このテストのやり方だと、今後ロジックへの変更が行われた時に、スナップショットを更新するだけになるので、こういったケースではスナップショットテストは向いていないと思います。
とりあえず getRanking の実装コードにあるコメントの動作を確認できる最小のデータとケースを作ってテストするのが良いと思いました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
とりあえず getRanking だけテストして、他は別になくてもいいんじゃないかなという気がしますね。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、snapshotじゃなくて
- ちゃんとソートされているか
- userのランクが適切かどうか
みたいなものを個別にテストした方がいいってことですね
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ちなみに他の関数についてもsnapshotじゃない方がいいんでしょうか
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、とりあえずgetRankingのテスト書き直します
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
42b0dd4
ロジックを一つずつテストに落としてみました
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
他もsnapshot使うのは適切でないからごそっと消すべき?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getData と getOptions はたいしたロジックもないので snapshot でいいんじゃないかなという印象ですね。
ただ、describe ブロックは分けたほうがいいと思います。
特に beforeEach で初期化したデータをすべての関数のテストで利用しているのは、変化にとても弱いのでよくないです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、一つ一つ分けてロジック書きますか
`getRanking`にコメントとして結果の変数が満たしうる条件を残しているので、それらが満たされているかどうかをチェック
8321faa
to
42b0dd4
Compare
/** ランクが1から始まっているか */ | ||
expect(ranking[0].rank).toBe(1); | ||
|
||
/** 同じ記事数の場合は同じランクになるか.ソートされているか. */ | ||
Array.from({ length: 50 }).forEach((_, i) => { | ||
const current = ranking[i]; | ||
const prev = ranking[i - 1]; | ||
|
||
/** どちらかが存在しない場合はスキップ */ | ||
if (!(current && prev)) { | ||
return; | ||
} | ||
|
||
if (prev && prev.articleCount === current.articleCount) { | ||
expect(current.rank).toBe(prev.rank); | ||
} else { | ||
expect(current.rank).toBeGreaterThan(prev.rank); | ||
} | ||
}); | ||
|
||
/** 記事数の合計が50であるか */ | ||
const totalArticleCount = ranking.reduce( | ||
(acc, { articleCount }) => acc + articleCount, | ||
0, | ||
); | ||
|
||
expect(totalArticleCount).toBe(50); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
同じ it ですべてテストせずに、 expect の数だけ it 分けたほうがテストケースの目的が単一になってよいです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、getRanking単体でdescribeを作ってitで分けるべきですね
Array.from({ length: 50 }).forEach((_, i) => { | ||
const current = ranking[i]; | ||
const prev = ranking[i - 1]; | ||
|
||
/** どちらかが存在しない場合はスキップ */ | ||
if (!(current && prev)) { | ||
return; | ||
} | ||
|
||
if (prev && prev.articleCount === current.articleCount) { | ||
expect(current.rank).toBe(prev.rank); | ||
} else { | ||
expect(current.rank).toBeGreaterThan(prev.rank); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
テストの中でループと条件分岐はバッドブラクティスです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この場合、どうすればいいんでしょう。
条件分岐なしにテストを書くのが難しそうなので気になりました
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
わかりました。具体的なもので作り直します
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ケースにそった input を用意して expect(getRanking(input)).toEqual(expected)
のようにテストすれば十分だと思います。
この場合は、 同じ記事数の場合は同じランクになる
と ソートされている
の2種類のinputを用意すれば大丈夫なはずです。
@tomoya 色々直しました!
|
const ARTICLES = [ | ||
{ githubUser: "alice" }, | ||
{ githubUser: "alice" }, | ||
{ githubUser: "alice" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "charlie" }, | ||
{ githubUser: "charlie" }, | ||
{ githubUser: "david" }, | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
だいぶいい感じになってきましたね。
それぞれのテストケースが何を目的としているのかわかるようになってきて、可読性も変更耐性も上がってきた感じがします。
次は、テストデータがこの ARTICLES ひとつですべてをまかなっているのを変えましょう。
いまだと、何か機能を追加したりするとき、テストデータを変更しなければ実現できない場合、このARTICLES の値を変更すると、その機能追加とは関係のないテストケースも失敗するようになるはずです(例:数が変わるといくつかのテストが落ちる)。
ですので、基本的にはそれぞれのテツトケースでテストデータを用意するようにしましょう。その際に、必要最小限のテストデータにすることで、データからテストの意図が読み取れるようになり、より可読性が向上します。
具体的には最初のテストのこのコードは、
const ranking = utils.getRanking(ARTICLES);
it("should start with rank 1", () => {
expect(ranking[0].rank).toBe(1);
});
こんな感じで、テストデータの順番から正しくランク順に並んでいることが示されればよい(数の重複は別のテストケースで担保するので、このテストではそういうデータを用いない)というわけです。
it("should start with rank 1", () => {
expect(utils.getRanking([
{ githubUser: "alice" },
{ githubUser: "bob" },
{ githubUser: "bob" },
{ githubUser: "bob" },
{ githubUser: "charlie" },
{ githubUser: "charlie" },
])).toEqual([
{
rank: 1,
user: "bob",
articleCount: 3,
},
{
rank: 2,
user: "charlie",
articleCount: 2,
},
{
rank: 3,
user: "alice",
articleCount: 1,
}
]);
});
考え方は最小構成と同じで、再現に必要なもの以外をすべて排除すること。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修正しました!レビューお願いします!
これで各テスト同士の依存がなくなったので、次はテストの質を改善するフェーズですかね。 |
it("should start with rank 1", () => { | ||
const ranking = utils.getRanking([ | ||
{ githubUser: "alice" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "charlie" }, | ||
{ githubUser: "charlie" }, | ||
]); | ||
expect(ranking[0].rank).toBe(1); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このテストだと、配列の1件目が rank 1 にはなってるけど、user などの値はが何でも構わないというテストになるわけですが、それが本当に担保するべき仕様なのでしょうか?
ちなみに、そうであるならば、テストデータの配列は1件でも十分になります。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ユーザーの値はなんでもいいですね。なので、一件でも十分だと思われます
it("should have correct ranking", () => { | ||
const ranking = utils.getRanking([ | ||
{ githubUser: "alice" }, | ||
{ githubUser: "alice" }, | ||
{ githubUser: "alice" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "bob" }, | ||
{ githubUser: "charlie" }, | ||
{ githubUser: "charlie" }, | ||
{ githubUser: "david" }, | ||
]); | ||
expect(ranking[0]).toEqual({ | ||
rank: 1, | ||
user: "alice", | ||
articleCount: 3, | ||
}); | ||
expect(ranking[1]).toEqual({ | ||
rank: 1, | ||
user: "bob", | ||
articleCount: 3, | ||
}); | ||
expect(ranking[0].rank).toBe(ranking[1].rank); | ||
|
||
expect(ranking[2]).toEqual({ | ||
rank: 3, | ||
user: "charlie", | ||
articleCount: 2, | ||
}); | ||
|
||
expect(ranking[3]).toEqual({ | ||
rank: 4, | ||
user: "david", | ||
articleCount: 1, | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correctとか、日本語だとよく「正しい」とか書いたりしますが、何が正しいのかが読んでも理解できないので、テストケースの説明としては不十分になります。
あと、1回の実行結果の expect を複数に分ける必要はなくて、配列ぜんぶを比較すればいいはずです。ぜんぶ比較しないのであれば、その理由が欲しいです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そうですね、一つのexpectにするのが適切ですね
vim-jp#868 (comment) - 説明に具体的な動作を追加 - 複数のexpectを一つにまとめ、配列全体で比較
1359a68
to
0d89882
Compare
修正しました! お願いします! |
@@ -4,7 +4,7 @@ import type { Article, Ranking } from "./types"; | |||
/** | |||
* ランキングを計算し、1つの配列にまとめる | |||
*/ | |||
export function getRanking(articles: Article[]): Ranking[] { | |||
export function getRanking(articles: Pick<Article, "githubUser">[]): Ranking[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
テストを書くと、必要なのは githubUser
だけだとわかるので、余計な引数を要求しなくていいことがわかりましたね。
expect(ranking[0].rank).toBe(1); | ||
}); | ||
|
||
it("should assign same rank for ties and skip next ranks appropriately", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
同順位がない場合のテストケースもあった方がいいと思いました。
そうすれば、仮にどう順位のロジックに変更があったとしても、そうじゃない場合は変わっていないということが担保されるためです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ちなみに今の同順位の仕様としては、引数配列の登場順という仕様が暗黙としてあり、名前順に変えるなどの変更を加えようとした時に、2つのテストケースがいい感じに機能します。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ちなみに登場順に並んでいる仕様に関してはテストした方がいいんでしょうか
例えば
const articles = [
{ githubUser: "alice" },
{ githubUser: "alice" },
{ githubUser: "bob" },
{ githubUser: "bob" },
];
と
const articles = [
{ githubUser: "bob" },
{ githubUser: "bob" },
{ githubUser: "alice" },
{ githubUser: "alice" },
];
の2パターン作るとか
重複あるなしを分けてみました |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これで良さそうですね!
ありがとうございます! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
細かいことを言うとコミットログがだいぶとっ散らかってしまっているなと思いましたが、細けぇこたぁいいんだよ、というのもあるので Approve しておきます。
以下は私見なので別の意見もあるかも。
- Revert はマージコミットに対してやればよい
- レビューの指摘とその修正をいちいちコミットに残す必要はない。後から見てわかりやすいか、Revert しやすいかの単位になっているかの方が重要
マージコミットに対してrevertは本当にそうですね 指摘をコミットに残しておくと後から振り返りやすいと思ったのですがどうなんでしょう もはや汚すぎてsquash mergeしたい |
とりあえずsquash mergeします |
昨日のリファクタリングが合ってるか確証が取れなかったのでテストを追加