refactor: 通用技能按类别拆分为独立目录
skills/ → skills-dev(9), skills-req(10), skills-ops(4), skills-integration(8), skills-biz(4), skills-workflow(7) generate-marketplace.py 改为自动扫描所有 skills-* 目录。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
145
skills-dev/dev-test-plugin/skills/dev-test/android-testing.md
Normal file
145
skills-dev/dev-test-plugin/skills/dev-test/android-testing.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Android 测试 (JUnit + Espresso)
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 单元测试
|
||||
./gradlew test
|
||||
|
||||
# UI 测试
|
||||
./gradlew connectedAndroidTest
|
||||
```
|
||||
|
||||
## 单元测试 (JUnit)
|
||||
|
||||
```kotlin
|
||||
class TaskViewModelTest {
|
||||
@get:Rule
|
||||
val instantTaskRule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val coroutineRule = MainCoroutineRule()
|
||||
|
||||
private lateinit var viewModel: TaskViewModel
|
||||
private lateinit var repository: FakeTaskRepository
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
repository = FakeTaskRepository()
|
||||
viewModel = TaskViewModel(repository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetchTasks updates state`() = runTest {
|
||||
// Arrange
|
||||
repository.addTasks(listOf(
|
||||
Task(1, "Task 1"),
|
||||
Task(2, "Task 2")
|
||||
))
|
||||
|
||||
// Act
|
||||
viewModel.fetchTasks()
|
||||
|
||||
// Assert
|
||||
val tasks = viewModel.tasks.first()
|
||||
assertEquals(2, tasks.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createTask adds task`() = runTest {
|
||||
// Act
|
||||
viewModel.createTask("New Task")
|
||||
|
||||
// Assert
|
||||
val tasks = viewModel.tasks.first()
|
||||
assertTrue(tasks.any { it.title == "New Task" })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## UI 测试 (Espresso)
|
||||
|
||||
```kotlin
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class TaskListActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(TaskListActivity::class.java)
|
||||
|
||||
@Test
|
||||
fun displayTaskList() {
|
||||
onView(withId(R.id.taskList))
|
||||
.check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickTask_opensDetail() {
|
||||
onView(withId(R.id.taskList))
|
||||
.perform(RecyclerViewActions.actionOnItemAtPosition<TaskViewHolder>(0, click()))
|
||||
|
||||
onView(withId(R.id.taskDetail))
|
||||
.check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addTask_showsInList() {
|
||||
// Click add button
|
||||
onView(withId(R.id.addButton)).perform(click())
|
||||
|
||||
// Enter title
|
||||
onView(withId(R.id.titleInput))
|
||||
.perform(typeText("New Task"), closeSoftKeyboard())
|
||||
|
||||
// Save
|
||||
onView(withId(R.id.saveButton)).perform(click())
|
||||
|
||||
// Verify in list
|
||||
onView(withText("New Task"))
|
||||
.check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Compose UI 测试
|
||||
|
||||
```kotlin
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TaskListScreenTest {
|
||||
|
||||
@get:Rule
|
||||
val composeRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun taskList_displays() {
|
||||
val tasks = listOf(
|
||||
Task(1, "Task 1"),
|
||||
Task(2, "Task 2")
|
||||
)
|
||||
|
||||
composeRule.setContent {
|
||||
TaskListScreen(tasks = tasks)
|
||||
}
|
||||
|
||||
composeRule.onNodeWithText("Task 1").assertExists()
|
||||
composeRule.onNodeWithText("Task 2").assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun taskClick_callsOnClick() {
|
||||
var clickedId: Int? = null
|
||||
val tasks = listOf(Task(1, "Task 1"))
|
||||
|
||||
composeRule.setContent {
|
||||
TaskListScreen(
|
||||
tasks = tasks,
|
||||
onTaskClick = { clickedId = it.id }
|
||||
)
|
||||
}
|
||||
|
||||
composeRule.onNodeWithText("Task 1").performClick()
|
||||
|
||||
assertEquals(1, clickedId)
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user