単体テスト
単体テストとは何ですか?
単体テストは通常、関数またはメソッドのテストを指します。単体テストの目的は、各ユニットが期待どおりに動作することを確認し、コードを変更するときに潜在的な問題を迅速に検出できるようにすることです。テスト ケースを作成することで、これらのモジュールが特定の入力に対して正しい出力を生成することを検証できます。単体テストの目的は、さまざまな状況下で各モジュールが正しく動作することを確認することです。
単体テストを作成する利点
次のようなメリットがもたらされます。
- コードの品質の向上:単体テストにより、境界条件や異常な条件など、コード内の潜在的な問題を事前に発見できるため、エラーの可能性が低くなります。
- コードの保守性の向上:単体テストは、開発者がコードの機能と実装の詳細を理解するのに役立ち、コードの保守と変更が容易になります。
- コードの信頼性の向上:コードが変更された後、単体テストは開発者がコードの正しさを検証するのに役立ち、それによってコードの信頼性が向上します。
単体テストの作成は、コードの品質、保守性、信頼性を向上させるだけでなく、開発効率を向上させ、継続的統合と継続的デリバリーをサポートする優れたソフトウェア開発手法です。
単体テストの開始
単体テストを始めるには、シンプルでわかりやすいため、通常は静的テスト (Static Test) から始めます。静的テスト (Static Test) とは、テスト ケースを作成するときに、すべてのテスト メソッドとテスト データを定義することを意味します。前進。これらのテスト方法とデータはコンパイル時に決定され、実行時に変更されません。Junit の静的テストには通常、@Test、@Before、@After などの通常のアノテーションが付いています。一連の単純な静的テストの例から始めましょう。
まず、 pom.xml
ファイルに JUnit の依存関係が含まれていることを確認してください。
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
</dependencies>
次に、単純な計算機クラスを作成します。通常は、実際にテストするビジネス クラスに置き換えられます。
public class SimpleCalculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
次に、 /test
対応するテスト クラスを同じディレクトリに作成します。
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SimpleCalculatorTest {
// 在所有测试方法执行前,仅执行一次。这个方法需要是静态的。
@BeforeAll
static void setup() {
System.out.println("BeforeAll - 初始化共享资源,例如数据库连接");
}
// 在所有测试方法执行后,仅执行一次。这个方法需要是静态的。
@AfterAll
static void tearDown() {
System.out.println("AfterAll - 清理共享资源,例如关闭数据库连接");
}
// 在每个测试方法执行前,都会执行一次。用于设置测试方法所需的初始状态。
@BeforeEach
void init() {
System.out.println("BeforeEach - 初始化测试实例所需的数据");
}
// 在每个测试方法执行后,都会执行一次。用于清理测试方法使用的资源。
@AfterEach
void cleanup() {
System.out.println("AfterEach - 清理测试实例所用到的资源");
}
// 标注一个测试方法,用于测试某个功能。
@Test
void testAddition() {
System.out.println("Test - 测试加法功能");
SimpleCalculator calculator = new SimpleCalculator();
assertEquals(5, calculator.add(2, 3), "2 + 3 应该等于 5");
}
// 再添加一个测试方法
@Test
void testSubtraction() {
System.out.println("Test - 测试减法功能");
SimpleCalculator calculator = new SimpleCalculator();
assertEquals(1, calculator.subtract(3, 2), "3 - 2 应该等于 1");
}
}
上記のプログラムでは、Junit の一般的なアノテーションを使用する手順を確認できます。
- @BeforeAll: すべてのテスト メソッドが実行される前に 1 回だけ実行されます。このメソッドは静的である必要があります
- @AfterAll: すべてのテスト メソッドが実行された後に 1 回だけ実行されます。このメソッドは静的である必要があります
- @BeforeEach: 各テスト メソッドが実行される前に 1 回実行されます。テストメソッドに必要な初期状態を設定するために使用されます。
- @AfterEach: 各テスト メソッドが実行された後、1 回実行されます。テストメソッドで使用されるリソースをクリーンアップするために使用されます
- @Test: 関数をテストするためのテスト メソッドに注釈を付ける
Maven プロジェクトの場合は、次のコマンドを実行してディレクトリでテストを実行できます。
mvn test
出力結果:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running SimpleCalculatorTest
BeforeAll - 初始化共享资源,例如数据库连接
BeforeEach - 初始化测试实例所需的数据
Test - 测试加法功能
AfterEach - 清理测试实例所用到的资源
BeforeEach - 初始化测试实例所需的数据
Test - 测试减法功能
AfterEach - 清理测试实例所用到的资源
AfterAll - 清理共享资源,例如关闭数据库连接
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.058 s - in SimpleCalculatorTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
上記は静的テストの簡単な例です。
動的テスト
動的テスト (動的テスト): 動的テストとは、テスト ケースを作成するときに、実行時にテスト メソッドとテスト データを生成できることを意味します。これらのテスト メソッドとデータはコンパイル時には決定されませんが、特定の条件やデータ ソースに基づいて実行時に動的に生成されます。静的単体テストでは、テスト サンプル データが限られているため、すべての状況をカバーすることは通常困難であり、臨界値に達した場合にカバー率を向上させるのは困難であるためです。JUnit 5で導入された動的テスト は、静的テストよりも複雑ですが、もちろん柔軟性が高く、複雑なシナリオにより適しています。次に、簡単な例を使用して、動的テストと静的テストの違いを示します。 次のように、文字列を反転するMyStringUtil
メソッドを持つクラスを 作成します 。reverse()
public class MyStringUtil {
public String reverse(String input) {
if (input == null) {
return null;
}
return new StringBuilder(input).reverse().toString();
}
}
静的テスト クラスでは、 @Test
3 つのメソッドの定義を使用して、 reverse()
考えられる複数の状況をカバーしようとします。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyStringUtilStaticTest {
private MyStringUtil stringUtil = new MyStringUtil();
@Test
void reverseString() {
// 反转字符串 'hello'
assertEquals("olleh", stringUtil.reverse("hello"));
}
@Test
void reverseEmptyString() {
// 反转空字符串
assertEquals("", stringUtil.reverse(""));
}
@Test
void handleNullString() {
// 处理 null 字符串
assertEquals(null, stringUtil.reverse(null));
}
}
次に、同じテスト ケースを動的テストで実装します。
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
public class MyStringUtilDynamicTest {
private MyStringUtil stringUtil = new MyStringUtil();
// 使用 @TestFactory 注解定义了一个动态测试工厂方法 reverseStringDynamicTests()
// 工厂方法返回一个 Collection<DynamicTest>
@TestFactory
Collection<DynamicTest> reverseStringDynamicTests() {
// 包含了 3 个动态测试用例,每个测试用例使用 dynamicTest() 方法创建
return Arrays.asList(
dynamicTest("动态测试:反转字符串 'hello'", () -> assertEquals("olleh", stringUtil.reverse("hello"))),
dynamicTest("动态测试:反转空字符串", () -> assertEquals("", stringUtil.reverse(""))),
dynamicTest("动态测试:处理 null 字符串", () -> assertEquals(null, stringUtil.reverse(null)))
);
}
}
動的テスト クラスのロジックは次のとおりです。
@TestFactory
動的テスト ファクトリ メソッドは、 アノテーションを使用して 定義されますreverseStringDynamicTests()
。- ファクトリ メソッドは
Collection<DynamicTest>
、3 つの動的テスト ケースを含む を返します。 - 各テスト ケースは
dynamicTest()
メソッドを使用して作成されます。
以上が基本的な単体テストの使用方法であり、Junit 5 の具体的な使用方法についてはここでは詳しく説明しませんので、興味のある方は Junit 5 の公式ドキュメントを参照してください。
単体テスト + DBC
単体テストの記述は、可能な限り 契約による設計 (Design By Contract、DbC)コード スタイルに従う必要があります 。契約による設計については、次の説明を参照してください。
Design By Contract (DbC) は、ソフトウェア開発における各モジュールまたは機能について、その入力と出力の合意 (契約) を明確に定義する必要があることを強調したソフトウェア開発手法です。これらの契約には、前提条件と事後条件、および発生する可能性のある例外を含めることができます。コードが実装されるときは、これらの規約が満たされる必要があり、満たされない場合はエラーまたは例外がスローされます。
これを言うのは抽象的かもしれませんが、次のサンプル コードを通じて、アサーションを使用してコントラクト設計を実装する方法を理解できます。
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void withdraw(double amount) {
assert amount > 0 : "Amount must be positive";
assert amount <= balance : "Insufficient balance";
balance -= amount;
assert balance >= 0 : "Balance can't be negative";
}
public double getBalance() {
return balance;
}
}
この例では、Java のアサーションを使用して Design by Contract を実装します。具体的には:
assert amount > 0 : "Amount must be positive";
引き出し金額がamount
0より大きくなければならないことを示しますassert amount <= balance : "Insufficient balance";
引き出し金額がamount
アカウント残高以下である必要がある ことを示しますbalance
assert balance >= 0 : "Balance can't be negative";
出金完了後のbalance
口座残高の値がマイナスでないことを示します。
アサーション機能はJVM パラメータを使用して有効にできます -ea
が、Java ネイティブ アサーションを有効にするのは面倒なので、Guava チームはアサーションを置き換えるために常に有効になる Verify クラスを追加しました。Verify メソッドを静的にインポートすることを推奨しています。使い方はアサーションと似ているので、ここでは詳しく説明しません。
テスト駆動開発 TDD
コードを書く前に単体テストを書くテスト駆動開発(TDD)はソフトウェア開発手法の1つで、個人的にとてもおすすめしているソフトウェア開発手法でもあります。TDD の中心的な考え方は、コードを書く前にテスト ケースを書くことです。開発者は、テスト ケースを作成できるように、コードを作成する前に期待される結果について考えます。次に、開発者はテスト ケースに合格するのに十分な単純なコードを作成し、コードをリファクタリングして品質と保守性を向上させます。
TDD の長期実践者として、私は TDD がもたらす利点を次のように要約します。
- 保守性の向上: 通常、コードの一部を保守しない理由は、テストがないためです。TDD によって確立された完璧なテストは、コードのリファクタリングを保証します。
- 開発の高速化: 多くの開発者は常に関数を実装してからテストを補足することを考えていますが、通常は関数が実装された後にさらに多くの関数が存在するため、関数が開始される前にテストを作成するようにしてください。
- より高品質な配信: ここで言うまでもなく、テストに合格するコードは、テストされていないコードとはまったく異なります。テストされていないコードは本番環境にまったく対応していません
ログ
適切なログは、開発者がプログラムの動作をより深く理解するのに役立ちます。ログを見ることで、プログラム内で何が起こったのか、どこで問題が発生したのかを把握できます。これにより、開発者は問題をより迅速に発見して修正できるようになり、プログラムの安定性と信頼性が向上します。さらに、ログを使用してプログラムのパフォーマンスと動作を追跡し、最適化と改善を行うことができます。
ログ出力
簡単なログを出力する例を次に示します。
1. まず、SLF4J の依存関係をプロジェクトに追加する必要があります。Maven または Gradle に次の依存関係を追加できます。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
2. 次に、Logback や Log4j2 などの SLF4J の実装を選択し、プロジェクトに追加する必要があります。Maven または Gradle に次の依存関係を追加できます。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
3. コード内で次のコードを使用して、Hello World を出力できます。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
private static final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
public static void main(String[] args) {
logger.info("Hello World");
}
}
これにより、SLF4J を使用して文字列「Hello World」を含むメッセージが出力されます。この情報は、コンソールまたはログ ファイルで確認できます。
ログレベル
主に、開発者がログ出力をより適切に制御および管理できるようにするためです。SLF4J では、いくつかのログ レベルが定義されています。
ログレベル | コンテンツ |
---|---|
痕跡 | 通常はデバッグのために、プログラムの詳細をトレースするために使用されます。 |
デバッグ | プログラムをデバッグするために使用され、変数の値やメソッドの呼び出しなど、プログラム内の詳細情報を出力します。 |
情報 | プログラムの起動、終了、データベースへの接続など、プログラムの実行ステータス情報を出力するために使用されます。 |
警告 | プログラムに潜在的な問題がある可能性があることを示す警告情報を出力するために使用されますが、プログラムの通常の動作には影響しません。 |
エラー | プログラム内で致命的なエラーを含むエラーが発生したことを示すエラー情報を出力するために使用されます。 |
異なる情報を記録するには、異なるログ レベルが使用されます。この目的は、不要なログ出力とファイル サイズを削減するだけでなく、迅速な検索機能も提供することです。たとえば、開発環境では通常、TRACE ログと DEBUG ログが使用され、運用環境では通常、INFO ログと WARN ログが使用されます。これらの情報は logback.xml
ログ構成ファイルで構成できます。
ログ構成
以下は、ログをコンソールとファイルに出力する基本的なログバック構成ファイルの例です。
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/myapp.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/myapp.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
この構成ファイルでは、2 つのアペンダーが定義されています。
- 1 つはコンソールへのログ記録用 (CONSOLE)
- 1 つはファイルへの出力のログ記録用 (FILE)
コンソールのログ形式はパターン形式を使用し、ファイルのログは RollingFileAppender を使用して日次ローテーションを実現し、最大 7 日間保存できるログ履歴を定義します。同時に、ルート レベルが INFO であるロガーが定義され、CONSOLE と FILE の 2 つのアペンダーにログが出力され、他のログ レベル (TRACE、DEBUG、WARN、ERROR) はルート ロガーに出力されます。デフォルト設定。
コードの静的チェック
Java 静的スキャン ツールは、開発者が開発プロセス中にコードの問題やエラーをタイムリーに発見して修正するのに役立ち、それによってコードの品質とセキュリティが向上します。これらの静的スキャン ツールはコード スタイルを制約することもできます。チーム支援開発では、統一されたスタイルによりチームのコラボレーションとコミュニケーションが強化され、コードの可読性と保守性が向上し、不必要な議論や論争が減少するため、その後のコードレビューの進行に有益です。一般的に使用される Java 静的スキャン ツールをいくつか示します。
Github アドレスにアクセスすると、開発者がこれらのツールをより深く理解し、使用できるようにするための詳細情報とサポートも提供されます。さらに、コードの検査と修復を自動化するために、開発プロセス中にこれらのツールを継続的統合および継続的配信プロセスに統合することをお勧めします。
コードレビュー
通常、手動によるコードレビューは開発プロセスの最後のステップですが、これまでに非常に多くのテストと検査ツールが実行されてきたのに、最後に手動による検査が必要になるのはなぜでしょうか?
静的スキャン ツールは通常、手動検査と比較していくつかの単純な問題やエラーのみをチェックできるため、次の制限があります。
- 構文エラー、セキュリティ ホールによくあるエラーなどのみをチェックできます。
- 問題やエラーを確認することしかできず、より良い提案や解決策を提供することはできません。(提供される一般的なソリューションが最善ではない可能性があります)
- 静的スキャン ツールは、コードが特定の仕様や標準に準拠しているかどうかをチェックすることしかできませんが、コードの品質と読みやすさを保証することはできません。
機械によるスキャンと比較して、手動によるコード レビューには次のようなかけがえのない利点があります。
- ビジネス ロジックの問題、不合理な設計、不必要な複雑さなど、より複雑な問題が見つかる可能性があります。
- 機械による提案と比較して、手動のコードレビューは経験と知識に基づいたより良い解決策と提案を提供できます。
- チームのコラボレーションと学習を促進し、コードを共有して議論することで、開発者のスキルと知識を向上させ、チームの結束力と効率を向上させることができます。
要約すると、静的スキャン ツールは開発者がコード内の問題やエラーを自動的に発見するのに役立ちますが、コード レビューは依然として必要なソフトウェア開発実践であり、コードの品質、可読性、保守性を向上させることができ、またチームワークと学習も促進します。したがって、開発プロセス中に手動のコード レビューと静的スキャン ツールを組み合わせて、より包括的かつ詳細なコードのレビューとレビューを行うことをお勧めします。
関連分野の拡大:(技術フロンティア)
大声で叫ぶ!現在のローコードについてはテクニカル分野で大活躍!
ローコードとは何ですか? 一連のデジタルテクノロジーツールプラットフォームは、グラフィカルなドラッグアンドドロップやパラメータ化された構成などのより効率的な方法に基づいて、迅速な構築、データ配置、接続エコロジー、ミドルエンドサービスなどを実現できます。コードをほとんどまたはまったく使わずに、デジタル変革におけるシナリオ アプリケーションの革新を実現します。これは、巨大な市場需要と従来の開発生産性によって引き起こされる需要と供給の矛盾を緩和または解決することができ、デジタル変革のプロセスにおけるコスト削減と効率向上のトレンドの産物です。
ここでは、使いやすいローコード プラットフォーム、JNPF 高速開発プラットフォームを紹介します。近年、市場パフォーマンスと製品競争力の点で優れており、最新の主流である前後分離フレームワーク (SpringBoot+Mybatis-plus+Ant-Design+Vue 3 )を採用しています。コードジェネレータは依存性が低く、柔軟な拡張性を備えており、二次開発も柔軟に実現できます。
JNPFに代表されるエンタープライズレベルのローコードプラットフォームは、より技術要件の高いアプリケーション開発を支援するため、データベースモデリング、Web API構築、ページデザインに至るまで従来のソフトウェア開発とほとんど変わりません。 「削除、変更、クエリ」機能を使用すると、ローコードについてまだ学習していないパートナーでも理解することができます。
アプリケーション: https://www.jnpfsoft.com/?csdn
これにより、開発者は開発プロセスを簡単に開始でき、従来の開発モードで蓄積された経験を最大限に活用できます。したがって、ローコード プラットフォームはプログラマーにとって非常に役立ちます。
要約する
最新のソフトウェア開発では、単体テスト、TDD、ロギング、静的チェック スキャン、手動コード レビューはすべて、開発者がソフトウェアの品質を確保し、コードの可読性と保守性を向上させ、チームのコラボレーションと学習を促進するのに役立つ必要な実践です。
まず、単体テストとは、関数やメソッドなどのコードの基本単位をテストするために使用されるテスト方法です。単体テストは、開発者がコードの問題やバグを早期に発見して解決するのに役立ち、それによってコードの品質と信頼性が向上します。同時に、単体テストによってコードの可読性と保守性も向上し、コードの理解と変更が容易になります。
2つ目のTDD(テスト駆動開発、テスト駆動開発)とは、コードを書く前にテストケースを書く必要がある開発手法です。TDD を使用することで、開発者はコードの要件と仕様をより深く理解し、コード内のエラーや問題を回避し、コードの可読性と保守性を向上させることができます。
3 番目に、ログは、プログラムの実行時のステータスと情報を記録する方法です。ログは、開発者がプログラムをデバッグし、潜在的なエラーや問題を発見し、より適切なエラー処理と処理スキームを提供するのに役立ちます。同時に、ログにはプログラム実行時のパフォーマンスとステータスも記録できるため、開発者がプログラムのパフォーマンスを分析して最適化するのに役立ちます。
4 番目に、静的検査スキャン ツールは、開発者がコードの問題やエラーを早期に発見して解決できるようにする、自動化されたコード レビューおよびレビュー ツールです。静的検査スキャン ツールを使用すると、開発者はコードの問題やエラーをより徹底的にチェックし、コードの品質と読みやすさを向上させることができます。
最後に、人間によるコードレビューは、手動でコードをレビューしてレビューする方法であり、コードの問題やエラーをより深く調査し、より良い解決策や提案を提供できます。手動コード レビューにより、チームのコラボレーションと学習が促進され、コードの品質と読みやすさが向上し、特定のコーディング規範や標準に準拠することもできます。
要約すると、単体テスト、TDD、ロギング、静的検査スキャン、手動コード レビューはすべて、コードの品質、可読性、保守性を向上させ、チームのコラボレーションと学習を促進するために必要なソフトウェア開発実践です。ソフトウェア開発を行うときは、可能な限りこれらの慣行に従い、コードのレビューとテストに適切なツールとテクニックを使用する必要があります。