首页 安卓& Kotlin Books Android测试驱动驱动的教程

16
处理测试数据的策略 由lance gleason撰写

在前三章中,你学会了如何快速移动慢速。既然您开始添加新功能,您的测试套件开始变大。许多无家可归的编码伴侣正在与开发人员放置。但是,正如那样的情况一样,一个不可避免的问题就是呈现自己。即,您的测试数据开始难以维护。对于某些测试,它对于其他人来说,它是一个丢弃的文件和/或类的混血。

有许多方法可以解决这些问题,您可以在本章中了解。但是,你不太可能找到一个魔法的银弹,解决了所有这些。

JSON数据

在过去的三章中,你大量使用模仿韦斯勒。当您开始将服务器置于测试时,最简单的方法是使用邮递员等工具进行请求,并将数据放入MockWebServer的JSON文件中。然后,您将结束拦截呼叫的调度程序,在这些文件中读取,并将内容放在响应主体中。

要在行动中查看此操作,请从上一章打开本章或最终项目的入门项目。

笔记:项目中的测试仅在编译为JUnit测试时工作。

看着那(这 commontestdatautil.kt. 在里面的帮助阶级 COM▸raywenderlich▸鳕鱼Panionfinder 测试目录。查看您的调度员,您将看到以下内容:

fun dispatch(request: RecordedRequest): MockResponse? {
  return when (request.path) {
    "/animals?limit=20&location=30318" -> {
      MockResponse().setResponseCode(200).setBody(
        readFile("search_30318.json")
      )
    }
    "/animals?limit=20&location=90210" -> {
      MockResponse().setResponseCode(200).setBody(
        readFile("search_90210.json")
      )
    }
    else -> {
      MockResponse().setResponseCode(404).setBody("{}")
    }
  }
}

在此示例中,您在请求路径上进行精确匹配,并根据请求指定文件。这样做使得可以轻松获取,但它将很快导致一个非常大的调度功能,因为您正在使用的JSON文件的数量。

处理大调度功能的一种方法是解析URL,并使用这些参数将其JSON对象传送到负载。例如,与 编码伴侣发现者 应用程序,您可能有一个看起来这样的调度方法:

fun dispatch(request: RecordedRequest): MockResponse? {
  val fileNameAndPath = getFileNameAndPath(request.path)
  return if (!fileNameAndPath.isEmpty()) {
      MockResponse().setResponseCode(200).setBody(
        readFile(fileNameAndPath)
      )
    else -> {
      MockResponse().setResponseCode(404).setBody("{}")
    }
  }
}

getFileNameAndPath(path: String) 通过替换字符将URL转换为文件名。

For example, a path of /animals?limit=20&location=30318 becomes animals_limit_20_location_30318.json, and /animals?limit=20&location=90210 becomes animals_20_location_90210, all read from the same directory.

Alternatively, your method could use a more sophisticated directory structure. /big/animals?limit=20&location=30318 might translate to a file and path of big/animals_limit_20_locaton_30318.json, and small/animals?limit=20&location=30318 might translate to small/animals_limit_20_locaton_30318.json.

发布请求

到现在为止,你MockWebServer只处理GET请求。现在是时候看看如何处理POST请求。

class AuthorizationInterceptor : Interceptor, KoinComponent {
  private val petFinderService: PetFinderService by inject()
  private var token = Token()
  @Throws(IOException::class)
  // 1
  override fun intercept(chain: Interceptor.Chain): Response {
    var mainResponse = chain.proceed(chain.request())
    val mainRequest = chain.request()
    // 2
    if ((mainResponse.code() == 401 ||
        mainResponse.code() == 403) &&
        !mainResponse.request().url().url()
          .toString().contains("oauth2/token")) {
      // 3
      val tokenRequest = petFinderService.getToken(
        clientId = MainActivity.API_KEY,
        clientSecret = MainActivity.API_SECRET)
      val tokenResponse = tokenRequest.execute()
      if (tokenResponse.isSuccessful) {
        // 4
        tokenResponse.body()?.let {
          token = it
          // 5
          val builder =
            mainRequest.newBuilder().header("Authorization",
                "Bearer " + it.accessToken)
              .method(mainRequest.method(), mainRequest.body())
          mainResponse = chain.proceed(builder.build())
        } }
    }
    // 6
    return mainResponse
  }
}

fun dispatch(request: RecordedRequest): MockResponse? {
  val headers = request.headers
  // 1
  if(request.method.equals("POST")){
    if(request.path.equals("/oauth2/token")){
      return MockResponse().setResponseCode(200).setBody(
        "{\"access_token\":\"valid_token\"}")
    }
  }
  // 2
  val authorization = headers.values("Authorization")
  if (!authorization.isEmpty() &&
      authorization.get(0).equals("Bearer valid_token")) {
    return when (request.path) {
      "/animals?limit=20&location=30318" -> {
        MockResponse().setResponseCode(200).setBody(
          CommonTestDataUtil.readFile("search_30318.json")
        )
      }
      "/animals?limit=20&location=90210" -> {
        MockResponse().setResponseCode(200)
          .setBody("{\"animals\": []}")
      }
      else -> {
        MockResponse().setResponseCode(404).setBody("{}")
      }
    }
  } else {
    // 3
    return MockResponse().setResponseCode(401).setBody("{}")
  }
}

val tokenRequest = petFinderService.getToken(
  clientId = MainActivity.API_KEY,
  clientSecret = MainActivity.API_SECRET)
private val petFinderService: PetFinderService by inject()
fun nonInterceptedDispatch(
  request: RecordedRequest
): MockResponse? {
  val headers = request.headers
  return when (request.path) {
    "/animals?limit=20&location=30318" -> {
      MockResponse().setResponseCode(200).setBody(
      readFile("search_30318.json")
      )
    }
    "/animals?limit=20&location=90210" -> {
      MockResponse().setResponseCode(200)
        .setBody("{\"animals\": []}")
    }
    else -> {
      MockResponse().setResponseCode(404).setBody("{}")
    }
  }
}
val dispatcher: Dispatcher = object : Dispatcher() {
  @Throws(InterruptedException::class)
  override fun dispatch(
    request: RecordedRequest
  ): MockResponse {
    return CommonTestDataUtil.nonInterceptedDispatch(request) ?:
      MockResponse().setResponseCode(404)
  }
}

硬编码数据

您使用的另一种方法是硬编码数据。当您首先开始获得被测应用程序时,快速入门的方式是在测试功能中或在与测试中的同一类中进行硬代码测试值。在 COM▸raywenderlich▸鳕鱼Panionfinder, 打开你的 ViewCompanionViewModeltest.kt. 你会看到以下内容:

class ViewCompanionViewModelTest {
// 1
  val animal = Animal(
    22,
    Contact(
      phone = "404-867-5309",
      email = "[email protected]",
      address = Address(
        "",
        "",
        "Atlanta",
        "GA",
        "30303",
        "USA"
      ) ),
    "5",
    "small",
    arrayListOf(),
    Breeds("shih tzu", "", false, false),
    "Spike",
    "male",
    "A sweet little guy with spikey teeth!"
  )

  @Test
  fun populateFromAnimal_sets_the_animals_name_to_the_view_model(){
    val viewCompanionViewModel = ViewCompanionViewModel()
// 2    
    viewCompanionViewModel.populateFromAnimal(animal)
// 3    
    assert(viewCompanionViewModel.name.equals("Spike"))
  }
}

测试对象库

为解决硬编码的数据问题的一种方法是创建一个测试对象库。这是最好的代码解释。

object AnimalData {
  val atlantaShihTzuNamedSpike = Animal(
    22,
    atlantaCodingShelter,
    "5",
    "small",
    arrayListOf(),
    shihTzu,
    "Spike",
    "male",
    "A sweet little guy with spikey teeth!"
  )
}

object AddressData {
  val atlantaAddress = Address(
    "",
    "",
    "Atlanta",
    "GA",
    "30303",
    "USA"
  )
}

object BreedsData {
  val shihTzu = Breeds("shih tzu", "", false, false)
}

object ContactsData {
  val atlantaCodingShelter = Contact(
    phone = "404-867-5309",
    email = "[email protected]",
    address = atlantaAddress
  )
}
class ViewCompanionViewModelTest {

  @Test
  fun populateFromAnimal_sets_the_animals_name_to_the_view_model() {
    val viewCompanionViewModel = ViewCompanionViewModel()
    viewCompanionViewModel
      .populateFromAnimal(atlantaShihTzuNamedSpike)
    assert(viewCompanionViewModel.name
      .equals(atlantaShihTzuNamedSpike.name))
  }
}

骗子

截至目前,每次在测试中使用它们时,您的实际测试值都是硬编码字符串。随着时间的推移,您可能会耗尽名称的想法。除此之外,您的测试数据中还没有很多种类,这可能导致您缺少某些边缘案例。但别担心,有一个工具来帮助你解决这个问题: 骗子。首先在第10章中看到这一点,“测试网络层”。

testImplementation 'com.github.javafaker:javafaker:0.18'
androidTestImplementation 'com.github.javafaker:javafaker:0.18'
val faker = Faker()
val fakerAnimal = Animal(
  faker.number().digits(3).toInt(),
  fakerShelter,
  faker.number().digit(),
  faker.commerce().productName(),
  arrayListOf(),
  fakerBreed,
  faker.name().name(),
  faker.dog().gender(),
  faker.chuckNorris().fact()
)
val fakerAddress = Address(
  faker.address().streetAddress(),
  faker.address().secondaryAddress(),
  faker.address().city(),
  faker.address().state(),
  faker.address().zipCode(),
  faker.address().country()
)
val fakerBreed = Breeds(faker.cat().breed(),
  faker.dog().breed(), faker.bool().bool(), faker.bool().bool())
val fakerShelter = Contact(
  faker.phoneNumber().cellPhone(),
  faker.internet().emailAddress(),
  fakerAddress
)
@Test
fun populateFromAnimal_sets_the_animals_description_to_the_view_model(){
  val viewCompanionViewModel = ViewCompanionViewModel()
  System.out.println(fakerAnimal.toString())
  viewCompanionViewModel.populateFromAnimal(fakerAnimal)
  assertEquals("faker", viewCompanionViewModel.description)
}

Animal(
  id=798,
  contact=Contact(
    phone=1-256-143-0873,
    [email protected],
    address=Address(
      address1=09548 Wayne Dale,
      address2=Suite 523,
      city=Charitybury,
      state=West Virginia,
      postcode=30725-9938,
      country=Northern Mariana Islands
      )
    ),
    age=0,
    size=Synergistic Wool Bottle,
    photos=[],
    breeds=Breeds(
      primary=Khao Manee,
      secondary=Sealyham Terrier,
      mixed=false,
      unknown=false),
    name=Miss Linnea Hills,
    gender=female,
    description=For Chuck Norris, NP-Hard = O(1).)

assertEquals(fakerAnimal.description,
  viewCompanionViewModel.description)

当地持久的数据

如果你有一个应用程序在本地的竞争仍然存在,你可能有一些测试,需要有数据存储在一定的状态运行测试之前。

关键点

  • 没有Magic Silver Bullets,具有测试数据。
  • 快速入门终端到终端的测试,但可以变得难以维护的测试套件获得更大的JSON数据是巨大的。
  • 硬编码数据的效果很好,当你的测试套件虽小,但作为测试套件的增长缺乏各种测试数据。
  • 骗子更轻松地为对象库生成各种测试数据。
  • 测试,因为你需要将数据插入到数据存储程序需要获得的数据存储到某种状态可能很昂贵。

然后去哪儿?

争吵您的测试数据是另一种方式,即您能够慢慢移动速度。在您的情况下,快速意味着更多的家庭和程序员具有更高质量代码的同伴。要了解有关Faker的更多信息,请查看项目页面 //github.com/DiUS/java-faker.

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您对UI,UX,高亮反馈,或者我们的在线读者的其他功能,您可以付款给设计团队与下面的表格:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解开这本书,我们的书籍和视频整个目录,具有raywenderlich.com专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。