วิธีที่ดีที่สุดในการทดสอบการสืบค้น SQL [ปิด]


109

ฉันประสบปัญหาในกรณีที่เรามีการสืบค้น SQL ที่ซับซ้อนอยู่เสมอโดยมีข้อผิดพลาด โดยพื้นฐานแล้วสิ่งนี้ส่งผลให้ส่งอีเมลไปยังลูกค้าที่ไม่ถูกต้องและ 'ปัญหา' อื่น ๆ เช่นนั้น

ประสบการณ์ของทุกคนในการสร้างแบบสอบถาม SQL เช่นนั้นคืออะไร? เรากำลังสร้างข้อมูลกลุ่มประชากรตามรุ่นใหม่ทุกสัปดาห์

ดังนั้นนี่คือความคิดและข้อ จำกัด บางประการของฉัน:

  • การสร้างข้อมูลทดสอบในขณะที่สิ่งนี้จะพิสูจน์ได้ว่าเรามีข้อมูลที่ถูกต้องทั้งหมด แต่ก็ไม่ได้บังคับใช้การยกเว้นความผิดปกติในการผลิต นั่นเป็นข้อมูลที่ถือว่าผิดในปัจจุบัน แต่อาจจะถูกต้องเมื่อ 10 ปีก่อน มันไม่ได้รับการจัดทำเป็นเอกสารดังนั้นเราจึงรู้หลังจากที่ดึงข้อมูลแล้วเท่านั้น

  • สร้างแผนภาพเวนน์และแผนที่ข้อมูลดูเหมือนว่าจะเป็นวิธีที่ดีในการทดสอบการออกแบบแบบสอบถาม แต่ก็ไม่ได้รับประกันว่าการนำไปใช้นั้นถูกต้อง ทำให้นักพัฒนาวางแผนล่วงหน้าและคิดถึงสิ่งที่เกิดขึ้นขณะเขียน

ขอบคุณสำหรับข้อมูลที่คุณสามารถให้กับปัญหาของฉัน

คำตอบ:


164

คุณจะไม่เขียนแอปพลิเคชันที่มีฟังก์ชันยาว 200 บรรทัด คุณจะแยกย่อยฟังก์ชันยาวเหล่านั้นออกเป็นฟังก์ชันย่อย ๆ โดยแต่ละฟังก์ชันมีความรับผิดชอบที่กำหนดไว้อย่างชัดเจนเพียงอย่างเดียว

ทำไมต้องเขียน SQL ของคุณแบบนั้น?

ย่อยสลายคำค้นหาของคุณเช่นเดียวกับที่คุณย่อยสลายฟังก์ชันของคุณ สิ่งนี้ทำให้สั้นลงง่ายขึ้นเข้าใจง่ายขึ้นทดสอบง่ายขึ้นง่ายต่อการ refactor และช่วยให้คุณสามารถเพิ่ม "shims" ระหว่างพวกมันและ "wrapper" รอบ ๆ พวกมันได้เช่นเดียวกับที่คุณทำในโค้ดขั้นตอน

คุณจะทำอย่างไร? การทำให้สิ่งสำคัญแต่ละอย่างที่แบบสอบถามทำในมุมมอง จากนั้นคุณจะเขียนข้อความค้นหาที่ซับซ้อนมากขึ้นจากมุมมองที่ง่ายกว่านี้เช่นเดียวกับที่คุณเขียนฟังก์ชันที่ซับซ้อนขึ้นจากฟังก์ชันดั้งเดิมมากขึ้น

และสิ่งที่ยอดเยี่ยมคือสำหรับการจัดองค์ประกอบภาพส่วนใหญ่คุณจะได้รับประสิทธิภาพที่เท่ากันทุกประการจาก RDBMS ของคุณ (สำหรับบางคนคุณจะไม่ทำอะไรการเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากเหง้าของความชั่วร้ายทั้งหมดตั้งรหัสให้ถูกต้องก่อนจากนั้นจึงเพิ่มประสิทธิภาพหากคุณต้องการ)

นี่คือตัวอย่างของการใช้หลายมุมมองเพื่อแยกการสืบค้นที่ซับซ้อน

ในตัวอย่างเนื่องจากแต่ละมุมมองเพิ่มการเปลี่ยนแปลงเพียงรายการเดียวแต่ละมุมมองสามารถทดสอบได้อย่างอิสระเพื่อค้นหาข้อผิดพลาดและการทดสอบก็ง่าย

นี่คือตารางฐานในตัวอย่าง:

create table month_value( 
    eid int not null, month int, year int,  value int );

ตารางนี้มีข้อบกพร่องเนื่องจากใช้สองคอลัมน์เดือนและปีเพื่อแสดงข้อมูลหนึ่งรายการซึ่งเป็นเดือนที่แน่นอน นี่คือข้อกำหนดของเราสำหรับคอลัมน์จากการคำนวณใหม่:

เราจะทำเช่นนั้นเป็นการแปลงเชิงเส้นเพื่อให้มันเรียงลำดับเช่นเดียวกับ (ปีเดือน) และสำหรับทูเปิล (ปีเดือน) ใด ๆ จะมีค่าเดียวและค่าทั้งหมดต่อเนื่องกัน:

create view cm_absolute_month as 
select *, year * 12 + month as absolute_month from month_value;

ตอนนี้สิ่งที่เราต้องทดสอบนั้นมีอยู่ในสเป็คของเรากล่าวคือสำหรับทูเพิลใด ๆ (ปีเดือน) มีหนึ่งและเพียงหนึ่ง (เดือนสัมบูรณ์) และ (absolute_month) ที่ต่อเนื่องกัน มาเขียนแบบทดสอบกัน

การทดสอบของเราจะเป็นselectแบบสอบถามSQL โดยมีโครงสร้างดังต่อไปนี้: ชื่อการทดสอบและคำสั่งกรณีที่รวมเข้าด้วยกัน ชื่อการทดสอบเป็นเพียงสตริงที่กำหนดเอง คำสั่งกรณีที่เป็นเพียงงบการทดสอบcase whenthen 'passed' else 'failed' end

คำสั่งทดสอบจะเป็นเพียง SQL เลือก (เคียวรีย่อย) ที่ต้องเป็นจริงเพื่อให้การทดสอบผ่าน

นี่เป็นการทดสอบครั้งแรกของเรา:

--a select statement that catenates the test name and the case statement
select concat( 
-- the test name
'For every (year, month) there is one and only one (absolute_month): ', 
-- the case statement
   case when 
-- one or more subqueries
-- in this case, an expected value and an actual value 
-- that must be equal for the test to pass
  ( select count(distinct year, month) from month_value) 
  --expected value,
  = ( select count(distinct absolute_month) from cm_absolute_month)  
  -- actual value
  -- the then and else branches of the case statement
  then 'passed' else 'failed' end
  -- close the concat function and terminate the query 
  ); 
  -- test result.

การเรียกใช้แบบสอบถามนั้นก่อให้เกิดผลลัพธ์นี้: For every (year, month) there is one and only one (absolute_month): passed

ตราบใดที่มีข้อมูลการทดสอบเพียงพอใน month_value การทดสอบนี้ก็ใช้ได้

เราสามารถเพิ่มการทดสอบสำหรับข้อมูลการทดสอบที่เพียงพอได้เช่นกัน:

select concat( 'Sufficient and sufficiently varied month_value test data: ',
   case when 
      ( select count(distinct year, month) from month_value) > 10
  and ( select count(distinct year) from month_value) > 3
  and ... more tests 
  then 'passed' else 'failed' end );

ตอนนี้เรามาทดสอบต่อเนื่องกัน:

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b 
on (     (a.month + 1 = b.month and a.year = b.year) 
      or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )  
where a.absolute_month + 1 <> b.absolute_month ) = 0 
then 'passed' else 'failed' end );

ตอนนี้เรามาทำการทดสอบของเราซึ่งเป็นเพียงแบบสอบถามลงในไฟล์และเรียกใช้สคริปต์นั้นกับฐานข้อมูล อันที่จริงถ้าเราเก็บนิยามมุมมองของเราไว้ในสคริปต์ (หรือสคริปต์ฉันขอแนะนำให้เรียกใช้หนึ่งไฟล์ต่อมุมมองที่เกี่ยวข้อง) เพื่อรันกับฐานข้อมูลเราสามารถเพิ่มการทดสอบของเราสำหรับแต่ละมุมมองลงในสคริปต์เดียวกันเพื่อให้การกระทำของ (re -) การสร้างมุมมองของเรายังเรียกใช้การทดสอบของมุมมอง ด้วยวิธีนี้เราทั้งคู่จะได้รับการทดสอบการถดถอยเมื่อเราสร้างมุมมองใหม่และเมื่อการสร้างมุมมองทำงานเทียบกับการผลิตมุมมองจะถูกทดสอบในการผลิตด้วย


27
นี่เป็นครั้งแรกที่ฉันเห็นรหัสที่สะอาดและการทดสอบหน่วยใน sql ฉันมีความสุขสำหรับวันนี้ :)
Maxime ARNSTAMM

1
แฮ็ก sql ที่ยอดเยี่ยม
CodeFarmer

13
นี่เป็นสิ่งที่ดี แต่ทำไมต้องใช้ชื่อตัวอักษรเดียวสำหรับคอลัมน์และชื่อมุมมองที่อ่านได้ยาก? เหตุใด SQL จึงควรจัดทำเอกสารในตัวเองหรืออ่านได้น้อยกว่า Python
snl

1
คำอธิบายที่ยอดเยี่ยมสำหรับสิ่งที่เป็นประโยชน์ที่ฉันไม่เคยมองในโลกของ SQL / DB ฉันชอบวิธีที่คุณทดสอบฐานข้อมูลที่นี่เช่นกัน
Jackstine

เช่นเดียวกับคำเตือนฉันเคยเห็นมุมมอง sql ที่เข้าร่วมกับมุมมอง sql ทำงานได้ไม่ดีใน PostgreSQL อย่างไรก็ตามฉันได้ใช้เทคนิคนี้อย่างมีประสิทธิภาพกับ M $ SQL
Ben Liyanage

6

สร้างฐานข้อมูลระบบทดสอบที่คุณสามารถโหลดซ้ำได้บ่อยเท่าที่คุณต้องการ โหลดข้อมูลของคุณหรือสร้างข้อมูลของคุณและบันทึกไว้ สร้างวิธีง่ายๆในการโหลดซ้ำ แนบระบบการพัฒนาของคุณเข้ากับฐานข้อมูลนั้นและตรวจสอบรหัสของคุณก่อนที่คุณจะไปที่การผลิต เตะตัวเองทุกครั้งที่จัดการเพื่อให้ปัญหาเข้าสู่การผลิต สร้างชุดการทดสอบเพื่อตรวจสอบปัญหาที่ทราบและขยายชุดทดสอบของคุณเมื่อเวลาผ่านไป


4

คุณอาจต้องการตรวจสอบDbUnitดังนั้นคุณอาจลองเขียนการทดสอบหน่วยสำหรับโปรแกรมของคุณด้วยชุดข้อมูลที่ตายตัว ด้วยวิธีนี้คุณจะสามารถเขียนข้อความค้นหาที่มีผลลัพธ์ที่คาดเดาได้ไม่มากก็น้อย

สิ่งอื่นที่คุณอาจต้องทำคือสร้างโปรไฟล์สแต็กการดำเนินการ SQL Server ของคุณและดูว่าแบบสอบถามทั้งหมดเป็นคำค้นหาที่ถูกต้องหรือไม่เช่นหากคุณใช้แบบสอบถามเพียงรายการเดียวซึ่งส่งคืนทั้งผลลัพธ์ที่ถูกต้องและไม่ถูกต้องให้ชัดเจนว่าแบบสอบถามนั้น ใช้เป็นคำถาม แต่จะเกิดอะไรขึ้นหากแอปพลิเคชันของคุณส่งคำถามที่แตกต่างกันไปตามจุดต่างๆในโค้ด

ความพยายามใด ๆ ในการแก้ไขแบบสอบถามของคุณก็จะไร้ผล ... คำค้นหาที่หลอกลวงอาจยังคงเป็นคำค้นหาที่ทำให้เกิดผลลัพธ์ที่ไม่ถูกต้องอยู่ดี


2

เรื่อง tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b  
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )   
where a.am + 1 <> b.am ) = 0  

โปรดทราบว่าสิ่งนี้จะตรวจสอบว่าค่า am สำหรับเดือนติดต่อกันจะติดต่อกันไม่ใช่ข้อมูลที่ต่อเนื่องกันอยู่ (ซึ่งอาจเป็นสิ่งที่คุณตั้งใจไว้ในตอนแรก) สิ่งนี้จะผ่านไปเสมอหากไม่มีข้อมูลต้นทางของคุณติดต่อกัน (เช่นคุณมีจำนวนเดือนที่เป็นเลขคู่เท่านั้น) แม้ว่าการคำนวณ am ของคุณจะถูกปิดโดยสิ้นเชิงก็ตาม

ฉันยังขาดอะไรบางอย่างหรือครึ่งหลังของประโยค ON นั้นชนค่าเดือนที่ไม่ถูกต้อง? (คือเช็คว่า 12/2011 มาหลัง 1/2553)

ที่แย่กว่านั้นคือถ้าฉันจำไม่ผิดอย่างน้อย SQL Server ก็อนุญาตให้คุณมีมุมมองน้อยกว่า 10 ระดับก่อนที่เครื่องมือเพิ่มประสิทธิภาพจะโยนมือเสมือนขึ้นไปในอากาศและเริ่มทำการสแกนแบบเต็มตารางในทุกคำขอดังนั้นอย่าทำวิธีนี้มากเกินไป

อย่าลืมทดสอบ heck จากกรณีทดสอบของคุณ!

มิฉะนั้นการสร้างชุดข้อมูลที่กว้างมากเพื่อครอบคลุมรูปแบบอินพุตส่วนใหญ่หรือทั้งหมดที่เป็นไปได้โดยใช้ SqlUnit หรือ DbUnit หรือหน่วย * อื่น ๆ เพื่อตรวจสอบผลลัพธ์ที่คาดหวังกับข้อมูลนั้นโดยอัตโนมัติและการตรวจสอบบำรุงรักษาและอัปเดตตามความจำเป็นโดยทั่วไปดูเหมือนจะเป็น ทางไป.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.