我們為應用程式編寫測試
文章開頭:
寫JRTB-3。現在我們需要考慮測試。所有新增的程式碼都應該經過測試,以便我們可以確保功能能如我們的預期運作。首先,我們將為 SendBotMessageService 服務編寫單元測試。
單元測試是測試應用程式的一小部分邏輯的測試:通常這些是方法。所有具有此方法的連接都會使用模擬替換為假連接。 |
現在你會看到一切。在同一個套件中,僅在
./src/test/java資料夾中,我們建立一個與我們將測試的類別同名的類,並在末尾添加
Test。也就是說,對於
SendBotMessageService我們將有
SendBotMessageServiceTest,它將包含此類的所有測試。測試它的想法如下:我們放入一個模擬(假)JavaRushTelegarmBot,然後我們詢問是否使用這樣的參數呼叫了執行方法。事情是這樣的:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
@DisplayName("Unit-level testing for SendBotMessageService")
public class SendBotMessageServiceTest {
private SendBotMessageService sendBotMessageService;
private JavarushTelegramBot javarushBot;
@BeforeEach
public void init() {
javarushBot = Mockito.mock(JavarushTelegramBot.class);
sendBotMessageService = new SendBotMessageServiceImpl(javarushBot);
}
@Test
public void shouldProperlySendMessage() throws TelegramApiException {
String chatId = "test_chat_id";
String message = "test_message";
SendMessage sendMessage = new SendMessage();
sendMessage.setText(message);
sendMessage.setChatId(chatId);
sendMessage.enableHtml(true);
sendBotMessageService.sendMessage(chatId, message);
Mockito.verify(javarushBot).execute(sendMessage);
}
}
我使用 Mockito 創建了一個模擬 JavaRushBot 對象,並將其傳遞給我們服務的建構子。接下來,我編寫了一個測試(帶有 Test 註釋的每個方法都是單獨的測試)。此方法的結構始終相同 - 它不帶參數並傳回 void。測試名稱應該告訴您我們正在測試什麼。在我們的例子中,這是:應該正確發送訊息 - 必須正確發送訊息。我們的測驗分為三個部分:
- //給定區塊 - 我們準備測試所需的一切;
- block //when - 我們啟動計劃測試的方法的位置;
- //then 區塊 - 我們檢查該方法是否正確運作。
由於到目前為止我們服務中的邏輯很簡單,因此對此類進行一次測試就足夠了。現在讓我們為 CommandContainer 編寫一個測試:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Arrays;
@DisplayName("Unit-level testing for CommandContainer")
class CommandContainerTest {
private CommandContainer commandContainer;
@BeforeEach
public void init() {
SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
commandContainer = new CommandContainer(sendBotMessageService);
}
@Test
public void shouldGetAllTheExistingCommands() {
Arrays.stream(CommandName.values())
.forEach(commandName -> {
Command command = commandContainer.retrieveCommand(commandName.getCommandName());
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
});
}
@Test
public void shouldReturnUnknownCommand() {
String unknownCommand = "/fgjhdfgdfg";
Command command = commandContainer.retrieveCommand(unknownCommand);
Assertions.assertEquals(UnknownCommand.class, command.getClass());
}
}
這不是一個非常明顯的測試。它依賴容器的邏輯。機器人支援的所有命令都在 CommandName 清單中,必須位於容器中。因此,我獲取了所有 CommandName 變量,轉到 Stream API,並為每個變數從容器中搜尋了一個命令。如果沒有這樣的命令,則傳回 UnknownCommand。這是我們在這一行中檢查的內容:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
要檢查 UnknownCommand 是否為預設命令,您需要一個單獨的測試 -
shouldReturnUnknownCommand。我建議您重寫並分析這些測試。目前將為團隊提供半正式測試,但需要編寫。邏輯將與測試 SendBotMessageService 相同,因此我將把通用測試邏輯移至 AbstractCommandTest 類別中,並將繼承每個特定測試類別並定義其所需的欄位。由於所有測試都是同一類型,因此每次編寫相同的內容並不容易,而且這也不是好程式碼的標誌。這就是廣義抽象類別的結果:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageServiceImpl;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.internal.verification.VerificationModeFactory;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
abstract class AbstractCommandTest {
protected JavarushTelegramBot javarushBot = Mockito.mock(JavarushTelegramBot.class);
protected SendBotMessageService sendBotMessageService = new SendBotMessageServiceImpl(javarushBot);
abstract String getCommandName();
abstract String getCommandMessage();
abstract Command getCommand();
@Test
public void shouldProperlyExecuteCommand() throws TelegramApiException {
Long chatId = 1234567824356L;
Update update = new Update();
Message message = Mockito.mock(Message.class);
Mockito.when(message.getChatId()).thenReturn(chatId);
Mockito.when(message.getText()).thenReturn(getCommandName());
update.setMessage(message);
SendMessage sendMessage = new SendMessage();
sendMessage.setChatId(chatId.toString());
sendMessage.setText(getCommandMessage());
sendMessage.enableHtml(true);
getCommand().execute(update);
Mockito.verify(javarushBot).execute(sendMessage);
}
}
正如您所看到的,我們有三個抽象方法,定義每個命令後應該執行此處編寫的測試並正確執行。當主要邏輯位於抽象類別中但細節在後代中定義時,這是一種非常方便的方法。事實上,這裡是具體測試的實作:
幫助命令測試:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.HELP;
import static com.github.javarushcommunity.jrtb.command.HelpCommand.HELP_MESSAGE;
@DisplayName("Unit-level testing for HelpCommand")
public class HelpCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return HELP.getCommandName();
}
@Override
String getCommandMessage() {
return HELP_MESSAGE;
}
@Override
Command getCommand() {
return new HelpCommand(sendBotMessageService);
}
}
無命令測試:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.NO;
import static com.github.javarushcommunity.jrtb.command.NoCommand.NO_MESSAGE;
@DisplayName("Unit-level testing for NoCommand")
public class NoCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return NO.getCommandName();
}
@Override
String getCommandMessage() {
return NO_MESSAGE;
}
@Override
Command getCommand() {
return new NoCommand(sendBotMessageService);
}
}
開始命令測試:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.START;
import static com.github.javarushcommunity.jrtb.command.StartCommand.START_MESSAGE;
@DisplayName("Unit-level testing for StartCommand")
class StartCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return START.getCommandName();
}
@Override
String getCommandMessage() {
return START_MESSAGE;
}
@Override
Command getCommand() {
return new StartCommand(sendBotMessageService);
}
}
停止命令測試:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.STOP;
import static com.github.javarushcommunity.jrtb.command.StopCommand.STOP_MESSAGE;
@DisplayName("Unit-level testing for StopCommand")
public class StopCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return STOP.getCommandName();
}
@Override
String getCommandMessage() {
return STOP_MESSAGE;
}
@Override
Command getCommand() {
return new StopCommand(sendBotMessageService);
}
}
未知指令測試:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.UnknownCommand.UNKNOWN_MESSAGE;
@DisplayName("Unit-level testing for UnknownCommand")
public class UnknownCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return "/fdgdfgdfgdbd";
}
@Override
String getCommandMessage() {
return UNKNOWN_MESSAGE;
}
@Override
Command getCommand() {
return new UnknownCommand(sendBotMessageService);
}
}
很明顯,這款遊戲是值得的,並且感謝 AbstractCommandTest,我們最終得到了簡單易懂的測試,這些測試易於編寫且易於理解。此外,我們消除了不必要的程式碼重複(向 DRY -> Don't Repeat Yourself 原則致敬)。另外,現在我們有真實的測試來判斷應用程式的效能。為機器人本身編寫測試也很好,但一切都不會那麼容易解決,總的來說,也許遊戲得不償失,正如他們所說。因此,在這個階段我們將完成我們的任務。最後也是最喜歡的一件事 - 我們創建一個提交,寫入訊息:
JRTB-3:添加了用於處理 Telegram Bot 命令的命令模式 和往常一樣 - Github 已經知道並提供創建拉取請求:
構建已經通過,您已經可以合併...但是不!我忘記更新專案版本並寫在RELEASE_NOTES中。我們新增版本的條目 - 0.2.0-SNAPSHOT:
我們在 pom.xml 中更新此版本並建立新提交:
新提交:
JRTB-3:更新的 RELEASE_NOTES.md現在推送並等待建置完成。建置已經通過,你可以合併它:
我不會刪除分支,所以你可以隨時查看和比較發生了什麼變化。我們的任務板已更新:
結論
今天我們做了一件大事:我們引入了工作指令範本。一切都已準備就緒,現在新增團隊將是一個簡單明了的過程。我們今天也討論了測試。我們甚至在團隊的不同測試中嘗試不重複程式碼。
像往常一樣,我建議在 GitHub 上註冊並追蹤我的帳戶,以關注本系列以及我在那裡從事的其他專案。我還創建了一個電報頻道,我將複製新文章的發布。一個有趣的事情是,程式碼通常在文章發布前一周發布,每次完成新任務我都會在頻道上寫,這讓我有機會在閱讀文章之前弄清楚程式碼。很快我將持續發布該機器人,訂閱電報頻道的人將是第一個知道它的人;) 感謝大家的閱讀,待續。
GO TO FULL VERSION