Ryoma137 / record-of-expense

0 stars 0 forks source link

(支出)支出情報の登録 #8

Open Ryoma137 opened 2 years ago

Ryoma137 commented 2 years ago
Ryoma137 commented 2 years ago

現在の総資産をDBに登録するために、DBに現在の総資産を表すcolumnの追加が必要

Ryoma137 commented 2 years ago

登録ボタンを押すことにより、入力欄に入力した各項目がDBに保存される 着手開始

Ryoma137 commented 2 years ago

入力欄の各値に入力した値をbindingするためにth:fieldを追加

6.2 Inputs https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#inputs

AmountRepository内に@Query("UPDATE amount SET name=?, price=?, category=?,comments=?")と@ModifyingをつけたupdateExpenseメソッドの作成

Annotation Type Modifying https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/Modifying.html

Ryoma137 commented 2 years ago

入力欄のフォームに 'th:object="${registerExpense}"'を 追加。 AmountRepository内に追加したUPDATEのクエリーをINSERT文に変更。

Exception evaluating SpringEL expression: "name" thymeleafServlet.service() for servlet [dispatcherServlet] in context with path [] threw exceptionとエラーが出ているので、以下のqiitaの記事を参考に、AmountのEntitiyでgetterとsetterの作成方法を@Dataのアノテーション付与から、setter、setterのメソッド名が[get + フィールド名] 、[set + フィールド名]がなるように手動で作成。 → 同じエラーが発生、エラーの解決にはならず。

https://qiita.com/axs-dev/items/f21cb004560416e98680

Ryoma137 commented 2 years ago

課題: 最初の時点で問題が起きているのがフロント側なのか、サーバー側なのかの問題解決の当てができていない。

t-ita commented 2 years ago

@Ryoma137

認識が違うので指摘しておきます。

板垣さんに相談すると、サーバーサイドは最初に書いたUdemyで参考にしたものであっている。  → サーバーサイドに間違いはないので、Exception evaluating SpringEL expression: "name" のエラーはフロント側で起きているエラーと判明。

フロント側で起きているエラーではありません。サーバーサイドのエラーです。 Udemy の例は間違いではないが、石崎さんの書いたコードが Udemy の記述方法とは異なっていた事が原因でした。

具体的には、PostMapping された メソッドの引数には、任意の DTO を指定すべきところを、Model 型にしていたことがエラーの原因です。(Model 型には name というフィールドはないですからね)

Ryoma137 commented 2 years ago

DAOで指定する箇所をModel型からnameフィールドのあるAmount型に変更しても同じエラー(Exception evaluating SpringEL expression: "name")が発生。  → 矢納さんに相談すると、POSTのメソッドを実行する前にGetのメソッドでエラーが発生していることが判明。   th:objectで指定したModelAttribute ('@ModelAttribute') をGetのメソッドに引数として追加。

Exception evaluating SpringEL expression: "name"のエラー解決したが、入力欄に情報を入力し、登録ボタンを押すとServlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Modifying queries can only use void or int/Integer as return type! Offending method:のエラーが発生。

Ryoma137 commented 2 years ago

(割り込みタスク)DBに保存されている支出額の合計金額を支出画面に表示 着手開始

Ryoma137 commented 2 years ago

CrudRepositoryのsaveを使用するために、自分で作ったAmountRepository内のsaveを削除。

すると、登録ボタンを押すと発生するエラーが'Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.AMOUNT(ID) ( / key:1 / CAST(1 AS BIGINT), 'Food', 'What a scrumptious sandwich', 'Sandwich', 450)"; SQL statement:' に変わる。 IDの値をBIGINT型にCASTする際に何かエラーが起きていると推測。

下記の記事を参考に@Column(columnDefinition = "id BIGINT GENERATED BY DEFAULT AS IDENTITY DEFAULT ON NULL PRIMARY KEY")をAmount のlong idに付与。 https://stackoverflow.com/questions/71101007/jdbcsqlintegrityconstraintviolationexception-when-inserting-new-entry-after-sche

'Error executing DDL "create table amount (id id BIGINT GENERATED BY DEFAULT AS IDENTITY DEFAULT ON NULL PRIMARY KEY not null, category varchar(255), comments varchar(255), name varchar(255), price integer not null, primary key (id))" via JDBC Statement'が発生。→ 朝会後にエラーログ等の詳細確認

Ryoma137 commented 2 years ago

@Column(columnDefinition = "id BIGINT GENERATED BY DEFAULT AS IDENTITY DEFAULT ON NULL PRIMARY KEY")がそもそもあっているかわからないので削除して再度実行してエラーログを確認。 すると、他にもSQLState: 23505とろくに書かれていたので、SQLState: 23505のエラーの意味を調べてみると、uniqueであるはずの値がが重複している際に出るエラーと判明。 アプリを実行するとH2のDBが作成されるが、そのタイミングで、生成されたDBの値をTRUNCATE TABLE文で削除する必要があると推測。 → H2は組み込みのDBで起動時に自動で作成されるからどこにTRUNCATEの処理を記載したらいいかわからない。resourcesフォルダの下にsqlファイルを作成して、そこにTRUNCATE TABLE文を書く?

DB2 SQL-Error: -803 SQLState: 23505 https://www.sqlerror.de/db2_sql_error_-803_sqlstate_23505.html

Ryoma137 commented 2 years ago
Ryoma137 commented 2 years ago

@GeneratedValue https://itpfdoc.hitachi.co.jp/manuals/link/cosmi_v0870/APR4/EU260072.HTM#ID00294 Enum GenerationType https://docs.oracle.com/javaee/6/api/javax/persistence/GenerationType.html

GenerationType.IDENTITYでデータベースのidentity列を利用してprimary key値を作成されているため、IDが重複することがないので、以下の2点のテストパターンは必要ないのではと推測

org.h2.api Class ErrorCode DUPLICATE_KEY_1 https://www.h2database.com/javadoc/org/h2/api/ErrorCode.html

t-ita commented 2 years ago

@Ryoma137

GenerationType.IDENTITYでデータベースのidentity列を利用してprimary key値を作成されているため、IDが重複することがな> いので、以下の2点のテストパターンは必要ないのではと推測

与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていること

与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないこと

上記、推測の根拠を「理論立てて説明してもらえれば」、削除しても大丈夫です。

Ryoma137 commented 2 years ago

@t-ita GenerationType.IDENTITYはデータベースのidentity列を利用してprimary key(ID値)が自動生成される(仮に既存のDBに3つデータが入っていた際は4のID値が自動で生成される)ため、与えたれたデータのID値は既存のDBに保存されているIDと重複されることがない。 そのため、与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複しているか否かのテストは必要ない。

このような説明でよろしいでしょうか?

@Ryoma137

GenerationType.IDENTITYでデータベースのidentity列を利用してprimary key値を作成されているため、IDが重複することがな> いので、以下の2点のテストパターンは必要ないのではと推測 与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていること 与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないこと

上記、推測の根拠を「理論立てて説明してもらえれば」、削除しても大丈夫です。

Ryoma137 commented 2 years ago

RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在する時、与えられたデータでDBが更新されること) 着手開始 予定時間(見積り)時間 : 1.0h

Ryoma137 commented 2 years ago

RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在する時、与えられたデータでDBが更新されること) 着手終了(Ready for review) 予定時間(見積り)時間 : 1.0h 実働時間 17分

Ryoma137 commented 2 years ago

RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在しない時、DBが更新されないこと) 着手開始 予定時間(見積り)時間 : 0.5h

Ryoma137 commented 2 years ago

RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在しない時、DBが更新されないこと) 着手終了 (Ready for review) 予定時間(見積り)時間 : 0.5h 実働時間: 18分

Ryoma137 commented 2 years ago

AmountRepositoryTest内に記載しているテストメソッドごとにテストを実行すると、テストが通るが、AmountRepositoryTestのテストを一斉に起動すると、各テストメソッド追加したDBの値が消えていないので、エラーになる。sqlに書いているTRUNCATE TABLE Amount;がうまく機能していない?

そもそも、@GeneratedValueで生成したIDが付与されているデータは削除できるのかを確認するために、@GeneratedValueで生成したIDが付与されているデータが削除できることのテストを作成。  → @GeneratedValueで生成したIDが付与されているデータは削除可能と確認

月曜日のペアプロで板垣さんがテストを実行する際に読み込むメソッドの順番は不定期なのでsqlアノテーションをつけ箇所を考えないといけないと、言っていた事と関連があると推測。

beforeEachのアノテーションをつけたメソッドを作成し、その中でamountRepository.deleteAll(); でCrudRepositoryのdeleteAllを読ぶ  → sqlファイルで追加したデータが全て消えてしまうので、解決にならない。

sqlファイルで追加したデータが全て消えないように各テストメソッドが呼び出された際ではなく、各メソッドのテストを実行した後にデータを削除するためにbeforeEachのアノテーションをAfterEachのアノテーションに変えて実行。  → AfterEachのアノテーションがついていない時と同じエラーが発生。

Interface CrudRepository<T,ID> https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html

t-ita commented 2 years ago

@Ryoma137

このような説明でよろしいでしょうか?

「テストが必要無い」という判断は、「その状態になり得ることがない」「その操作が発生し得ることがない」ということで説明されます。 今回のケースだと、「与えられたデータとDBのデータのキーが重複している / いない状態」が(Repository内部の動作とは別に)あり得るか、というところが論点になります。 石崎さんが書いているのは、与えられたデータがDBに投入された結果、ID が付与される話をしていますが、それは Repository の内部動作であって、テスト条件の話ではないですね。 (なので、説明としてはスジが通らないです)

Ryoma137 commented 2 years ago

板垣さんとのペアプロにて、各テストメソッドで追加したDBのレコードがTRANCATEで削除されていないのではなく、レコード自体は削除されているが、IDは一意であるため削除したレコードに紐づいていたID値はDB内に残っていることが判明。 そのため、assertEqualsで指定したID値が期待通りのIDになっているかのテストを書き、各テストメソッドを一つずつ実行するとテストが通るが、全体でテストを実行すると別のテストメソッドでDBに保存した一意のID値がDB内に残っているため、与えられたデータがDB内に保存される時、IDの値が各テストメソッドを一つずつ実行する際と全体でテストを実行する際では変わってくる。

そのため、以下の4つのテストメソッドではassertEqualsで指定したID値が期待通りのIDになっているかのテストではなく、指定したID列のIDがDB内に存在する(nullである)か否かをassertNotNull、またはassertNullで確認する必要があるため、下記の4つのテストメソッドを修正 → テストが全てエラーがなく実行できた。

-- 修正したテストメソッド -- @GeneratedValueで生成したIDが付与されているデータが削除できること DBのテーブル内にデータが存在する時、DBのID列の最後尾にデータが追加されていること 与えられたデータのキーが一致するデータがDBに存在しない時、DBが更新されず新たなデータとして登録されているかの確認 IDがNullの時に@IDがNullの時に@GeneratedValueでIDが正しく生成されること

AmountRepositoryTest内に記載しているテストメソッドごとにテストを実行すると、テストが通るが、AmountRepositoryTestのテストを一斉に起動すると、各テストメソッド追加したDBの値が消えていないので、エラーになる。sqlに書いているTRUNCATE TABLE Amount;がうまく機能していない?

atw-wakasugi commented 2 years ago

今のテストくらいなら TRUNCATE じゃなくて DROP→CREATE でも良いのかもと思いました。

DROP TABLE Amount;
CREATE TABLE Amount (id bigint generated BY DEFAULT AS IDENTITY, category VARCHAR(255), comments VARCHAR(255), name VARCHAR(255), price INTEGER NOT NULL, PRIMARY KEY (id));
Ryoma137 commented 2 years ago

与えられたデータの処理は更新か追加しかない。 そのため、更新されない = 追加されるが成り立つので、与えられたデータのキーと一致するデータがDBに存在しない時、DBが更新されないことのテストに、与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていることの意味も含まれる。 なので、与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていることのテストは不要である。

同様に、更新される = 追加されないが成り立つので、与えられたデータのキーと一致するデータがDBに存在する時、与えられたデータでDBが更新されること与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないことの意味が含まれる。 そのため、与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないことのテストは不要である。

@Ryoma137

このような説明でよろしいでしょうか?

「テストが必要無い」という判断は、「その状態になり得ることがない」「その操作が発生し得ることがない」ということで説明されます。 今回のケースだと、「与えられたデータとDBのデータのキーが重複している / いない状態」が(Repository内部の動作とは別に)あり得るか、というところが論点になります。 石崎さんが書いているのは、与えられたデータがDBに投入された結果、ID が付与される話をしていますが、それは Repository の内部動作であって、テスト条件の話ではないですね。 (なので、説明としてはスジが通らないです)

  • RepositoryのJUnitテストの作成 必要なテストパターンの洗い出しをし、以下の5つのDisplayNameをつけたテストメソッドを作成。

    • DBのテーブル内にデータが存在しない時、与えられたデータがDBに追加されていること Done
    • 与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていること WIP
    • 与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないこと WIP
    • 与えられたデータのキーが一致するデータがDBに存在する時、与えられたデータでDBが更新されること
    • 与えられたデータのキーが一致するデータがDBに存在しない時、DBが更新されないこと
Ryoma137 commented 2 years ago

@atw-wakasugi IDは一意のため、TRANCATEでテーブルに格納されているデータをすべて削除しても同じIDは再利用できないので、一度DROPで一度テーブル自体を削除し、CREATEで再度テーブル自体を再作成した方が、今のテストであればテストがしやすくなるという認識でよいでしょうか?

今のテストくらいなら TRUNCATE じゃなくて DROP→CREATE でも良いのかもと思いました。

DROP TABLE Amount;
CREATE TABLE Amount (id bigint generated BY DEFAULT AS IDENTITY, category VARCHAR(255), comments VARCHAR(255), name VARCHAR(255), price INTEGER NOT NULL, PRIMARY KEY (id));
Ryoma137 commented 2 years ago

serviceのJUnitテストタスク洗い出し 着手開始 予定時間(見積り)時間 : 1.0h

atw-wakasugi commented 2 years ago

@Ryoma137

@atw-wakasugi IDは一意のため、TRANCATEでテーブルに格納されているデータをすべて削除してもIDはDB内に残るが、一度DROPで一度テーブル自体を削除し、CREATEで再度テーブル自体を再作成した方が、今のテストであればテストがしやすくなるという認識でよいでしょうか?

はい。合っています。

Ryoma137 commented 2 years ago
Ryoma137 commented 2 years ago

serviceのJUnitテストタスク洗い出し pending 予定時間(見積り)時間 : 1.0h 現時点での実働時間: 1時間5分

@t-ita 昨日板垣さんとのペアプロの途中で終わった与えられたデータのIDがnullになっていない時のテスト観点に自信がないので、お忙しいところ申し訳ないですが、お手隙の際にレビューとご指摘をお願いします。

  • [ ] service(registerAmount メソッド)のJUnitテスト作成

    • [ ] 与えられたデータのIDがnullになっている時、レコードが追加される
    • [ ] 与えられたデータのIDがnullではない時、DBのidentity列を利用したIDに変更されてレコードが追加される
Ryoma137 commented 2 years ago

以下の若杉さんのご指摘点の修正 着手開始 予定時間(見積り)時間 : 0.5h

@atw-wakasugi IDは一意のため、TRANCATEでテーブルに格納されているデータをすべて削除しても同じIDは再利用できないので、一度DROPで一度テーブル自体を削除し、CREATEで再度テーブル自体を再作成した方が、今のテストであればテストがしやすくなるという認識でよいでしょうか?

今のテストくらいなら TRUNCATE じゃなくて DROP→CREATE でも良いのかもと思いました。

DROP TABLE Amount;
CREATE TABLE Amount (id bigint generated BY DEFAULT AS IDENTITY, category VARCHAR(255), comments VARCHAR(255), name VARCHAR(255), price INTEGER NOT NULL, PRIMARY KEY (id));
Ryoma137 commented 2 years ago

若杉さんのご指摘点の修正 着手終了 push済 予定時間(見積り)時間 : 0.5h 実動時間 : 18分

Ryoma137 commented 2 years ago

service(registerAmount メソッド)のJUnitテストの与えられたデータのIDがnullになっている時、レコードが追加される 着手開始 予定時間(見積り)時間 : 2.0h

Ryoma137 commented 2 years ago

与えられたデータのIDがnullになっている時、レコードが追加される 着手終了 push済 予定時間(見積り)時間 : 2.0h 実動時間 : 1.0h

Ryoma137 commented 2 years ago

与えられたデータのIDがnullではない時、DBのidentity列を利用したIDに変更されてレコードが追加される 着手開始 予定時間(見積り)時間 : 1.0h

Ryoma137 commented 2 years ago

与えられたデータのIDがnullではない時、DBのidentity列を利用したIDに変更されてレコードが追加される 着手終了 push済 予定時間(見積り)時間 : 1.0h 実動時間 : 45分

Ryoma137 commented 2 years ago

入力欄に入力した値が登録ボタンを押すことにより、コントローラーに渡される機能をThymeleafで作成。 着手開始 予定時間(見積り)時間 : 2.0h

Ryoma137 commented 2 years ago

データの登録は可能になったが、drop downから選択したカテゴリーが反映されず、nullで登録される  → 原因調査中 (drop downで取得した値が上手くthymeleafからcontrollerに渡っていないのが原因と推測)

Ryoma137 commented 2 years ago

入力欄に入力した値が登録ボタンを押すことにより、コントローラーに渡される機能をThymeleafで作成。着手終了 push済(PRを分けてPR提出済) 予定時間(見積り)時間 : 2.0h 実動時間 : 2.5h

エラーの原因 : select tagにth:field="*{category}"がついていなかったので、カテゴリーリストから選択した値ではなくnullがDBに保存されていた。