Grails Mock Logging 带 Slf4j Test
本指南中,我们将学习如何在 Grails 中模拟和验证日志语句。
作者: Nirav Assar
Grails 版本 3.3.5
1 Grails 培训
Grails 培训 - 由创建和积极维护 Grails 框架的人员开发和提供的!。
2 开始
本指南中,您将通过使用 Slf4j Test 库,学习如何模拟日志记录和验证 Grails 应用程序中的日志事件。
2.1 需要具备的条件
要完成此指南,需要具备以下条件
-
一些时间
-
一个体面的文本编辑器或 IDE
-
已安装 JDK 1.8 或更高版本,并已正确配置了
JAVA_HOME
2.2 指南完成方式
准备时,执行以下操作
-
下载并解压源码
或
-
克隆 Git 存储库
git clone https://github.com/grails-guides/grails-mock-logging-slf4j-test.git
Grails 指南存储库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用程序,其中包含一些其他代码以方便您快速入门。 -
complete
一个完成的示例。这是完成指南所提供的步骤并对initial
文件夹应用这些更改的结果。
要完成此指南,请转到 initial
文件夹
-
cd
进入grails-guides/grails-mock-logging-slf4j-test/initial
并按照后续部分的说明进行操作。
如果您 cd 进入 grails-guides/grails-mock-logging-slf4j-test/complete ,则可以直接转到已完成示例 |
3 编写应用程序
我们将编写一个简单的应用程序并附带日志记录。对于一个简单的概念,我们将创建一个
首先,让我们配置应用程序,以便只记录与练习相关的项目。在
logger("example.grails", DEBUG, ['STDOUT'])
3.1 人员域和数据服务
我们将创建一个包含属性
> grails create-domain-class Person
编辑类,使其最终如下所示
package example.grails
import groovy.transform.CompileStatic
@CompileStatic
class Person {
String name
Integer age
String toString() {
"name: $name, age: $age"
}
}
为了创建和查找学生,我们将使用GORM Data Services,其会自动实现一个接口来提供数据访问逻辑。这可以注入到另一个 Grails 工件(例如,控制器)中。
package example.grails
import grails.gorm.services.Service
@Service(Person)
interface PersonDataService {
Person findPerson(String name)
Person savePerson(String name, Integer age)
}
3.2 创建人员
我们实现了创建
package example.grails
import grails.validation.ValidationException
import groovy.transform.CompileStatic
@CompileStatic
class PersonController {
static scaffold = Person
PersonDataService personDataService
def createPerson(String name, Integer age) {
try {
Person person = personDataService.savePerson(name, age)
log.info "person saved successfully: ${person}"
respond person, view: 'show'
} catch (ValidationException e) {
log.error "Error occurred on save!"
redirect action: "index"
}
}
}
3.3 为人员提供建议
在
def offerAdvice(String name) {
AgeAdvisor ageAdvisor = new AgeAdvisor()
Person person = personDataService.findPerson(name)
if (person) {
ageAdvisor.offerAgeAdvice(person.age)
} else {
log.error "No person by name ${name} found."
}
redirect action: "index"
}
POGO 很简单,并使用
package example.grails
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
@Slf4j (1)
@CompileStatic
class AgeAdvisor {
void offerAgeAdvice(Integer age) {
if (age in 0..<30 ) {
log.info ("You are a young and vibrant!")
log.info ("Live life to the fullest.")
} else if (30 <= age) {
log.warn ("It's all downhill from here, sorry.")
}
}
}
1 | 请注意 |
4使用 Slf4j Test 模拟日志记录
Slf4j Test是 Slf4j 的一种测试实现,可将日志信息存储在内存中并提供用于检索它们的方法,以便验证。它基本上是一种替代实现,并且应该是测试类路径上唯一的一种实现。为实现此目的,需要声明依赖项,并从 testCompile 构建阶段中排除 Logback 依赖项。
dependencies {
...
testCompile 'uk.org.lidalia:slf4j-test:1.1.0'
}
configurations {
testCompile.exclude group: 'ch.qos.logback', module: 'logback-classic'
}
此外,我们将使用
dependencies {
...
testCompile "org.grails:grails-datastore-rest-client"
}
4.1 在控制器中验证日志
我们可以使用 Slf4j Test 验证控制器中是否发生了日志事件。我们将创建一个 Grails 集成测试,以便自动注入数据服务,而不用担心模拟。我们将使用
对于每个测试,流程都非常简单
-
访问测试记录程序。
-
调用功能。
-
使用测试记录器检索事件。
-
根据情景验证事件。
package example.grails
import com.google.common.collect.ImmutableList
import grails.gorm.transactions.Rollback
import grails.plugins.rest.client.RestBuilder
import grails.plugins.rest.client.RestResponse
import grails.testing.mixin.integration.Integration
import spock.lang.Shared
import spock.lang.Specification
import uk.org.lidalia.slf4jext.Level
import uk.org.lidalia.slf4jtest.LoggingEvent
import uk.org.lidalia.slf4jtest.TestLogger
import uk.org.lidalia.slf4jtest.TestLoggerFactory
@Integration
@Rollback
class PersonControllerIntSpec extends Specification {
@Shared
TestLogger personControllerLogger = TestLoggerFactory.getTestLogger("example.grails.PersonController") (1)
@Shared
RestBuilder rest = new RestBuilder()
def cleanup() {
TestLoggerFactory.clearAll() (2)
}
void "test create person successful logs"() {
when:"a person is created"
RestResponse resp = rest.get("http://localhost:${serverPort}/person/createPerson?name=Nirav&age=40")
ImmutableList<LoggingEvent> loggingEvents = personControllerLogger.getAllLoggingEvents() (3)
then: "check the logging events"
resp.status == 200
loggingEvents.size() == 1 (4)
loggingEvents[0].message == "person saved successfully: name: Nirav, age: 40" (5)
loggingEvents[0].level == Level.INFO
}
}
1 | 使用记录器名称从 TestLoggerFactory 中检索测试记录器。 |
2 | 清除测试之间的内存中记录事件。 |
3 | 检索记录事件以便验证。 |
4 | 断言记录事件的大小。 |
5 | 验证消息及其后的日志级别。 |
4.2 验证更多日志
添加两个测试以涵盖其他情景。
void "test create person unsuccessful logs"() {
when:"a person is created, but has a input error"
RestResponse resp = rest.get("http://localhost:${serverPort}/person/createPerson?name=Bob&age=Twenty")
ImmutableList<LoggingEvent> loggingEvents = personControllerLogger.getAllLoggingEvents()
then: "check the logging events"
resp.status == 200
loggingEvents.size() == 1
loggingEvents[0].message == "Error occurred on save!"
loggingEvents[0].level == Level.ERROR
}
void "test offerAdvice to old person"() {
given: "A person is already created"
RestResponse resp = rest.get("http://localhost:${serverPort}/person/createPerson?name=John&age=35")
TestLogger ageAdvisorLogger = TestLoggerFactory.getTestLogger("example.grails.AgeAdvisor") (1)
when:"we ask for advice"
resp = rest.get("http://localhost:${serverPort}/person/offerAdvice?name=John")
ImmutableList<LoggingEvent> loggingEvents = ageAdvisorLogger.getAllLoggingEvents()
then: "check the logging events"
resp.status == 200
loggingEvents.size() == 1
loggingEvents[0].message == "It's all downhill from here, sorry."
loggingEvents[0].level == Level.WARN
}
1 | 注意,我们正在访问 example.grails.AgeAdvisor 的记录器。工厂能够访问任何记录器来验证活动。 |
4.3 在 Groovy POGO 中验证日志
Slf4j 测试以同样的方式处理 POGO 对象。在 AgeAdvisor
中,回想使用 @Slf4J
注解建立记录器。这不会改变验证日志事件的方法。
package example.grails
import com.google.common.collect.ImmutableList
import example.grails.AgeAdvisor
import spock.lang.Shared
import spock.lang.Specification
import uk.org.lidalia.slf4jext.Level
import uk.org.lidalia.slf4jtest.LoggingEvent
import uk.org.lidalia.slf4jtest.TestLogger
import uk.org.lidalia.slf4jtest.TestLoggerFactory
class AgeAdvisorSpec extends Specification {
@Shared
AgeAdvisor ageAdvisor = new AgeAdvisor()
@Shared
TestLogger logger = TestLoggerFactory.getTestLogger("example.grails.AgeAdvisor") (1)
def cleanup() {
TestLoggerFactory.clear()
}
void "verify young age logs"() {
when: "method is invoked"
ageAdvisor.offerAgeAdvice(15)
ImmutableList<LoggingEvent> loggingEvents = logger.getLoggingEvents()
then: "check the logging events"
loggingEvents.size() == 2 (2)
loggingEvents[0].message == "You are a young and vibrant!"
loggingEvents[0].level == Level.INFO
loggingEvents[1].message == "Live life to the fullest."
loggingEvents[1].level == Level.INFO
}
void "verify old age logs"() {
when: "method is invoked"
ageAdvisor.offerAgeAdvice(31)
ImmutableList<LoggingEvent> loggingEvents = logger.getLoggingEvents()
then: "check the logging events"
loggingEvents.size() == 1
loggingEvents[0].message == "It's all downhill from here, sorry."
loggingEvents[0].level == Level.WARN
}
}
1 | 在使用 @Slf4j 注解的 POGO 中,我们只需以相同的方式检索记录器。 |
2 | 请注意,我们能够验证多个日志事件。 |
5 运行应用程序
要运行应用程序,请使用 ./gradlew bootRun
命令,它将在 8080 端口上启动应用程序。
要运行测试,请使用 ./gradlew check
。