gpt4 book ai didi

ios - 下面的代码应该怎么写测试用例?

转载 作者:行者123 更新时间:2023-12-04 14:52:39 25 4
gpt4 key购买 nike

如果这段代码不适合编写测试代码,应该如何修改编写测试用例的代码?

class MyFileManager {
static let shared = MyFileManager()

func isStored(atPath path: String) -> Bool {
return FileManager.default.fileExists(atPath: path)
}

func readData(atPath path: String) -> Data? {
return try? Data(contentsOf: URL(fileURLWithPath: path))
}
}

class SomeViewModel {
func getCachedData() -> Data? {
let path = "xxxxx"

if MyFileManager.shared.isStored(atPath: path) {
return MyFileManager.shared.readData(atPath: path)
} else {
return nil
}
}
}

class TestSomeViewModel: XCTestCase {
func testGetCachedData() {
let viewModel = SomeViewModel()
// Need to cover SomeViewModel.getCachedData() method
}
}

最佳答案

考虑将类的方法提取到单独的 protocol 中,这样我们就可以使实际类和模拟类都符合该协议(protocol),并且我们可以在单元测试中测试预期的功能,而不是在实际实现中执行代码。

/*
Extract the 2 methods of MyFileManager into a separate protocol.
Now we can create a mock class which also conforms to this same protocol,
which will help us in writing unit tests.
*/
protocol FileManagerProtocol {
func isStored(atPath path: String) -> Bool
func readData(atPath path: String) -> Data?
}

class MyFileManager: FileManagerProtocol {
static let shared = MyFileManager()

// To make a singleton instance, we have to make its initializer private.
private init() {
}

func isStored(atPath path: String) -> Bool {
//ideally, even FileManager.default instance should be "injected" into this class via dependency injection.
return FileManager.default.fileExists(atPath: path)
}

func readData(atPath path: String) -> Data? {
return try? Data(contentsOf: URL(fileURLWithPath: path))
}
}

SomeViewModel 类也可以通过依赖注入(inject)获取它的依赖。

class SomeViewModel {
var fileManager: FileManagerProtocol?

// We can now inject a "mocked" version of MyFileManager for unit tests.
// This "mocked" version will confirm to FileManagerProtocol which we created earlier.
init(fileManager: FileManagerProtocol = MyFileManager.shared) {
self.fileManager = fileManager
}

/*
I've made a small change to the below method.
I've added the path as an argument to this method below,
just to demonstrate the kind of unit tests we can write.
*/
func getCachedData(path: String = "xxxxx") -> Data? {
if let doesFileExist = self.fileManager?.isStored(atPath: path),
doesFileExist {
return self.fileManager?.readData(atPath: path)
}
return nil
}
}

上述实现的单元测试看起来类似于下面所写的内容。

class TestSomeViewModel: XCTestCase {
var mockFileManager: MockFileManager!

override func setUp() {
mockFileManager = MockFileManager()
}

override func tearDown() {
mockFileManager = nil
}

func testGetCachedData_WhenPathIsXXXXX() {
let viewModel = SomeViewModel(fileManager: self.mockFileManager)
XCTAssertNotNil(viewModel.getCachedData(), "When the path is xxxxx, the getCachedData() method should not return nil.")
XCTAssertTrue(mockFileManager.isStoredMethodCalled, "When the path is xxxxx, the isStored() method should be called.")
XCTAssertTrue(mockFileManager.isReadDataMethodCalled, "When the path is xxxxx, the readData() method should be called.")
}

func testGetCachedData_WhenPathIsNotXXXXX() {
let viewModel = SomeViewModel(fileManager: self.mockFileManager)
XCTAssertNil(viewModel.getCachedData(path: "abcde"), "When the path is anything apart from xxxxx, the getCachedData() method should return nil.")
XCTAssertTrue(mockFileManager.isStoredMethodCalled, "When the path is anything apart from xxxxx, the isStored() method should be called.")
XCTAssertFalse(mockFileManager.isReadDataMethodCalled, "When the path is anything apart from xxxxx, the readData() method should not be called.")
}
}

// MockFileManager is the mocked implementation of FileManager.
// Since it conforms to FileManagerProtocol, we can implement the
// methods of FileManagerProtocol with a different implementation
// for the assertions in the unit tests.
class MockFileManager: FileManagerProtocol {
private(set) var isStoredMethodCalled = false
private(set) var isReadDataMethodCalled = false

func isStored(atPath path: String) -> Bool {
isStoredMethodCalled = true
if path.elementsEqual("xxxxx") {
return true
}
return false
}

func readData(atPath path: String) -> Data? {
isReadDataMethodCalled = true
if path.elementsEqual("xxxxx") {
return Data()
}
return nil
}
}

请随意将上述所有类和单元测试复制粘贴到单独的 playground 文件中。要在 Playground 中运行两个单元测试,请编写 -

TestSomeViewModel.defaultTestSuite.run()

其他一些要记住的事情:-

  1. 建议先编写单元测试,运行它并查看它是否失败,然后编写通过单元测试所需的最少代码量。这叫做 Test Driven Development .
  2. 如果所有的实现类都使用依赖注入(inject),编写测试会更容易。
  3. 考虑避免使用单例。如果不小心使用单例,它们会使代码难以进行单元测试。欢迎阅读更多关于为什么我们应该谨慎使用单例的信息 herehere .

关于ios - 下面的代码应该怎么写测试用例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68820839/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com