1. 測(cè)試分為兩種及詳細(xì)介紹測(cè)試書籍:
? 1.1 Unit Test : 單元測(cè)試
? - test the business logic in your app : 測(cè)試應(yīng)用中的業(yè)務(wù)邏輯
? 1.2 UI? Test :? 界面測(cè)試
? - test the UI of your app : 測(cè)試應(yīng)用中的界面
? 1.3 測(cè)試書籍網(wǎng)址:
《Testing Swift》https://www.hackingwithswift.com/store/testing-swift
2. ViewModel 單元測(cè)試
? 2.1 創(chuàng)建 ViewModel,UnitTestingBootcampViewModel.swift
import Foundation
import SwiftUI
import Combine
/// 單元測(cè)試 ViewModel
class UnitTestingBootcampViewModel: ObservableObject{
@Published var isPremium: Bool
@Published var dataArray: [String] = []
@Published var selectedItem: String? = nil
let dataService: NewDataServiceProtocol
var cancellable = Set<AnyCancellable>()
init(isPremium: Bool, dataService: NewDataServiceProtocol = NewMockDataService(items: nil)) {
self.isPremium = isPremium
self.dataService = dataService
}
/// 添加子項(xiàng)
func addItem(item: String){
// 為空不往下執(zhí)行
guard !item.isEmpty else { return }
self.dataArray.append(item)
}
/// 選中項(xiàng)
func selectItem(item: String){
if let x = dataArray.first(where: {$0 == item}){
selectedItem = x
}else{
selectedItem = nil
}
}
/// 保存項(xiàng)
func saveItem(item: String) throws{
guard !item.isEmpty else{
throw DataError.noData
}
if let x = dataArray.first(where: {$0 == item}){
print("Save item here!!! \(x)")
} else {
throw DataError.itemNotFound
}
}
/// 錯(cuò)誤信息
enum DataError: LocalizedError{
case noData
case itemNotFound
}
/// 請(qǐng)求返回?cái)?shù)據(jù)
func downloadWithEscaping() {
dataService.downloadItemsWithEscaping { [weak self] returnedItems in
self?.dataArray = returnedItems
}
}
/// 下載用到的組合
func downloadWithCombine() {
dataService.downloadItemsWithCombine()
.sink { _ in
} receiveValue: { [weak self] returnedItems in
self?.dataArray = returnedItems
}
.store(in: &cancellable)
}
}
? 2.2 創(chuàng)建測(cè)試文件
? ? 當(dāng)創(chuàng)建項(xiàng)目時(shí),沒有選擇 Include Tests/包含測(cè)試 選項(xiàng)時(shí),需要添加文件去對(duì)應(yīng)項(xiàng)目,不然測(cè)試文件會(huì)報(bào) No such module 'XCTest' 編譯錯(cuò)誤
? ? 添加單元測(cè)試文件:
? ? 方法一 : 選擇項(xiàng)目 -> 菜單欄 Editor -> Add Target... -> 彈出對(duì)話框,選擇 Test 欄下 -> Unit Testing Bundle -> 填寫信息/可默認(rèn) -> Finish,完成創(chuàng)建單元測(cè)試文件。
? ? 方法二 : 選擇項(xiàng)目,點(diǎn)擊 PROJECT 列,最下的 + 按鈕,彈出對(duì)話框,選擇 Test 欄下 ,后面步驟與上一致文章來源:http://www.zghlxwxcb.cn/news/detail-722630.html
? ? 創(chuàng)建單元測(cè)試文件 UnitTestingBootcampViewModel_Tests.swift文章來源地址http://www.zghlxwxcb.cn/news/detail-722630.html
import XCTest
import Combine
/// 導(dǎo)入項(xiàng)目
@testable import SwiftfulThinkingAdvancedLearning
// 《Testing Swift》 測(cè)試書籍
// 書籍網(wǎng)址: https://www.hackingwithswift.com/store/testing-swift
// Naming Structure: test_UnitOfWork_StateUnderTest_ExpectedBehavior - 結(jié)構(gòu)體命名: 測(cè)試_工作單元_測(cè)試狀態(tài)_預(yù)期的行為
// Naming Structure: test_[struct or class]_[variable or function]_[expected result] - 測(cè)試_[結(jié)構(gòu)體 或者 類的名稱]_[類中的變量名 或者 函數(shù)名稱]_[預(yù)期結(jié)果 預(yù)期值]
// Testing Structure: Given, When, Then - 測(cè)試結(jié)構(gòu): 給定,什么時(shí)候,然后
final class UnitTestingBootcampViewModel_Tests: XCTestCase {
/// 解決多次引用相同的類
var viewModel: UnitTestingBootcampViewModel?
var cancellables = Set<AnyCancellable>()
/// 開始設(shè)置數(shù)據(jù)
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
viewModel = UnitTestingBootcampViewModel(isPremium: Bool.random())
}
/// 結(jié)束重置數(shù)據(jù)
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
viewModel = nil
cancellables.removeAll()
}
/// 單元測(cè)試函數(shù)名,根據(jù)命名規(guī)則命名:測(cè)試_類名稱_是否高質(zhì)量_應(yīng)該為真
func test_UnitTestingBootcampViewModel_isPremium_shouldBeTrue(){
// Given
let userIsPremium: Bool = true
// When
let vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)
// Then
XCTAssertTrue(vm.isPremium)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名:測(cè)試_類名稱_是否高質(zhì)量_應(yīng)該為假
func test_UnitTestingBootcampViewModel_isPremium_shouldBeFalse(){
// Given
let userIsPremium: Bool = false
// When
let vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)
// Then
XCTAssertFalse(vm.isPremium)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名:測(cè)試_類名稱_是否高品質(zhì)_注入值
func test_UnitTestingBootcampViewModel_isPremium_shouldBeInjectedValue(){
// Given
let userIsPremium: Bool = Bool.random()
// When
let vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)
// Then
XCTAssertEqual(vm.isPremium, userIsPremium)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 注入值_壓力 / for 循環(huán)
func test_UnitTestingBootcampViewModel_isPremium_shouldBeInjectedValue_stress(){
for _ in 0 ..< 10 {
// Given
let userIsPremium: Bool = Bool.random()
// When
let vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)
// Then
XCTAssertEqual(vm.isPremium, userIsPremium)
}
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 數(shù)組_預(yù)期值:為空
func test_UnitTestingBootcampViewModel_dataArray_shouldBeEmpty(){
// Given
// When
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// Then 斷言 = 判定
XCTAssertTrue(vm.dataArray.isEmpty)
XCTAssertEqual(vm.dataArray.count, 0)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 數(shù)組_預(yù)期值:添加項(xiàng)
func test_UnitTestingBootcampViewModel_dataArray_shouldAddItems(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let loopCount: Int = Int.random(in: 1..<100)
for _ in 0 ..< loopCount{
vm.addItem(item: UUID().uuidString)
}
// Then 斷言 = 判定
XCTAssertTrue(!vm.dataArray.isEmpty)
XCTAssertFalse(vm.dataArray.isEmpty)
XCTAssertEqual(vm.dataArray.count, loopCount)
XCTAssertNotEqual(vm.dataArray.count, 0)
// GreaterThan 大于
XCTAssertGreaterThan(vm.dataArray.count, 0)
// XCTAssertGreaterThanOrEqual
// XCTAssertLessThan
// XCTAssertLessThanOrEqual
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 數(shù)組_預(yù)期值:添加空白字符
func test_UnitTestingBootcampViewModel_dataArray_shouldNotAddBlankString(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
vm.addItem(item: "")
// Then 斷言 = 判定
XCTAssertTrue(vm.dataArray.isEmpty)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 數(shù)組_預(yù)期值:添加空白字符
func test_UnitTestingBootcampViewModel_dataArray_shouldNotAddBlankString2(){
// Given
guard let vm = viewModel else {
XCTFail()
return
}
// When
vm.addItem(item: "")
// Then 斷言 = 判定
XCTAssertTrue(vm.dataArray.isEmpty)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 選中項(xiàng)_預(yù)期值:開始為空
func test_UnitTestingBootcampViewModel_selectedItem_shouldStartAsNil(){
// Given
// When
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// Then 斷言 = 判定
XCTAssertTrue(vm.selectedItem == nil)
XCTAssertNil(vm.selectedItem)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 選中項(xiàng)_預(yù)期值:應(yīng)該為空 當(dāng)選擇無效項(xiàng)
func test_UnitTestingBootcampViewModel_selectedItem_shouldBeNilWhenSelectingInvalidItem(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// Select valid item : 選擇有效項(xiàng)
let newItem = UUID().uuidString
vm.addItem(item: newItem)
vm.selectItem(item: newItem)
// Select invalid item : 選擇無效項(xiàng)
// When
vm.selectItem(item: UUID().uuidString)
// Then 斷言 = 判定
XCTAssertNil(vm.selectedItem)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 選中項(xiàng)_預(yù)期值:應(yīng)該選中
func test_UnitTestingBootcampViewModel_selectedItem_shouldBeSelected(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let newItem = UUID().uuidString
vm.addItem(item: newItem)
vm.selectItem(item: newItem)
// Then 斷言 = 判定
XCTAssertNotNil(vm.selectedItem)
XCTAssertEqual(vm.selectedItem, newItem)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 選中項(xiàng)_預(yù)期值:選中_壓力測(cè)試
func test_UnitTestingBootcampViewModel_selectedItem_shouldBeSelected_stress(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let loopCount: Int = Int.random(in: 1..<100)
var itemsArray: [String] = []
for _ in 0 ..< loopCount {
let newItem = UUID().uuidString
vm.addItem(item: newItem)
itemsArray.append(newItem)
}
// 隨機(jī)取一個(gè)字符串
let randomItem = itemsArray.randomElement() ?? ""
// 檢查字符串不為空
XCTAssertFalse(randomItem.isEmpty)
vm.selectItem(item: randomItem)
// Then 斷言 = 判定
XCTAssertNotNil(vm.selectedItem)
XCTAssertEqual(vm.selectedItem, randomItem)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 保存項(xiàng)_預(yù)期值:輸出錯(cuò)誤異常_元素沒找到
func test_UnitTestingBootcampViewModel_saveItem_shouldThrowError_itemNotFound(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let loopCount: Int = Int.random(in: 1..<100)
for _ in 0 ..< loopCount {
vm.addItem(item: UUID().uuidString)
}
// Then 斷言 = 判定
XCTAssertThrowsError(try vm.saveItem(item: UUID().uuidString))
XCTAssertThrowsError(try vm.saveItem(item: UUID().uuidString), "Should throw Item Not Found error!") { error in
// 返回錯(cuò)誤
let returnedError = error as? UnitTestingBootcampViewModel.DataError
// 判斷錯(cuò)誤是否相同
XCTAssertEqual(returnedError, UnitTestingBootcampViewModel.DataError.itemNotFound)
}
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 保存項(xiàng)_預(yù)期值:輸出錯(cuò)誤異常_沒數(shù)據(jù)
func test_UnitTestingBootcampViewModel_saveItem_shouldThrowError_noData(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let loopCount: Int = Int.random(in: 1..<100)
for _ in 0 ..< loopCount {
vm.addItem(item: UUID().uuidString)
}
// Then 斷言 = 判定
do {
try vm.saveItem(item: "")
} catch let error {
// 返回錯(cuò)誤
let returnedError = error as? UnitTestingBootcampViewModel.DataError
// 判斷錯(cuò)誤是否相同
XCTAssertEqual(returnedError, UnitTestingBootcampViewModel.DataError.noData)
}
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 保存項(xiàng)_預(yù)期值:保存選項(xiàng)
func test_UnitTestingBootcampViewModel_saveItem_shouldSaveItem(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let loopCount: Int = Int.random(in: 1..<100)
var itemsArray: [String] = []
for _ in 0 ..< loopCount {
let newItem = UUID().uuidString
vm.addItem(item: newItem)
itemsArray.append(newItem)
}
// 隨機(jī)取一個(gè)字符串
let randomItem = itemsArray.randomElement() ?? ""
// 檢查字符串不為空
XCTAssertFalse(randomItem.isEmpty)
// Then 斷言 = 判定
XCTAssertNoThrow(try vm.saveItem(item: randomItem))
do {
try vm.saveItem(item: randomItem)
} catch {
XCTFail()
}
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 下載數(shù)據(jù)_預(yù)期值:返回選項(xiàng)
func test_UnitTestingBootcampViewModel_downloadWithEscaping_shouldReturnItems(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let expectation = XCTestExpectation(description: "Should return items after 3 seconds")
// dropFirst: 刪除第一個(gè)發(fā)布 數(shù)組值,因?yàn)槌跏蓟癁榭諗?shù)組,取的是第二個(gè)數(shù)組,模擬服務(wù)數(shù)據(jù)返回的數(shù)組
vm.$dataArray
.dropFirst()
.sink { returnedItems in
expectation.fulfill()
}
.store(in: &cancellables)
vm.downloadWithEscaping()
// Then 斷言 = 判定 GreaterThan:大于
// 為了安全獲取到值,設(shè)置等待 5 秒
wait(for: [expectation], timeout: 5)
XCTAssertGreaterThan(vm.dataArray.count, 0)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 下載數(shù)據(jù)組合_預(yù)期值:返回選項(xiàng)
func test_UnitTestingBootcampViewModel_downloadWithCombine_shouldReturnItems(){
// Given
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random())
// When
let expectation = XCTestExpectation(description: "Should return items after a seconds")
// dropFirst: 刪除第一個(gè)發(fā)布 數(shù)組值,因?yàn)槌跏蓟癁榭諗?shù)組,取的是第二個(gè)數(shù)組,模擬服務(wù)數(shù)據(jù)返回的數(shù)組
vm.$dataArray
.dropFirst()
.sink { returnedItems in
expectation.fulfill()
}
.store(in: &cancellables)
vm.downloadWithCombine()
// Then 斷言 = 判定 GreaterThan:大于
// 為了安全獲取到值,設(shè)置等待 5 秒
wait(for: [expectation], timeout: 5)
XCTAssertGreaterThan(vm.dataArray.count, 0)
}
/// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 下載數(shù)據(jù)組合_預(yù)期值:返回選項(xiàng)
func test_UnitTestingBootcampViewModel_downloadWithCombine_shouldReturnItems2(){
// Given
let items: [String] = [UUID().uuidString, UUID().uuidString, UUID().uuidString, UUID().uuidString]
let dataService: NewDataServiceProtocol = NewMockDataService(items: items)
let vm = UnitTestingBootcampViewModel(isPremium: Bool.random(), dataService: dataService)
// When
let expectation = XCTestExpectation(description: "Should return items after a seconds")
// dropFirst: 刪除第一個(gè)發(fā)布 數(shù)組值,因?yàn)槌跏蓟癁榭諗?shù)組,取的是第二個(gè)數(shù)組,模擬服務(wù)數(shù)據(jù)返回的數(shù)組
vm.$dataArray
.dropFirst()
.sink { returnedItems in
expectation.fulfill()
}
.store(in: &cancellables)
vm.downloadWithCombine()
// Then 斷言 = 判定 GreaterThan:大于
// 為了安全獲取到值,設(shè)置等待 5 秒
wait(for: [expectation], timeout: 5)
XCTAssertGreaterThan(vm.dataArray.count, 0)
XCTAssertEqual(vm.dataArray.count, items.count)
}
}
3. 模擬請(qǐng)求數(shù)據(jù) 單元測(cè)試
? 3.1 創(chuàng)建模擬請(qǐng)求數(shù)據(jù)類 NewMockDataService.swift
import Foundation
import SwiftUI
import Combine
/// 定義協(xié)議
protocol NewDataServiceProtocol{
func downloadItemsWithEscaping(completion: @escaping (_ items: [String]) -> ())
func downloadItemsWithCombine() -> AnyPublisher<[String], Error>
}
/// 實(shí)現(xiàn)模擬請(qǐng)求數(shù)據(jù)
class NewMockDataService: NewDataServiceProtocol {
let items: [String]
init(items: [String]?) {
self.items = items ?? [
"ONE", "TWO", "THREE"
]
}
/// 模擬網(wǎng)絡(luò)下載數(shù)據(jù) escaping: 轉(zhuǎn)義字符
func downloadItemsWithEscaping(completion: @escaping (_ items: [String]) -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completion(self.items)
}
}
/// 下載組合
func downloadItemsWithCombine() -> AnyPublisher<[String], Error> {
// 數(shù)據(jù)轉(zhuǎn)換
Just(self.items)
.tryMap({ publishedItems in
guard !publishedItems.isEmpty else {
throw URLError(.badServerResponse)
}
return publishedItems
})
.eraseToAnyPublisher()
}
}
? 3.2 創(chuàng)建單元測(cè)試類 NewMockDataService_Tests.swift
import XCTest
import Combine
/// 導(dǎo)入項(xiàng)目
@testable import SwiftfulThinkingAdvancedLearning
final class NewMockDataService_Tests: XCTestCase {
/// 隨時(shí)取消控制器
var cancellable = Set<AnyCancellable>()
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
cancellable.removeAll()
}
// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 測(cè)試_類名_初始化_預(yù)期值:正確的設(shè)置值
func test_NewMockDataService_init_doesSetValuesCorrectly() {
// 執(zhí)行
// Given: 給定
let items: [String]? = nil
let items2: [String]? = []
let items3: [String]? = [UUID().uuidString, UUID().uuidString]
// When: 時(shí)間
let dataService = NewMockDataService(items: items)
let dataService2 = NewMockDataService(items: items2)
let dataService3 = NewMockDataService(items: items3)
// Then 然后
XCTAssertFalse(dataService.items.isEmpty)
XCTAssertTrue(dataService2.items.isEmpty)
XCTAssertEqual(dataService3.items.count, items3?.count)
}
// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 測(cè)試_類名_下載轉(zhuǎn)換數(shù)據(jù)項(xiàng)_預(yù)期值:正確的設(shè)置值
func test_NewMockDataService_downloadItemsWithEscaping_doesReturnValues() {
// 執(zhí)行
// Given: 給定
let dataService = NewMockDataService(items: nil)
// When: 時(shí)間
var items: [String] = []
let expectation = XCTestExpectation()
dataService.downloadItemsWithEscaping { returnedItems in
items = returnedItems
expectation.fulfill()
}
// Then 然后
// 等待 5 秒
wait(for: [expectation], timeout: 5)
// 斷言兩個(gè)數(shù)組大小一樣
XCTAssertEqual(items.count, dataService.items.count)
}
// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 測(cè)試_類名_下載數(shù)據(jù)項(xiàng)組合_預(yù)期值:正確的設(shè)置值
func test_NewMockDataService_downloadItemsWithCombine_doesReturnValues() {
// 執(zhí)行
// Given: 給定
let dataService = NewMockDataService(items: nil)
// When: 時(shí)間
var items: [String] = []
let expectation = XCTestExpectation()
// 下載組合控制
dataService.downloadItemsWithCombine()
.sink { completion in
switch completion{
case .finished:
expectation.fulfill()
case .failure:
XCTFail()
}
} receiveValue: {returnedItems in
// fulfill: 完成
items = returnedItems
}
.store(in: &cancellable)
// Then 然后
// 等待 5 秒
wait(for: [expectation], timeout: 5)
// 斷言兩個(gè)數(shù)組大小一樣
XCTAssertEqual(items.count, dataService.items.count)
}
// 單元測(cè)試函數(shù)名 根據(jù)命名規(guī)則命名 - 測(cè)試_類名_下載數(shù)據(jù)項(xiàng)組合_預(yù)期值:確實(shí)失敗
func test_NewMockDataService_downloadItemsWithCombine_doesFail() {
// 執(zhí)行
// Given: 給定
let dataService = NewMockDataService(items: [])
// When: 時(shí)間
var items: [String] = []
let expectation = XCTestExpectation(description: "Does throw an error")
let expectation2 = XCTestExpectation(description: "Does throw URLError.badServerResponse")
// 下載組合控制
dataService.downloadItemsWithCombine()
.sink { completion in
switch completion{
case .finished:
XCTFail()
case .failure(let error):
expectation.fulfill()
//let urlError = error as? URLError
// 斷言,判定
//XCTAssertEqual(urlError, URLError(.badServerResponse))
// 錯(cuò)誤判斷
if error as? URLError == URLError(.badServerResponse) {
expectation2.fulfill()
}
}
} receiveValue: {returnedItems in
// fulfill: 完成
items = returnedItems
}
.store(in: &cancellable)
// Then 然后
// 等待 5 秒
wait(for: [expectation, expectation2], timeout: 5)
// 斷言兩個(gè)數(shù)組大小一樣
XCTAssertEqual(items.count, dataService.items.count)
}
}
4. 創(chuàng)建單元測(cè)試 View,調(diào)用測(cè)試的 ViewModel UnitTestingBootcampView.swift
import SwiftUI
/*
1. Unit Test : 單元測(cè)試
- test the business logic in your app : 測(cè)試應(yīng)用中的業(yè)務(wù)邏輯
2. UI Test : 界面測(cè)試
- test the UI of your app : 測(cè)試應(yīng)用中的界面
*/
/// 單元測(cè)試
struct UnitTestingBootcampView: View {
@StateObject private var vm: UnitTestingBootcampViewModel
init(isPremium: Bool){
_vm = StateObject(wrappedValue: UnitTestingBootcampViewModel(isPremium: isPremium))
}
var body: some View {
Text(vm.isPremium.description)
}
}
struct UnitTestingBootcampView_Previews: PreviewProvider {
static var previews: some View {
UnitTestingBootcampView(isPremium: true)
}
}
到了這里,關(guān)于UnitTesting 單元測(cè)試的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!