假设我有一个简单的Django模型:
class Transaction(models.Model):
description = models.CharField('description', max_length=150,
validators=[MinLengthValidator(2, 'Description\'s min length is 2'), ])
amount = models.DecimalField('amount', max_digits=10, decimal_places=2,
validators=[MinValueValidator(1, 'Min value is 1'), ])
user = models.ForeignKey(User)
# to trigger model fields' validation
def clean(self, *args, **kwargs):
super(Transaction, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(Transaction, self).save(*args, **kwargs)
我想进行单元测试,它可以精确地检查ValidationError
是否由 description 字段而不是 amount 字段(或任何其他字段)引起其他)。
所以我有一段测试,它以原始方式检查e.exception
中是否存在 description 字段:
def test_model_requires_description_min_2_characters(self):
with self.assertRaises(ValidationError) as e:
Transaction.objects.create(description='a', amount="50", user=self.user1)
err_dict = eval(str(e.exception))
self.assertIn('description', err_dict.keys())
但是我真的不喜欢使用eval()
,并且我相信还有一种更优雅的方式来表明ValidationError
的来源。我该怎么办?
编辑:我的模型类还包含重写的clean()
和save()
方法,因此验证程序运行良好。
答案 0 :(得分:2)
我会做这样的事情,Exception in thread "Thread-1" java.lang.IllegalArgumentException: Object must be initiated on a crossroad.
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:135)
at com.github.rinde.rinsim.core.model.road.GraphRoadModelImpl.addObjectAt(GraphRoadModelImpl.java:106)
at be.kuleuven.cs.LuysterborgMonsecour.executionContext.ExecutionContext.addSmartMessage(ExecutionContext.java:68)
at be.kuleuven.cs.LuysterborgMonsecour.agent.DeliveryAgent.tickImpl(DeliveryAgent.java:157)
...
具有属性ValidationError
,我们已经可以使用它来进行测试
error_dict
答案 1 :(得分:0)
并非所有library(shiny)
library(magrittr) # for pipe operator, %>%, used with debounce().
debounceDelay = 2000 # milliseconds
ui <- fluidPage(
titlePanel("Data affect Slider, both affect Subsequent Long Computation"),
sidebarLayout(
sidebarPanel(
# Data input:
textAreaInput(
inputId = "dataText" ,
label = "Type data, then click Submit:" ,
value = "10 20 30 40" ,
width = "200px" ,
height = "100px"
) ,
p(actionButton(inputId = "dataSubmit" ,
label = "Submit Data")) ,
# Slider input, to be updated by data:
sliderInput(
inputId = "slider1" ,
label = HTML("Constant to Add to Mean of Data
(after debounce delay):") ,
min=3000 , max=5000 , value=4000 ,
round = FALSE ,
step = 1 ,
ticks = FALSE
)
) ,
# end sidebarPanel
mainPanel(textOutput("theOutput"))
) # end sidebarLayout
) # end ui fluidPage
server <- function(input, output, session) {
sliderUpdates <- reactiveValues(latestProgrammatic = Sys.time(), timeDiff = 0)
# Parse data values out of data text:
theData = reactive({
input$dataSubmit# establish dependency on dataSubmit button
yText = isolate(input$dataText) # remove dependency on dataText
y = as.numeric(unlist(strsplit(yText, "\\s+")[[1]]))
if (any(is.na(y)) | length(y) < 2) {
y = c(-12.3, 45.6, 78.9) # arbitrary replacement values
updateTextAreaInput(session ,
inputId = "dataText" ,
value = paste(as.character(y), collapse = " "))
}
return(y)
})
# Computation on data for using in slider update:
upUI <- reactive({
low = min(theData())
val = median(theData())
high = max(theData())
return(list(
low = low ,
val = val ,
high = high
))
})
# Update slider based on data values:
observeEvent(upUI(), {
sliderUpdates$latestProgrammatic <- Sys.time()
print(paste("Programmatic slider update was triggered:" , sliderUpdates$latestProgrammatic))
updateSliderInput(
session ,
inputId = "slider1" ,
min = upUI()$low ,
max = upUI()$high ,
value = upUI()$val
)
})
# Debounce the slider value so it doesn't instantly trigger a cascade of long
# computations
sliderValue <- reactive({
latestUnkown <- Sys.time()
print(paste("Slider was updated:" , latestUnkown))
sliderUpdates$timeDiff <- latestUnkown - sliderUpdates$latestProgrammatic
req(input$slider1)
return(input$slider1)
}) %>% debounce(debounceDelay)
# Compute output:
output$theOutput <- renderText({
req(theData(), sliderValue(), req(isolate(sliderUpdates$timeDiff)))
print(paste("Elapsed time since the last programmatic slider update:", isolate(sliderUpdates$timeDiff)))
if(isolate(sliderUpdates$timeDiff) > 0.2){
Sys.sleep(3) # simulate lengthy computation time
return(
paste(
"Time-consuming computation...
Mean of data plus slider value: " ,
mean(theData()) + sliderValue()
)
)
} else {
NULL
}
}) # end of renderText
} # end server
shinyApp(ui = ui, server = server)
对象都 有ValidationError
。我们可以从implementation of the constructor of the ValidationError
[GitHub]中得出。这取决于error_dict
(构造的第一个参数)是否是字典。
我们可以要做的是为此使用getattr(..)
[Python-doc],其后备值如下:
message
因此,鉴于def test_model_requires_description_min_2_characters(self):
with self.assertRaises(ValidationError) as e:
Transaction.objects.create(description='a', amount="50", user=self.user1)
self.assertIn('description', getattr(e.exception, 'err_dict', {}))
不存在,我们将让error_dict
返回一个空字典,因此getattr(..)
将失败。
我们还可以为此实现一个实用程序功能,例如:
assertIn
因此,您可以在提供额外的断言功能的实用程序类中将此类_singleton = object()
class SomeTestCase(TestCase):
def assertKeyInErrorDict(self, key, error):
error_dict = getattr(error, 'err_dict', _singleton)
if error_dict is _singleton:
self.fail('The error {} has no error_dict'.format(error))
else:
self.assertIn(key, error_dict)
def test_model_requires_description_min_2_characters(self):
with self.assertRaises(ValidationError) as e:
Transaction.objects.create(description='a', amount="50", user=self.user1)
self.assertKeyInErrorDict('description', e.exception)
关联起来,然后在所有子类中使用它,从而删除许多 boilerplate 代码。