Open Ryoma137 opened 2 years ago
現在の総資産をDBに登録するために、DBに現在の総資産を表すcolumnの追加が必要
登録ボタンを押すことにより、入力欄に入力した各項目がDBに保存される 着手開始
入力欄の各値に入力した値を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
入力欄のフォームに '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 + フィールド名]がなるように手動で作成。
→ 同じエラーが発生、エラーの解決にはならず。
Exception evaluating SpringEL expression: "name"
のエラーが発生。EL1007E: Property or field 'name' cannot be found on null
と出ており、nameのフィールドが存在しないので、サーバーサイドのロジック的には問題がない気もするが、サーバーサイドに何かしらの間違いがあるのではと推測。
サーバーサイドの修正を行う
→ 迷走。Exception evaluating SpringEL expression: "name"
が発生。
→ 何が原因でエラーが起きているのかわからず、サーバーサイドは引き続き迷走状態。Exception evaluating SpringEL expression: "name"
のエラーはフロント側で起きているエラーと判明。課題: 最初の時点で問題が起きているのがフロント側なのか、サーバー側なのかの問題解決の当てができていない。
@Ryoma137
認識が違うので指摘しておきます。
板垣さんに相談すると、サーバーサイドは最初に書いたUdemyで参考にしたものであっている。 → サーバーサイドに間違いはないので、Exception evaluating SpringEL expression: "name" のエラーはフロント側で起きているエラーと判明。
フロント側で起きているエラーではありません。サーバーサイドのエラーです。 Udemy の例は間違いではないが、石崎さんの書いたコードが Udemy の記述方法とは異なっていた事が原因でした。
具体的には、PostMapping された メソッドの引数には、任意の DTO を指定すべきところを、Model 型にしていたことがエラーの原因です。(Model 型には name
というフィールドはないですからね)
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:
のエラーが発生。
(割り込みタスク)DBに保存されている支出額の合計金額を支出画面に表示 着手開始
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'が発生。→ 朝会後にエラーログ等の詳細確認
@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
RepositoryのJUnitテストの作成 必要なテストパターンの洗い出しをし、以下の5つのDisplayNameをつけたテストメソッドを作成。
Done
WIP
WIP
[ ] IDがNullの時に@GeneratedValueでIDが正しく生成されることを確認する
long
型からobject型のLong
型にnullを許容するために変更test-schema-not-data-exist.sql
を呼び出すための@Sqlを作成したメソッドに付与するsave
で作成した2つのデータをDBに保存[x] @GeneratedValueの動きについて調べる
@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点のテストパターンは必要ないのではと推測
与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていること
与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないこと
[ ] DBのテーブル内にデータが存在する時、DBのID列の最後尾にデータが追加されているかを確認する
DBのテーブル内にデータが存在する時、DBのID列の最後尾にデータが追加されていること
のテストメソッドを作成test-schema.sql
を呼び出すための@Sqlを作成したメソッドに付与するorg.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.AMOUNT(ID) ( /* key:1 */ CAST(1 AS BIGINT), 'Food', 'What a scrumptious sandwich', 'Sandwich', 450)"; SQL statement: insert into amount (id, category, comments, name, price) values (default, ?, ?, ?, ?) [23505-212]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
、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: insert into amount (id, category, comments, name, price) values (default, ?, ?, ?, ?)
が発生、23505-212
のエラーコードで23505
のエラーコードとは少し違うが、恐らく23505-212
のエラーと同じくprimary keyが重複しているためにエラーが起きていると推測。
@GeneratedValue(strategy = GenerationType.IDENTITY)
により、データをDBに追加時にIDが自動で生成されるので、test-schema.sql
のINSERT
文からIDの項目を削除。→ IDの重複によるエラー(23505
)の解消org.h2.api Class ErrorCode DUPLICATE_KEY_1 https://www.h2database.com/javadoc/org/h2/api/ErrorCode.html
@Ryoma137
GenerationType.IDENTITYでデータベースのidentity列を利用してprimary key値を作成されているため、IDが重複することがな> いので、以下の2点のテストパターンは必要ないのではと推測
与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複していない時、与えられたデータがDBに追加されていること
与えられたデータのキーがDBのテーブル内に存在するデータのキーと重複している時、与えられたデータがDBに追加されないこと
上記、推測の根拠を「理論立てて説明してもらえれば」、削除しても大丈夫です。
@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に追加されないこと
上記、推測の根拠を「理論立てて説明してもらえれば」、削除しても大丈夫です。
RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在する時、与えられたデータでDBが更新されること) 着手開始 予定時間(見積り)時間 : 1.0h
RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在する時、与えられたデータでDBが更新されること) 着手終了(Ready for review) 予定時間(見積り)時間 : 1.0h 実働時間 17分
RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在しない時、DBが更新されないこと) 着手開始 予定時間(見積り)時間 : 0.5h
RepositoryのJUnitテストの作成 (与えられたデータのキーが一致するデータがDBに存在しない時、DBが更新されないこと) 着手終了 (Ready for review) 予定時間(見積り)時間 : 0.5h 実働時間: 18分
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
@Ryoma137
このような説明でよろしいでしょうか?
「テストが必要無い」という判断は、「その状態になり得ることがない」「その操作が発生し得ることがない」ということで説明されます。 今回のケースだと、「与えられたデータとDBのデータのキーが重複している / いない状態」が(Repository内部の動作とは別に)あり得るか、というところが論点になります。 石崎さんが書いているのは、与えられたデータがDBに投入された結果、ID が付与される話をしていますが、それは Repository の内部動作であって、テスト条件の話ではないですね。 (なので、説明としてはスジが通らないです)
板垣さんとのペアプロにて、各テストメソッドで追加した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;
がうまく機能していない?
今のテストくらいなら 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));
与えられたデータの処理は更新か追加しかない。
そのため、更新されない = 追加される
が成り立つので、与えられたデータのキーと一致するデータが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が更新されないこと
@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));
serviceのJUnitテストタスク洗い出し 着手開始 予定時間(見積り)時間 : 1.0h
@Ryoma137
@atw-wakasugi IDは一意のため、
TRANCATE
でテーブルに格納されているデータをすべて削除してもIDはDB内に残るが、一度DROP
で一度テーブル自体を削除し、CREATE
で再度テーブル自体を再作成した方が、今のテストであればテストがしやすくなるという認識でよいでしょうか?
はい。合っています。
[ ] service(registerAmount メソッド)のJUnitテスト作成
作業が終わりpush済の状態でチェックをつける
serviceのJUnitテストタスク洗い出し pending 予定時間(見積り)時間 : 1.0h 現時点での実働時間: 1時間5分
@t-ita 昨日板垣さんとのペアプロの途中で終わった与えられたデータのIDがnullになっていない時
のテスト観点に自信がないので、お忙しいところ申し訳ないですが、お手隙の際にレビューとご指摘をお願いします。
[ ] service(registerAmount メソッド)のJUnitテスト作成
- [ ] 与えられたデータのIDがnullになっている時、レコードが追加される
- [ ] 与えられたデータのIDがnullではない時、DBのidentity列を利用したIDに変更されてレコードが追加される
以下の若杉さんのご指摘点の修正 着手開始 予定時間(見積り)時間 : 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));
若杉さんのご指摘点の修正 着手終了 push済 予定時間(見積り)時間 : 0.5h 実動時間 : 18分
service(registerAmount メソッド)のJUnitテストの与えられたデータのIDがnullになっている時、レコードが追加される 着手開始 予定時間(見積り)時間 : 2.0h
与えられたデータのIDがnullになっている時、レコードが追加される 着手終了 push済 予定時間(見積り)時間 : 2.0h 実動時間 : 1.0h
与えられたデータのIDがnullではない時、DBのidentity列を利用したIDに変更されてレコードが追加される 着手開始 予定時間(見積り)時間 : 1.0h
与えられたデータのIDがnullではない時、DBのidentity列を利用したIDに変更されてレコードが追加される 着手終了 push済 予定時間(見積り)時間 : 1.0h 実動時間 : 45分
入力欄に入力した値が登録ボタンを押すことにより、コントローラーに渡される機能をThymeleafで作成。 着手開始 予定時間(見積り)時間 : 2.0h
データの登録は可能になったが、drop downから選択したカテゴリーが反映されず、nullで登録される → 原因調査中 (drop downで取得した値が上手くthymeleafからcontrollerに渡っていないのが原因と推測)
入力欄に入力した値が登録ボタンを押すことにより、コントローラーに渡される機能をThymeleafで作成。着手終了 push済(PRを分けてPR提出済) 予定時間(見積り)時間 : 2.0h 実動時間 : 2.5h
エラーの原因 : select tagにth:field="*{category}"
がついていなかったので、カテゴリーリストから選択した値ではなくnullがDBに保存されていた。
[ ] 登録ボタンを押すことにより、入力欄に入力した各項目がDBに保存される。
[ ] 登録ボタンを押すことにより、入力欄に入力した各項目がDBに保存される テストを行う